Обычно парсятся целые страницы, но иногда нужно написать функцию или даже просто распарсить всего-лишь один тег.
Рассмотрим задачу: есть тег textarea, у которого может быть сколько угодно совершенно разных атрибутов. Сколько, каких и каков их порядок — не известно. Задача — получить хэш, ключами которого будут имена атрибутов, а значениями — их значения (опа, каламбур).
Итак, вот тестовый код.
<textarea onkeypress='CaracMax(this, 20) ;' wrap="VIRTUAL" rows="0" cols="0" name="content_text" id="content_text"
moslabel="Основной текст" mosreq="1" class="boss_required"></textarea>
Как видим, тут есть всякие нестандартные атрибуты, обработчик JavaScript и теги со значениями, содержащими пробелы и произвольное содержимое.
Когда я увидел задачу, я решил что нет ничего сложного — сейчас мы все разобъем на кусочки вида: атрибут="значение", а потом каждый кусочек разобъем split-ом и вуаля. Но тут же понял, что разбить по пробелам не получится, что затрудняет задачу.
Итак, алгоритм будет таким:
Перед тем, как разбивать строку на чанки, я решил вытащить все атрибуты одной строкой, чтобы не путаться с тегами.
my ($area) = $content =~ /
Дальше можно добавить проверку. Если $area пустая, то парсить дальше нечего. Но так как у нас учебный пример и мы знаем что есть что парсить — продолжим так.
Сейчас нужно разбить строку с атрибутами на чанки.
$area =~ /\b(\w+=(["'])[^\2]+?\2)/g;
Мы находим границу слова (\b), за которой следуют символы слова в количестве минимум 1 штука. Затем идет знак равенства. Затем либо ', либо ". Причем, нам не важно какая именно кавычка тут будет. Но важно запомнить ее. Запоминаем ее в $2. Внутри кавычек не должно быть открывающей, что мы и пишем в классе символов: [^\2]. Символ, не являющийся \2 повторяется 1 или более раз до тех пор, пока не упрется в ту самую $2-кавычку. Все.
Но если мы напишем просто
@pairs = $area =~ /\b(\w+=(["'])[^\2]+?\2)/g;
Мы получим мусорные элементы в @pairs. А именно — кавычки из $2. Поэтому я использую мощь Perl и на лету отфильтрую их, добавив grep.
my @pairs = grep {$_!~/^['"]$/} $area =~ /\b(\w+=(["'])[^\2]+?\2)/g;
Grep проверяет. Если элемент, попавший в него — кавычка, то он его отбрасывает. Если нет — пропускает в массив @pairs.
Теперь у нас есть массив @pairs, который содержит чанки имя-значение. Осталось только распарсить их. Мы будем перебирать их в массиве. И первое, что нужно сделать — разбить их по знаку "=".
foreach my $pair(@pairs) {
my ($key, $value) = split /\s*=\s*/, $pair;
}
На всякий случай, я допускаю что вокруг знака равенства могут стоять пробелы.
Теперь появляется еще одна небольшая проблемка. Так как значение окружено кавычками — $value тоже будет окружено ими, что нам не нужно. Поэтому я предлагаю опять на лету удалять ведущие и замыкающие кавычки.
foreach my $pair(@pairs) {
push @attr, map {$_=~s/(^['"]|['"]$)//g;$_;} split /\s*=\s*/, $pair;
}
map ищет любую из кавычек в начале и конце каждого элемента, полученного от split, удаляет их и возвращает значение. Конечно, он будет срабатывать через раз — только на второй элемент результирующего массива split. Первый — это название атрибута, которое не содержит кавычек.
Как бы то ни было, мы получили массив @attr, который имеет структуру: (ключ, значение, ключ, значение, ключ, значение).
Но нам нужен хэш! Но это не проблема. Сделать из массива такого вида хэш — проще пареной репы.
my %attr = @attr;
Все. Теперь у нас есть хэш, ключами которого являются названия атрибутов, а значениями — их значения.
Сейчас все пишут на php, поэтому я решил перевести свое решение на этот язык, чтобы им можно было пользоваться большему количеству читателей.
Алгоритм подробно описывать не буду, потому что он практически идентичен вышеприведенному.
preg_match("/
preg_match_all("/\b(\w+=([\"'])[^\\2]+?\\2)/", $area, $pairs);
$pairs = $pairs[0];
Для работы array_map требует функцию. У меня не получилось использовать анонимную функцию (возможно, я просто не понял как это сделать). Поэтому заводим ее отдельно.
function trim_quotes($data) {
$data = preg_replace("/(^['\"]|['\"]$)/","",$data);
return $data;
}
$attrs = Array();
foreach($pairs AS $pair) {
$atr = array_map("trim_quotes", preg_split("/\s*=\s*/", $pair));
$attrs[$atr[0]] = $atr[1];
}
Результирующий массив в данном случае — $attrs.
Таким образом, можно получить доступ к атрибутам любого html-элемента. Можно написать библиотеку, которая позволит быстро получать доступ к ним для любого элемента html. А можно использовать модули для работы с XML :) В принципе, использовать модули — более правильное решение. Но не всегда применимое.
Если вас интересуют парсеры и граберы сайтов или других данных — вы можете заказать их у меня.