Як працюють синтаксичні аналізи HTML, якщо вони не використовують регулярний вираз?


96

Я щодня бачу запитання, як проаналізувати або витягнути щось із HTML-рядку, і перша відповідь / коментар - це завжди "Не використовуйте RegEx для розбору HTML, щоб не відчути гнів!" (остання частина іноді опущена).

Це досить заплутано для мене, я завжди думав, що загалом найкращий спосіб розбору будь-якого складного рядка - це використання регулярного виразу. То як працює HTML-аналізатор? Чи не використовує він регулярні вирази для синтаксичного аналізу.

Одним із конкретних аргументів використання регулярного виразу є те, що не завжди існує альтернатива синтаксичного аналізу (наприклад, JavaScript, де DOMDocument не є загальнодоступною опцією). Наприклад, jQuery, здається, чудово справляється, використовуючи регулярний вираз для перетворення рядка HTML у вузли DOM.

Не впевнений, чи слід це робити, чи не, це справжнє запитання, на яке я хочу відповісти, а насправді не має на меті бути дискусійною темою.


Позначаючи, щоб додати синтаксичний розбір та html-розбір - @Andy E, я сподіваюся, що з вами це нормально - я вважав, що це буде корисно.
JXG

@JXG: Зі мною це нормально, дякую :-)
Енді Е

Відповіді:


65

Зазвичай за допомогою маркера. Проект специфікації HTML5 має розгорнутий алгоритм обробки "реального HTML".


1
Хороша знахідка ... цитувати "Для обробки цих випадків синтаксичні аналізатори мають рівень вкладеності сценарію, який спочатку повинен бути встановлений на нуль, і прапор паузи синтаксичного аналізатора, який спочатку повинен бути встановлений на false." - Іншими словами, ви повинні самі це повторити і мати безліч власної логіки: P
Тимофій Хорі

1
Оновлення Краще наголосити на алгоритмічній складності замість якоїсь технології.
Арніс Лапса

1
Повторювати це самостійно з великою кількістю власної логіки - не така чудова ідея. Якщо можете, скористайтеся бібліотекою, яка підтримує стандартний алгоритм. напр. search.cpan.org/~tobyink/HTML-HTML5-Parser-0.03/lib/HTML/HTML5/… / code.google.com/p/html5lib
Квентін

8
Основна проблема синтаксичних аналізаторів HTML полягає в тому, що, зіткнувшись з помилкою, ви не можете виплюнути "Помилка синтаксичного аналізу" і залишити це на цьому. Ви переходите в режим дивацтв і намагаєтеся зрозуміти все, що можливо, з безладу, з яким ви зіткнулися, включаючи невідповідні теги, переплетення стилю [{]} та всілякі дивацтва, намагаючись зробити результат якнайкращим і неминучим невдача найменш болюча ... це не те, що ви можете зробити з регулярними виразами.
СФ.

7
@Timothy K: 'Примітка: Через те, як цей алгоритм змушує елементи змінювати батьків, його охрестили "алгоритмом агенції з усиновлення" (на відміну від інших можливих алгоритмів роботи з помилковим вмістом, який включав "алгоритм інцесту", "алгоритм таємної справи" та "алгоритм Гейзенберга"). '
JXG

133

То як працює HTML-аналізатор? Чи він не використовує регулярні вирази для розбору?

Ну, ні.

Якщо ви повертаєтеся у своєму мозку до теорії обчислювального курсу, якщо ви проходили його, або курсу компіляторів, або чогось подібного, ви можете згадати, що існують різні типи мов та обчислювальні моделі. Я не кваліфікований, щоб вносити всі деталі, але можу переглянути декілька основних моментів з вами.

Найпростіший тип мови та обчислень (для цих цілей) - звичайна мова. Вони можуть бути згенеровані за допомогою регулярних виразів та розпізнані за допомогою кінцевих автоматів. В основному це означає, що "розбір" рядків у цих мовах використовує стан, але не допоміжну пам'ять. HTML, звичайно, не є звичайною мовою. Якщо ви подумаєте над цим, список тегів можна вкладати довільно глибоко. Наприклад, таблиці можуть містити таблиці, і кожна таблиця може містити безліч вкладених тегів. За допомогою регулярних виразів ви зможете виділити пару тегів, але, звичайно, не будь-що довільно вкладене.

Класична проста мова, яка не є регулярною, - це правильно підібрані дужки. Спробуйте як можна, ви ніколи не зможете побудувати регулярний вираз (або кінцевий автомат), який завжди буде працювати. Вам потрібна пам’ять, щоб відстежувати глибину гніздування.

Наступна сила обчислювальної моделі - державна машина з стеком для пам'яті. Це називається автоматом, що висувається, і він розпізнає мови, створені безконтекстними граматиками. Тут ми можемо розпізнати правильно підібрані дужки - дійсно, стек є ідеальною моделлю пам'яті для цього.

Що ж, це досить добре для HTML? На жаль, ні. Можливо, для супер-пупер ретельно перевіреного XML, власне, в якому всі теги завжди вирівнюються ідеально. У реальному HTML-коді ви можете легко знайти такі фрагменти, як <b><i>wow!</b></i>. Це, очевидно, не гніздиться, тому для правильного синтаксичного аналізу стек просто недостатньо потужний.

Наступним рівнем обчислень є мови, сформовані загальними граматиками та визнані машинами Тьюрінга. Це загальновизнано ефективною найсильнішою обчислювальною моделлю, що існує - автомат стану з допоміжною пам’яттю, пам’ять якої можна змінювати де завгодно. Це те, що можуть робити мови програмування. Це рівень складності, в якому живе HTML.

Щоб узагальнити все тут одним реченням: щоб проаналізувати загальний HTML, вам потрібна справжня мова програмування, а не регулярний вираз.

Синтаксичний аналіз HTML здійснюється так само, як аналіз інших мов: лексирування та синтаксичний аналіз. Крок лексики розбиває потік окремих символів на значущі лексеми. Крок синтаксичного аналізу збирає маркери, використовуючи стани та пам’ять, у логічно цілісний документ, з яким можна діяти.


22

Регулярні вирази - це лише одна з форм синтаксичного аналізу. HTML-аналізатор добросовісності буде значно складнішим, ніж це можна виразити в регулярних виразах, використовуючи рекурсивне походження , прогнозування та кілька інших прийомів правильного тлумачення тексту. Якщо ви дійсно хочете зайнятися цим, ви можете перевірити lex & yacc та подібні інструменти.

Заборону використовувати регулярні вирази для синтаксичного аналізу HTML слід писати більш правильно, як: "Не використовуйте наївні регулярні вирази для синтаксичного аналізу HTML ..." (щоб ви не відчували гніву) "... і з обережністю ставитеся до результатів." Для певних конкретних цілей регулярний вираз цілком може бути цілком адекватним, але ви повинні бути дуже обережними, щоб бути в курсі обмежень вашого регулярного виразу та настільки обережними, наскільки це підходить до джерела тексту, який ви аналізуєте (наприклад, якщо це введення користувачем, будьте дуже обережні).


+1, хороша відповідь. Я повинен визнати, що я раніше використовував регулярні вирази, навіть коли я не контролював HTML, але не в жодній публічно відкритій програмі. Я теж "відчував гнів", бо це було наївно. Але це було дуже давно :-)
Енді Е

6

Синтаксичний розбір HTML - це перетворення лінійного тексту в деревоподібну структуру. Регулярні вирази загалом не можуть обробляти деревні структури. Регулярний вираз, який вам потрібен у кожній точці, щоб постійно змінювати наступний маркер. Ви можете використовувати регулярні вирази в синтаксичному аналізаторі, але вам знадобиться цілий масив регулярних виразів для кожного можливого стану розбору.


2

Якщо ви хочете мати 100% -ве рішення: Вам потрібно написати власний спеціальний код, який повторюється за допомогою символу HTML за символом, і вам потрібно мати величезну кількість логіки, щоб визначити, чи слід зупиняти поточний вузол і запускати наступний.

Причина в тому, що це дійсний HTML:

<ul>
<li>One
<li>Two
<li>Three
</ul>

Але так само і це:

<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>

Якщо у вас все добре з "90% рішенням": Тоді використання XML-аналізатора для завантаження документа - це нормально. Або за допомогою регулярного виразу (хоча xml простіше, якщо ви тоді володієте вмістом).


4
Синтаксичний аналізатор XML більше схожий на 1% розчин. Кількість добре сформованих XML-документів HTML незначна.
Квентін

4
Так, вони ... не сприймають «символ за символом» буквально, оскільки ви можете спробувати передавати речі. Але я хочу сказати, що вам потрібно написати власний парсер. Програмісти нового віку не звикли писати такий код ... ми звикли "HtmlDocumentUtility.Load" і подібні речі :)
Тімоті Хурі,

4
@Andy E: Regexes не є магією, вони також працюють за характером, як і будь-який інший вид розбору, або чорт, будь-який інший рядок функції.
Барт ван Хейкелом,

1
BTW: Ваш перший приклад - це не просто "напівдійсний HTML". Це фактично дійсний HTML 4.01 Strict. Ви можете використовувати, наприклад, валідатор W3C, щоб підтвердити це. Закриваючий тег офіційно не є обов'язковим для <li> (див. Специфікацію HTML 4).
sleske

2
@Bart: хороший момент, іноді мій мозок забуває всю логіку і вважає, що справи працюють за магією.
Енді Е
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.