Поиск по блогу

суббота, 26 июня 2010 г.

Очистка текста от лишних html-тегов

С задачей очистки html от лишних тегов сталкиваются абсолютно все.

Первое, что приходит на ум, это использовать php-функцию strip_tags():
string strip_tags (string str [, string allowable_tags])


Функция возвращает строку, очищенную от тегов. В качестве аргумента allowable_tags передаются теги, которые не надо удалять. Функция работает, но, мягко говоря, неидеально. По ходу, там нет проверки на валидность кода, что может повлечь за собой удаление текста, не входящего в тэги.
Инициативные разработчики сложа руки не сидели — в сети можно найти доработанные функции. Хорошим примером является strip_tags_smart.

Применять или не применять готовые решения — личный выбор программиста. Так сложилось, что мне чаще всего не требуется "универсального" обработчика и бывает удобнее почистить код регулярками.

От чего зависит выбор того или иного способа обработки?

1. От исходного материала и сложности его анализа.
Если вам нужно обрабатывать достаточно простые htmp-тексты, без какой-либо навороченной верстки, ясные, как день :), то можно использовать стандартные функции.
Если в текстах есть определенные особенности, которые надо учесть, то тут-то и пишутся специальные обработчики. В одних может использоваться просто str_replace. Например:

$s = array('’' => '’',         // Right-apostrophe (eg in I'm)
'“' => '“', // Opening speech mark
'–' => '—', // Long dash
'â€' => '”', // Closing speech mark
'é' => 'é', // e acute accent
chr(226) . chr(128) . chr(153) => '’', // Right-apostrophe again
chr(226) . chr(128) . chr(147) => '—', // Long dash again
chr(226) . chr(128) . chr(156) => '“', // Opening speech mark
chr(226) . chr(128) . chr(148) => '—', // M dash again
chr(226) . chr(128) => '”', // Right speech mark
chr(195) . chr(169) => 'é', // e acute again
);

foreach ($s as $needle => $replace)
{
$htmlText = str_replace($needle, $replace, $htmlText);
}


Другие могут быть основаны на регулярных выражениях. Как пример:

function getTextFromHTML($htmlText)
{
$search = array ("'<script[^>]*?>.*?</script>'si", // Remove javaScript
"'<style[^>]*?>.*?</style>'si", // Remove styles
"'<xml[^>]*?>.*?</xml>'si", // Remove xml tags
"'<[\/\!]*?[^<>]*?>'si", // Remove HTML-tags
"'([\r\n])[\s] '", // Remove spaces
"'&(quot|#34);'i", // Replace HTML special chars
"'&(amp|#38);'i",
"'&(lt|#60);'i",
"'&(gt|#62);'i",
"'&(nbsp|#160);'i",
"'&(iexcl|#161);'i",
"'&(cent|#162);'i",
"'&(pound|#163);'i",
"'&(copy|#169);'i",
"'&#(\d );'e"); // write as php

$replace = array ("",
"",
"",
"",
"\\1",
"\"",
"&",
"<",
">",
" ",
chr(161),
chr(162),
chr(163),
chr(169),
"chr(\\1)");

return preg_replace($search, $replace, $htmlText);
}

(В такие минуты как никогда радует возможность preg_replace работать с массивами в качестве параметров). Массив вы при необходимости дополняете своими регулярками. Помочь в их составлении вам может, например, этот конструктор регулярных выражений. Начинающим разработчикам может быть полезной статья "All about HTML tags. 9 Regular Expressions to strip HTML tags". Посмотрите там примеры, проанализируйте логику.

2. От объемов.
Объемы напрямую связаны со сложностью анализа (из предыдущего пункта). Большое количество текстов увеличивает вероятность, что, пытаясь предусмотреть и почистить все регулярками, вы можете что-нибудь да упустить. В этом случае подойдет метод "многоступенчатой" очистки. То есть очистить сначала, допустим, функцией strip_tags_smart (исходники на всякий случай не удаляем). Потом выборочно просматриваем некоторое количество текстов на выявление "аномалий". Ну и "зачищаем" аномалии регулярками.

3. От того, что надо получить в результате.
Алгоритм обработки может быть упрощен разными способами в зависимости от ситуации. Случай, описанный мной в одной из предыдущих статей, хорошо это демонстрирует. Напомню, текст там находился в div-е, в котором кроме него был еще div с "хлебными крошками", реклама адсенс, список похожих статей. При анализе выборки статей обнаружилось, что статьи не содержат рисунков и просто разбиты на абзацы с помощью <p></p>. Чтобы не чистить "главный" див от посторонних вещей, можно найти все абзацы (с Simple HTML DOM Parser это очень просто) и соединить их содержимое. Так что прежде чем составлять регулярки для чистки, посмотрите, нельзя ли обойтись малой кровью.

Вообще, между сторонниками парсинга html-кода, основанного чисто на регулярных выражениях, и парсинга, в основе которого лежит анализ DOM-структуры документа, в сети разгораются настоящие холивары. Вот, например, на оверфлоу. Невинный с первого взгляда вопрос вызвал очень бурное обсуждение (особенно обратите внимание на первый коммент, за который проголосовало уже более 3 тысяч человек, — чувак отжег :) ).
В общем, каждый выбирает то, что ему ближе и что лучше подходит для конкретной ситуации.

А какой способ предпочитаете вы?
___

Чтобы быть в курсе обновлений блога, можно подписаться на RSS.

Статьи схожей тематики:



6 комментариев:

  1. Если не ошибаюсь, выражение:
    echo preg_replace("'([\r\n])[\s] '", "\\1", $htmlText);
    как я понимаю, должно было убрать все пробелы за исключением переноса строки?

    ОтветитьУдалить
  2. @wmas, если быть точным - все пробелы в начале строк.

    ОтветитьУдалить
  3. Маша здравствуйте!
    1. Вы совсем забыли про то, что разные сайты выдают контент в разных кодировках, а их достаточно много. Поэтому в контенте могут встречаться интернациональные символы в той кодировке в которой выдает контент web-сервер. И что произойдет, если этот контент интерпритировать в родной для Windows 1251? А что, если это Windows 1252? Или совсем другая?

    2. Список замен HTML Entities у вас не совсем полный. Точнее он полон всего процентов на 5 - 10% от полного набора по стандарту. К тому же, HTML Entities есть отображение в конкретный символ таблицы UNICODE.

    ОтветитьУдалить
  4. Здравствуйте.
    1. Почему забыла? Я не забыла, в статье приводится пример решения, а не универсальное решение.
    2. Да, знаю, см. пункт 1. :)

    ОтветитьУдалить
  5. Маша, здравствуйте!
    Я немного в той тональности видимо написал комментарий, извините.

    Просто вы спросили как парсите вы, а я указал на несовершенство примера решения, что с моей стороны ну совсем не корректно.

    ОтветитьУдалить

Комментарии модерируются, вопросы не по теме удаляются, троллинг тоже.

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

Поделиться