В SO нет дня, который не проходит без вопросов о синтаксическом анализе (X) HTML или XML с запросом регулярных выражений.
Хотя относительно легко придумать примеры, демонстрирующие нежизнеспособность регулярных выражений для этой задачи или с набором выражений для представления концепции, я все еще не мог найти в SO формальное объяснение того, почему это невозможно сделать в непрофессиональном условия.
Единственные формальные объяснения, которые я смог найти на этом сайте, вероятно, чрезвычайно точны, но также весьма загадочны для программиста-самоучки:
недостаток здесь в том, что HTML - это грамматика Хомского типа 2 (контекстно-свободная грамматика), а RegEx - это грамматика Хомского типа 3 (регулярное выражение).
или:
Регулярные выражения могут соответствовать только регулярным языкам, но HTML - это контекстно-свободный язык.
или:
Конечный автомат (который является структурой данных, лежащей в основе регулярного выражения) не имеет памяти, кроме состояния, в котором он находится, и если у вас произвольно глубокая вложенность, вам понадобится произвольно большой автомат, который противоречит понятию конечного автомата.
или:
Лемма о накачке для обычных языков - причина, по которой вы не можете этого сделать.
[Честно говоря: большинство приведенных выше объяснений ссылаются на страницы википедии, но их не намного легче понять, чем сами ответы].
Итак, мой вопрос: не мог бы кто-нибудь предоставить перевод формальных объяснений, приведенных выше, почему невозможно использовать регулярное выражение для синтаксического анализа (X) HTML / XML?
РЕДАКТИРОВАТЬ: После прочтения первого ответа я подумал, что должен уточнить: я ищу «перевод», который также кратко объясняет концепции, которые он пытается перевести: в конце ответа читатель должен иметь приблизительное представление - например - что означают "регулярный язык" и "контекстно-свободная грамматика" ...
источник
Ответы:
Сконцентрируйтесь на этом:
Определение регулярных выражений эквивалентно тому , что тест соответствует ли строка шаблона может быть выполнен с помощью конечного автомата (один другого автомата для каждого шаблона). У конечного автомата нет памяти - нет стека, нет кучи, нет бесконечной ленты для каракулей. Все, что у него есть, - это конечное количество внутренних состояний, каждое из которых может считывать единицу ввода из проверяемой строки и использовать это, чтобы решить, к какому состоянию перейти к следующему. В качестве особых случаев он имеет два состояния завершения: «да, совпало» и «нет, не совпало».
HTML, с другой стороны, имеет структуры, которые могут встраиваться сколь угодно глубоко. Чтобы определить, является ли файл допустимым HTML или нет, вам необходимо проверить, что все закрывающие теги соответствуют предыдущему открывающему тегу. Чтобы понять это, нужно знать, какой элемент закрывается. Без каких-либо средств «запомнить», какие открывающие теги вы видели, нет шансов.
Однако обратите внимание, что большинство библиотек «регулярных выражений» на самом деле позволяют не только строгое определение регулярных выражений. Если они могут сопоставить обратные ссылки, значит, они вышли за рамки обычного языка. Поэтому причина, по которой вам не следует использовать библиотеку регулярных выражений в HTML, немного сложнее, чем простой факт, что HTML не является регулярным.
источник
Тот факт, что HTML не представляет собой обычный язык, - отвлекающий маневр. Регулярные выражения и регулярные языки кажутся похожими , но это не так - они имеют одно и то же происхождение, но между академическими «регулярными языками» и нынешней мощностью согласования движков существует значительная разница. Фактически, почти все современные движки регулярных выражений поддерживают нерегулярные функции - простой пример
(.*)\1
. который использует обратную ссылку для сопоставления повторяющейся последовательности символов, например123123
, илиbonbon
. Сопоставление рекурсивных / сбалансированных структур делает их еще более увлекательными.Википедия прекрасно описывает это в цитате Ларри Уолла :
«Регулярное выражение может соответствовать только регулярным языкам», как вы можете видеть, - это не что иное, как распространенное заблуждение.
Так почему бы и нет?
Хорошая причина не сопоставлять HTML с регулярным выражением заключается в том, что «только потому, что вы можете, не значит, что вы должны». Хотя может быть возможно - просто есть лучшие инструменты для работы . Принимая во внимание:
Очень часто невозможно сопоставить часть данных, не проанализировав их целиком. Например, вы можете искать все заголовки и в конечном итоге найти соответствие в комментарии или строковом литерале.
<h1>.*?</h1>
может быть смелой попыткой найти основной заголовок, но он может найти:Или даже:
Последний пункт самый важный:
Хорошее краткое изложение предмета и важный комментарий о том, когда смешивание Regex и HTML может быть уместным, можно найти в блоге Джеффа Этвуда: Parsing Html The Cthulhu Way .
Когда лучше использовать регулярное выражение для синтаксического анализа HTML?
В большинстве случаев лучше использовать XPath в структуре DOM, которую может предоставить вам библиотека. Тем не менее, вопреки распространенному мнению, есть несколько случаев, когда я настоятельно рекомендую использовать регулярное выражение, а не библиотеку парсера:
Учитывая некоторые из этих условий:
источник
Потому что HTML может иметь неограниченное количество вложений,
<tags><inside><tags and="<things><that><look></like></tags>"></inside></each></other>
а регулярное выражение не может справиться с этим, потому что не может отслеживать историю того, во что он спустился и откуда вышел.Простая конструкция, иллюстрирующая сложность:
99,9% обобщенных подпрограмм извлечения на основе регулярных выражений не смогут правильно предоставить мне все, что находится внутри
div
с идентификаторомfoo
, потому что они не могут отличить закрывающий тег для этого div от закрывающего тега дляbar
div. Это потому, что у них нет возможности сказать: «Хорошо, я сейчас спустился во второй из двух div, поэтому следующий закрытый div, который я вижу, возвращает меня к одному, а следующий за ним - закрывающий тег для первого» , Программисты обычно отвечают, разрабатывая регулярные выражения для особых случаев для конкретной ситуации, которые затем ломаются, как только в них вводятся новые теги,foo
и их приходится распутывать, что требует огромных затрат времени и разочарований. Вот почему люди злятся на все это.источник
<(\w+)(?:\s+\w+="[^"]*")*>(?R)*</\1>|[\w\s!']+
соответствует вашему образцу кода.Регулярный язык - это язык, которому может соответствовать конечный автомат.
(Понимание конечных автоматов, машин выталкивания вниз и машин Тьюринга - это, по сути, учебная программа четвертого года обучения в колледже.)
Рассмотрим следующую машину, которая распознает строку «привет».
Это простая машина для распознавания обычного языка; Каждое выражение в скобках - это состояние, а каждая стрелка - переход. Построение такой машины позволит вам протестировать любую входную строку на соответствие регулярному языку, а значит, и регулярному выражению.
HTML требует, чтобы вы знали больше, чем просто, в каком состоянии вы находитесь - он требует истории того, что вы видели раньше, чтобы соответствовать вложенности тегов. Вы можете добиться этого, если добавите в машину стек, но тогда он больше не будет «обычным». Это называется выталкивающей машиной и распознает грамматику.
источник
Регулярное выражение - это машина с конечным (и обычно довольно небольшим) числом дискретных состояний.
Для синтаксического анализа XML, C или любого другого языка с произвольной вложенностью языковых элементов вам необходимо помнить, насколько вы глубоки. То есть вы должны уметь считать фигурные скобки / скобки / теги.
Вы не можете считать с ограниченной памятью. Уровней скобок может быть больше, чем у вас штатов! Вы могли бы проанализировать подмножество вашего языка, которое ограничивает количество уровней вложенности, но это будет очень утомительно.
источник
Грамматика - это формальное определение того, куда могут идти слова. Например, прилагательные предшествуют существительным
in English grammar
, но следуют за существительнымиen la gramática española
. Контекстно-свободный означает, что грамматика универсальна во всех контекстах. Контекстно-зависимый означает, что в определенных контекстах существуют дополнительные правила.В C #, например,
using
означает что-то другое вusing System;
верхней части файлов, чемusing (var sw = new StringWriter (...))
. Более подходящим примером является следующий код в коде:источник
Есть еще одна практическая причина не использовать регулярные выражения для синтаксического анализа XML и HTML, которая вообще не имеет ничего общего с теорией информатики: ваше регулярное выражение будет либо ужасно сложным, либо неправильным.
Например, очень хорошо написать регулярное выражение для соответствия
Но если ваш код верен, то:
Он должен разрешать пробелы после имени элемента как в начальном, так и в конечном тегах.
Если документ находится в пространстве имен, то он должен разрешать использование любого префикса пространства имен.
Вероятно, он должен разрешать и игнорировать любые неизвестные атрибуты, появляющиеся в начальном теге (в зависимости от семантики конкретного словаря)
Может потребоваться разрешить пробелы до и после десятичного значения (опять же, в зависимости от подробных правил конкретного словаря XML).
Он не должен совпадать с чем-то, что выглядит как элемент, но на самом деле находится в комментарии или разделе CDATA (это становится особенно важным, если есть вероятность, что вредоносные данные попытаются обмануть ваш синтаксический анализатор).
Возможно, потребуется предоставить диагностику, если ввод неверен.
Конечно, отчасти это зависит от применяемых вами стандартов качества. Мы видим множество проблем в StackOverflow, когда людям приходится генерировать XML определенным образом (например, без пробелов в тегах), потому что он читается приложением, которое требует, чтобы он был написан определенным образом. Если ваш код долговечен, важно, чтобы он мог обрабатывать входящий XML, написанный любым способом, который позволяет стандарт XML, а не только один образец входного документа, на котором вы тестируете свой код.
источник
В чисто теоретическом смысле регулярные выражения не могут анализировать XML. Они определены таким образом, что не позволяют им запоминать какое-либо предыдущее состояние, что препятствует правильному сопоставлению произвольного тега, и они не могут проникать на произвольную глубину вложенности, поскольку вложенность должна быть встроена в регулярное выражение.
Однако современные парсеры регулярных выражений созданы для их полезности для разработчика, а не для их соответствия точному определению. Таким образом, у нас есть такие вещи, как обратные ссылки и рекурсия, которые используют информацию о предыдущих состояниях. Используя их, очень просто создать регулярное выражение, которое может исследовать, проверять или анализировать XML.
Рассмотрим, например,
Он найдет следующий правильно сформированный тег XML или комментарий и найдет его, только если все его содержимое правильно сформировано. (Это выражение было протестировано с помощью Notepad ++, в котором используется библиотека регулярных выражений Boost C ++, которая очень близка к PCRE.)
Вот как это работает:
/>
, таким образом завершая тег, либо он будет заканчиваться на>
, и в этом случае он будет продолжен, исследуя содержимое тега.<
, после чего он вернется к началу выражения, позволяя ему работать либо с комментарием, либо с новым тегом.<
что он не может проанализировать. Несоответствие, конечно, приведет к тому, что процесс начнется заново. В противном случае,<
предположительно, это начало закрывающего тега для этой итерации. Используя обратную ссылку внутри закрывающего тега<\/\1>
, он будет соответствовать открывающему тегу для текущей итерации (глубины). Есть только одна группа захвата, так что это сопоставление несложно. Это делает его независимым от имен используемых тегов, хотя вы можете изменить группу захвата для захвата только определенных тегов, если вам нужно.В этом примере решаются проблемы, связанные с пробелами или идентификацией релевантного содержимого, с помощью групп символов, которые просто отменяют
<
или>
, или в случае комментариев, с помощью[\S\s]
, который будет соответствовать чему угодно, включая возврат каретки и новые строки, даже в однострочном. режим, продолжая, пока не достигнет-->
. Следовательно, он просто рассматривает все как действительное, пока не достигнет чего-то значимого.В большинстве случаев такое регулярное выражение не особенно полезно. Он подтвердит, что XML сформирован правильно, но это все, что он действительно делает, и он не учитывает свойства (хотя это было бы несложным добавлением). Это просто потому, что в нем не учитываются проблемы реального мира, подобные этой, а также определения имен тегов. Если приспособить его для реального использования, он будет больше похож на зверя. В общем, настоящий XML-анализатор будет намного лучше. Этот, вероятно, лучше всего подходит для обучения работе рекурсии.
Короче говоря: используйте синтаксический анализатор XML для реальной работы и используйте его, если хотите поиграть с регулярными выражениями.
источник
Не анализируйте XML / HTML с помощью регулярных выражений, используйте правильный синтаксический анализатор XML / HTML и мощный XPath запрос.
теория:
realLife © ® ™ повседневный инструмент в оболочка :
Вы можете использовать одно из следующих:
xmllint часто устанавливается по умолчанию с
libxml2
xpath1 (проверьте мою оболочку, чтобы вывод был разделен символами новой строкиxmlstarlet может редактировать, выбирать, преобразовывать ... По умолчанию не установлен, xpath1
xpath устанавливается через модуль Perl XML :: XPath, xpath1
xidel xpath3
saxon-lint мой собственный проект, оболочка над библиотекой Java Saxon-HE от @Michael Kay, xpath3
или вы можете использовать языки высокого уровня и правильные библиотеки, я думаю:
питон's
lxml
(from lxml import etree
)Perl«S
XML::LibXML
,XML::XPath
,XML::Twig::XPath
,HTML::TreeBuilder::XPath
Рубин nokogiri, проверьте этот пример
PHP
DOMXpath
, проверьте этот примерПроверка: использование регулярных выражений с тегами HTML
источник