Я пытаюсь сопоставить блок div, у которого есть определенный идентификатор. Вот мой код регулярного выражения:
<div\s+[^>]*\s*id\s*=\s*["|']content["|']\s*>[^/div]+
Я хочу, чтобы регулярное выражение соответствовало всему блоку div. Поэтому я помещаю [^ / div] + в мое регулярное выражение, я предполагаю, что он будет соответствовать оставшимся символам, пока он не достигнет конца, но он не смог соответствовать до конца, потому что выражение [^] считало, что я не хочу для соответствия любому, что есть </ | d | i | v | >. Я хочу, чтобы все это рассматривалось как целое. Попытка [^ ()] не поможет.
Поэтому, пожалуйста, скажите мне, как я должен кодировать эту проблему?
<div id="content"> <noscript></noscript> <a href="blabla.com"> <h1> <a href="blablac.com">Blablabla</a> </h1> </div>
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Во-первых, я согласен с тем, что, в общем, регулярное выражение не лучший инструмент для анализа HTML. Тем не менее, в правильных руках (и с несколькими предостережениями) мощная (и, безусловно, нерегулируемая ) библиотека PCRE Филиппа Хазеля (используемая семейством функций PHP preg_*()
) позволяет разрешать нетривиальные скребли данных такие проблемы, как этот (с некоторыми ограничениями и оговорками – см. ниже). Задача, описанная выше, особенно сложна для решения только с использованием регулярного выражения, а решения регулярных выражений, такие как приведенные ниже, предназначены не для всех и никогда не должны предприниматься новичком регулярного выражения. Для правильного понимания нижеприведенного ответа требуется достаточно глубокое понимание нескольких расширенных конструкций и методов регулярных выражений.
Разве кто-нибудь не подумает о детях! Да, я прочитал легендарный ответ бобинца, и я знаю, что здесь есть острый вопрос (по меньшей мере). Но, пожалуйста, если у вас есть соблазн сразу же нажать стрелку вниз, потому что я '/(?:actual|brave|stupid)ly/'
используя слова: REGEX и: HTML на одном дыхании (и на не- – тривиальная проблема, не менее), я смиренно прошу вас воздерживаться достаточно долго, чтобы прочитать весь этот пост и фактически попробовать это решение для себя.
Имея это в виду, если вы хотите увидеть, как может быть создано расширенное регулярное выражение для решения этой проблемы (для всех, кроме нескольких (маловероятных) особых случаев – см. Ниже примеры), читайте далее …
РАСШИРЕННЫЙ РЕКУРСИВНЫЙ РЕЖИМ РЕШЕНИЯ: Как правильно указывает Уэс Хардакер , DIV
могут (и часто) вставляться. Однако он не на 100% прав, когда говорит: «Вы не можете построить тот, который будет соответствовать до правильного </ div>» . Правда, с PHP, вы можете! (с некоторыми ограничениями – см. ниже). Как и Perl и .NET, механизм регулярных выражений PCRE в PHP предоставляет рекурсивные выражения (например, (?R)
, (?1)
, (?2)
и т. Д.), Которые позволяют сопоставлять вложенные структуры с любой произвольной глубиной (ограничено только памятью). Например, вы можете легко сопоставить сбалансированные вложенные круглые скобки с этим выражением: '/\((?:[^()]++|(?R))*+\)/'
. Запустите этот простой тест, если у вас есть какие-либо сомнения:
$text = 'zero(one(two)one(two(three)two)one)zero'; if (preg_match('/\((?:[^()]++|(?R))*+\)/', $text, $matches)) { print_r($matches); }
Поэтому, если мы все можем согласиться с тем, что регулярное выражение PHP может действительно соответствовать вложенным структурам, давайте перейдем к проблеме. Эта конкретная проблема осложняется тем фактом, что внешний DIV
должен иметь атрибут id="content"
, но любые вложенные DIV
s могут или не могут. Таким образом, мы не можем использовать конструкцию (?R)
рекурсивно-матч-целое-выражение , потому что подвыражение, соответствующее внешнему DIV, не совпадает с тем, которое необходимо для соответствия внутренним DIV
. В этом случае нам нужна группа захвата (в данном случае группа 2), которая будет служить «рекурсивной подпрограммой» , которая соответствует внутренним, вложенным DIV
. Итак, вот проверенный фрагмент кода PHP, в котором есть продвинутый, но не полностью прокомментированный, так что вы могли бы на самом деле быть способным к созданию, (в большинстве случаев – см. ниже), DIV
с id="content"
, который сам может содержать вложенные DIV
s:
$re = '% # Match a DIV element having id="content". <div\b # Start of outer DIV start tag. [^>]*? # Lazily match up to id attrib. \bid\s*+=\s*+ # id attribute name and = ([\'"]?+) # $1: Optional quote delimiter. \bcontent\b # specific ID to be matched. (?(1)\1) # If open quote, match same closing quote [^>]*+> # remaining outer DIV start tag. ( # $2: DIV contents. (may be called recursively!) (?: # Non-capture group for DIV contents alternatives. # DIV contents option 1: All non-DIV, non-comment stuff... [^<]++ # One or more non-tag, non-comment characters. # DIV contents option 2: Start of a non-DIV tag... | < # Match a "<", but only if it (?! # is not the beginning of either /?div\b # a DIV start or end tag, | !-- # or an HTML comment. ) # Ok, that < was not a DIV or comment. # DIV contents Option 3: an HTML comment. | <!--.*?--> # A non-SGML compliant HTML comment. # DIV contents Option 4: a nested DIV element! | <div\b[^>]*+> # Inner DIV element start tag. (?2) # Recurse group 2 as a nested subroutine. </div\s*> # Inner DIV element end tag. )*+ # Zero or more of these contents alternatives. ) # End 2$: DIV contents. </div\s*> # Outer DIV end tag. %isx'; if (preg_match($re, $text, $matches)) { printf("Match found:\n%s\n", $matches[0]); }
Как я уже сказал, это регулярное выражение довольно сложно, но будьте уверены, оно работает! за исключением некоторых маловероятных случаев, отмеченных ниже – (и, возможно, еще нескольких, которые были бы очень благодарны, если бы вы могли найти). Попробуйте и убедитесь сами!
Должен ли я использовать это? Было бы целесообразным использовать это регулярное решение в производственной среде, где сотни или тысячи документов должны обрабатываться с 100% -ной надежностью и точностью? Конечно нет. Может ли это быть полезным для ограниченного однократного запуска некоторых HTML-файлов? (например, возможно, тот, кто задал этот вопрос?) Возможно. Это зависит от того, насколько комфортно с расширенными регулярными выражениями. Если регулярное выражение выглядит похоже на то, что оно написано на иностранном языке (оно есть) и / или пугает вас от дикков, ответ, вероятно, нет.
Оно работает? Да. Например, с учетом следующих тестовых данных, правильное выражение выше правильно выбирает DIV
с id="content"
(или id='content'
или id=content
на то пошло):
<!DOCTYPE HTML SYSTEM> <html> <head><title>Test Page</title></head> <body> <div id="non-content-div"> <h1>PCRE does recursion!</h1> <div id='content'> <h2>First level matched</h2> <!-- this comment </div> is tricky --> <div id="one-deep"> <h3>Second level matched</h3> <div id=two-deep> <h4>Third level matched</h4> <div id=three-deep> <h4>Fourth level matched</h4> </div> <p>stuff</p> </div> <!-- this comment <div> is tricky --> <p>stuff</p> </div> <p>stuff</p> </div> <p>stuff</p> </div> <p>stuff</p> </body></html>
CAVEATS: Итак, каковы некоторые сценарии, когда это решение не работает? Ну, стартовые теги DIV
могут НЕ иметь никаких угловых скобок в любом из их атрибутов (это можно устранить, но это добавляет немного больше кода). И следующие промежутки CDATA
, которые содержат конкретный начальный тег DIV
мы ищем (крайне маловероятный), вызовет ошибку regex:
<style type="text/css"> p:before { content: 'Unlikely CSS string with <div id=content> in it.'; } </style> <p title="Unlikely attribute with a <div id=content> in it">stuff</p> <script type="text/javascript"> alert("evil script with <div id=content> in it">"); </script> <!-- Comment with <div id="content"> in it --> <![CDATA[ a CDATA section with <div id="content"> in it ]]>
Я бы очень хотел узнать о других.
GO READ MRE3 Как я уже говорил, действительно понять, что здесь происходит, требует довольно глубокого понимания нескольких передовых методов. Эти методы не очевидны или интуитивно понятны. Есть только один способ, которым я знаю, чтобы получить эти навыки, а именно – сесть и изучить: Освоение регулярных выражений (3-е издание) Джеффри Фридла (MRE3). (Ты не пожалеешь об этом!)
Я могу честно сказать, что это самая полезная книга, которую я прочитал за всю свою жизнь!
Ура!
EDIT 2013-04-30 Исправлено регулярное выражение. Ранее он не разрешал тег без DIV
который сразу же начинался с начального тега DIV
.
[^ / Div] + остановится, когда достигнет любого из этих символов, чего вы не хотите. Поскольку это остановится, когда оно достигнет слишком из-за i .
К сожалению, вы не можете делать то, что хотите, не зная внутренней структуры HTML. Учти это:
<div id="content"> <div id="somethingelse"> </div> </div>
Даже если вы можете создать регулярное выражение, которое будет соответствовать до </div>
, вы не сможете создать тот, который будет соответствовать вплоть до правильного </div>
. Вам нужно сделать гораздо более интенсивный синтаксический анализ.
Используйте синтаксический анализатор, а не регулярное выражение.
Вот пример PHP: http://htmlparsing.com/php.html
Эта статья удивительна и является идеальным решением для моих нужд!
Он даже работает на html-коде, где simpleXML или DOMDocument терпят неудачу!
Иногда вам приходится разбирать html-код, созданный третьей стороной, на которой у вас нет контроля, и не уважает какой-либо dtd , поэтому сюда приходят рекурсивные регулярные выражения.
Я просто добавляю несколько модификаций к вашему коду и использую его с функцией PHP preg_match_all.
В следующем примере мы попытаемся правильно сопоставить содержимое div # :
$content = <<<HTML <div id="content"> <!-- tutu --> <div id="something"> <div id="somethingElse"> <ul> <li>lorem 1</li> <li class="dfg" toto="titi">lorem 2</li> <li class="dfg">lorem 3</li> <li class="dfg">lorem 4</li> <li class="dfg">lorem 5</li> <li class="dfg">lorem 6</li> </ul> <br /> <div id="emptyStuff"></div> </div> </div> <table> <tr> <td>cell 1</td> <td>cell 2</td> <td>cell 3</td> <td>cell 4</td> <td>cell 5</td> <td>cell 6</td> </tr> <tr> <td>cell 1</td> <td>cell 2</td> <td>cell 3</td> <td>cell 4</td> <td>cell 5</td> <td>cell 6</td> </tr> </table> </div> HTML; $pattern = '@# match nested tag (?(DEFINE) (?<comment> <!--.*?-->) (?<cdata> <![CDATA[.*?]]>) (?<empty> <\w+[^>]*?/>) (?<inline> <(script|style)[^>]+>.*?</\g{-1}>) (?<nested> <(\w+)[^>]*(?<!/)>(?&innerHTML)</\g{-1}>) (?<unclosed> <\w+[^>]*(?<!/)>) (?<text> [^<]+) ) (?<outerHTML><(?<tagName>div)\s?(?<attributes>[^>]*?id\h*=\h*(?<quote>"|\')[^(?"e)\v>]*\bcontent\b[^(?"e)\v>]*(?"e)[^>]*)> # opening tag (?<innerHTML> (?: (?&comment) | (?&cdata) | (?&empty) | (?&inline) | (?&nested) | (?&unclosed) | (?&text) )* ) </(?&tagName)>) # closing tag @six'; preg_match_all($pattern, $content, $matches); var_dump(array_intersect_key($matches, array( 'tagName' => 1, 'attributes' => 1, 'innerHTML' => 1, 'outerHTML' => 1 )));
Вот результат :
array(4) { ["outerHTML"]=> array(1) { [0]=> string(639) "<div id="content"> <!-- tutu --> <div id="something"> <div id="somethingElse"> <ul> <li>lorem 1</li> <li class="dfg" toto="titi">lorem 2</li> <li class="dfg">lorem 3</li> <li class="dfg">lorem 4</li> <li class="dfg">lorem 5</li> <li class="dfg">lorem 6</li> </ul> <br /> <div id="emptyStuff"></div> </div> </div> <table> <tr> <td>cell 1</td> <td>cell 2</td> <td>cell 3</td> <td>cell 4</td> <td>cell 5</td> <td>cell 6</td> </tr> <tr> <td>cell 1</td> <td>cell 2</td> <td>cell 3</td> <td>cell 4</td> <td>cell 5</td> <td>cell 6</td> </tr> </table> </div>" } ["tagName"]=> array(1) { [0]=> string(3) "div" } ["attributes"]=> array(1) { [0]=> string(12) "id="content"" } ["innerHTML"]=> array(1) { [0]=> string(615) " <!-- tutu --> <div id="something"> <div id="somethingElse"> <ul> <li>lorem 1</li> <li class="dfg" toto="titi">lorem 2</li> <li class="dfg">lorem 3</li> <li class="dfg">lorem 4</li> <li class="dfg">lorem 5</li> <li class="dfg">lorem 6</li> </ul> <br /> <div id="emptyStuff"></div> </div> </div> <table> <tr> <td>cell 1</td> <td>cell 2</td> <td>cell 3</td> <td>cell 4</td> <td>cell 5</td> <td>cell 6</td> </tr> <tr> <td>cell 1</td> <td>cell 2</td> <td>cell 3</td> <td>cell 4</td> <td>cell 5</td> <td>cell 6</td> </tr> </table> " } }
Надеюсь, это поможет !
<div id=content>.*?</div>
это то, что вам нужно – до тех пор, пока у вас нет вложенных div. Если у вас их есть, сдайте и используйте фактический синтаксический анализатор XML.
Включите опцию «dotall» (проверьте http://www.regular-expressions.info/dot.html и узнайте, как это сделать с вашим ароматом regex).
Незначительные детали до вас. 🙂