Регулярное выражение для получения содержимого таблицы HTML - PullRequest
3 голосов
/ 06 сентября 2011

Я наткнулся здесь на небольшую проблему: как получить содержимое таблицы в HTML с помощью регулярного выражения.Допустим, это наша таблица:

<table someprop=2 id="the_table" otherprop="val">
    <tr>
        <td>First row, first cell</td>
        <td>Second cell</td>
    </tr>
    <tr>
        <td>Second row</td>
        <td>...</td>
    </tr>
    <tr>
        <td>Another row, first cell</td>
        <td>Last cell</td>
    </tr>
</table>

Я уже нашел метод, который работает, но включает несколько регулярных выражений, которые должны быть выполнены в шагах:

  1. Получите правотаблицы и поместите ее строки в обратную ссылку 1 (в документе может быть больше одной):

    <table[^>]*?id="the_table"[^>]*?>(.*?)</table>

  2. Получить строки таблицы ипоместите ячейки в обратную ссылку 1:

    <tr.*?>(.*?)</tr>

  3. И, наконец, получите содержимое ячейки в обратной ссылке 1:

    <td.*?>(.*?)</td>

Теперь все это хорошо, но было бы бесконечно более круто делать все это с помощью одного причудливого регулярного выражения ... Кто-нибудь знает, возможно ли это?

Ответы [ 2 ]

3 голосов
/ 06 сентября 2011

На самом деле не существует возможного решения для регулярных выражений, которое работает для произвольного числа табличных данных и помещает каждую ячейку в отдельную обратную ссылку.Это связано с тем, что для обратных ссылок вам нужно иметь отдельную открытую скобку для каждого обратного ссылки, которую вы хотите создать, и вы не знаете, сколько у вас ячеек.

Нет ничего плохого в использовании зацикливания того или иного рода для извлечения данных.Например, на последнем в Perl это будет так, поскольку $tr уже содержит нужную вам строку:

@td = ( $tr =~ m{<td.*?>(.*?)</td>}sg );

Теперь $td[0] будет содержать первые <td>, $td[1] будет содержать второй и т. д. Если вам нужен двумерный массив, вы можете заключить его в такой цикл, чтобы заполнить новую переменную @cells:

our $table;  # assume has full table in it
my @cells;
while(my($tr) =~ $table = m{<tr.*?>(.*?)</tr>}sg) {
    push @cells, [ $tr =~ m{<td.*?>(.*?)</td>}sg ];
}

Теперь вы можете сделать дваадресация, допускающая $cells[0][0] и т. д. Внешний явный цикл обрабатывает таблицу по очереди, а внутренний неявный цикл вытягивает все ячейки.

Это будет работать с постоянными выборочными данными.ты показал.Если это достаточно хорошо для вас, тогда отлично.Используйте его и двигайтесь дальше.

Что с этим может быть не так?

Однако в ваших шаблонах есть довольно много предположений относительно содержания ваших данных, которые я не делаюзнаю, что вы знаете.Во-первых, обратите внимание, как я использовал /s, чтобы он не застревал на новых строках.

Но главная проблема в том, что минимальные совпадения не всегда вполне соответствуют тому, что вы хотите здесь.По крайней мере, не в общем случае.Иногда они не так минимальны, как вы думаете, соответствуют больше, чем вы хотите, а иногда они просто не соответствуют.

Например, шаблон, подобный <i>(.*?)</i>, получит больше, чем вы хотите, если строка:

<i>foo<i>bar</i>ness</i>

Поскольку вы в конечном итоге сопоставите строку <i>foo<i>bar</i>.

Другая распространенная проблема (не считая необычных) состоит в том, что шаблон, подобный <tag.*?>, может слишком мало совпадать, например, с

<img alt=">more" src="somewhere">

Теперь, если вы используете упрощенный <img.*?> на этом, вы бы только захватили <img alt=">, что, конечно, неправильно.

Я думаю, что последняя серьезная проблема заключается в том, что вы должны вообще игнорировать некоторые вещи при разборе.Простейшая демонстрация этого встроенного комментария (также <script>, <style>, and CDATA`), поскольку у вас может быть что-то вроде

<i> some <!-- secret</i>  --> stuff </i>

, которое будет сбрасывать что-то вроде <i>(.*?)</i>.

Конечно, есть способы обойти это.После того, как вы это сделали, и это действительно немало усилий, вы обнаружите, что создали себе настоящий парсер, полностью с множеством вспомогательной логики, а не с одним паттерном.

Даже тогда вы обрабатываете только правильно сформированные входные строки.Восстановление после ошибок и мягкий сбой - совершенно другое искусство.

2 голосов
/ 06 сентября 2011

Этот ответ был добавлен до того, как стало известно, что ОП требовалось решение для c ++ ...

Поскольку использование regex для анализа html технически неверно, я предложу лучшее решение. Вы можете использовать js, чтобы получить данные и поместить их в двумерный массив. Я использую jQuery в примере.

var data = [];
$('table tr').each(function(i, n){
    var $tr = $(n);
    data[i] = [];
    $tr.find('td').text(function(j, text){
        data[i].push(text);
    });
});

jsfiddle из примера: http://jsfiddle.net/gislikonrad/twzM7/

EDIT

Если вам нужен простой способ сделать это на javascript (без использования jQuery), то это может быть больше для вас:

var data = [];
var rows = document.getElementById('the_table').getElementsByTagName('tr');
for(var i = 0; i < rows.length; i++){
    var d = rows[i].getElementsByTagName('td');
    data[i] = [];
    for(var j = 0; j < d.length; j++){
       data[i].push(d[j].innerText);     
    }
}

Обе эти функции возвращают данные одинаково.

...