Используйте селекторы CSS для сбора элементов HTML из потокового синтаксического анализатора (например, потока SAX).

Как распарсить селектор CSS (CSS3) и использовать его (в стиле jQuery) для сбора элементов HTML не из DOM (из древовидной структуры), а из потока (например, SAX), т.е. используя последовательный доступ парсер на основе событий?

Кстати, существуют ли какие-либо селекторы CSS (или их комбинация), которым нужен доступ к DOM (страница Википедии SAX говорит что селекторы XPath «должны иметь доступ к любому узлу в любое время в проанализированном XML-дереве»)?

Меня больше всего интересует реализация комбинаторов селекторов, например. Селектор потомков 'A B'.

Я предпочитаю решения, описывающие алгоритм, или на Perl (для HTML::Zoom).


person Jakub Narębski    schedule 11.01.2011    source источник
comment
Это может быть сложно, поскольку SAX работает сверху вниз, а селекторы CSS оцениваются снизу вверх (справа налево). Но не невозможно.   -  person Gerben    schedule 14.01.2011
comment
На каком языке вы хотите это реализовать? Вы хотите реализовать ограничение обратного вызова селектора css? В прошлом я разрабатывал инкрементальный XML-парсер C++ (не SAX) для огромного xml-файла (~ 500 Мб), добавить такую ​​функцию поверх парсера SAX будет не так уж сложно (по крайней мере, в c или C++).   -  person VGE    schedule 18.01.2011
comment
@VGE: я бы предпочел Perl или описание алгоритма, но я достаточно хорошо читаю C++, чтобы добраться до основного алгоритма.   -  person Jakub Narębski    schedule 21.01.2011


Ответы (3)


Я бы сделал это с помощью регулярных выражений.

Во-первых, преобразуйте селектор в регулярное выражение, которое соответствует простому нисходящему списку открывающих тегов, представляющих данное состояние стека синтаксического анализатора. Чтобы объяснить, вот несколько простых селекторов и соответствующих им регулярных выражений:

  • A становится /<A[^>]*>$/
  • A#someid становится /<A[^>]*id="someid"[^>]*>$/
  • A.someclass становится /<A[^>]*class="[^"]*(?<= |")someclass(?= |")[^"]*"[^>]*>$/
  • A > B становится /<A[^>]*><B[^>]*>$/
  • A B становится /<A[^>]*>(?:<[^>]*>)*<B[^>]*>$/

И так далее. Обратите внимание, что все регулярные выражения заканчиваются на $, но не начинаются на ^; это соответствует тому, что селекторы CSS не должны совпадать с корнем документа. Также обратите внимание, что в коде сопоставления классов есть некоторые функции просмотра назад и просмотра вперед, которые необходимы, чтобы вы случайно не сопоставили «someclass-super-duper», когда вам нужен совершенно отличный класс «someclass».

Если вам нужно больше примеров, пожалуйста, дайте мне знать.

Создав регулярное выражение селектора, вы готовы начать синтаксический анализ. При анализе поддерживайте стек тегов, которые применяются в данный момент; обновляйте этот стек всякий раз, когда вы спускаетесь или поднимаетесь. Чтобы проверить соответствие селектора, преобразуйте этот стек в список тегов, которые могут соответствовать регулярному выражению. Например, рассмотрим этот документ:

<x><a>Stuff goes here</a><y id="boo"><z class="bar">Content here</z></y></x>

Ваша строка состояния стека будет проходить через следующие значения в порядке ввода каждого элемента:

  1. <x>
  2. <x><a>
  3. <x><y id="boo">
  4. <x><y id="boo"><z class="bar">

Процесс сопоставления прост: всякий раз, когда синтаксический анализатор переходит к новому элементу, обновляйте строку состояния и проверяйте, соответствует ли она регулярному выражению селектора. Если регулярное выражение совпадает, то селектор соответствует этому элементу!

Проблемы, на которые следует обратить внимание:

  • Двойные кавычки внутри атрибутов. Чтобы обойти это, примените кодировку объекта html к значениям атрибутов при создании регулярного выражения и к значениям атрибутов при создании строки состояния стека.

  • Порядок атрибутов. При построении как регулярного выражения, так и строки состояния используйте некоторый канонический порядок атрибутов (проще всего в алфавитном порядке). В противном случае вы можете обнаружить, что ваше регулярное выражение для селектора a#someid.someclass, которое ожидает <a id="someid" class="someclass">, к сожалению, терпит неудачу, когда ваш синтаксический анализатор переходит в <a class="someclass" id="someid">.

  • Чувствительность к регистру. Согласно HTML-спецификации, класс и Атрибуты id учитывают регистр (обратите внимание на маркер «CS» в соответствующих разделах). Таким образом, вы должны использовать сопоставление регулярных выражений с учетом регистра. Однако в HTML имена элементов не чувствительны к регистру, хотя они и представлены в XML. Если вы хотите сопоставление имен элементов, подобное HTML, без учета регистра, то канонизируйте имена элементов либо в верхнем, либо в нижнем регистре как в регулярном выражении селектора, так и в строке стека состояния.

  • Дополнительная магия необходима для работы с шаблонами селекторов, которые предполагают наличие или отсутствие родственных элементов, а именно A:first-child и A + B. Вы можете сделать это, добавив к тегу специальный атрибут, содержащий имя непосредственно предшествующего тега, или "", если этот тег является первым дочерним. Также есть общий селектор родственных элементов, A ~ B; Я не совсем уверен, как справиться с этим.

РЕДАКТИРОВАНИЕ. Если вам не нравится хакерство с использованием регулярных выражений, вы все равно можете использовать этот подход для решения проблемы, только используя свою собственную конечную машину вместо механизма регулярных выражений. В частности, селектор CSS может быть реализован как недетерминированный конечный автомат, что является пугающим -звучащий термин, но на практике означает следующее:

  1. Может быть более одного возможного перехода из любого заданного состояния
  2. Машина пробует один из них, и если это не срабатывает, то отступает и пробует другой.
  3. Самый простой способ реализовать это - сохранить стек для машины, в который вы вставляете каждый раз, когда следуете по пути, и извлекаете из него всякий раз, когда вам нужно вернуться. Это сводится к тому же, что и при поиске в глубину.

Секрет почти всей удивительности регулярных выражений заключается в использовании этого типа конечного автомата.

person DSimon    schedule 20.01.2011
comment
Оглядываясь назад, я замечаю ошибку, которую совершил; примеры регулярного выражения селектора, которые я написал, будут соответствовать тегам, которые просто начинаются с желаемого имени тега, поэтому, например, селектор для «A» будет неправильно соответствовать тегу с именем «Alakazam». Чтобы избежать этого, вам нужно настроить части [^›]* так, чтобы они были чем-то более похожим на (?:| [^›]*), чтобы он не начал игнорировать неважные атрибуты до тех пор, пока пробел не укажет имя тега. закончен. - person DSimon; 29.07.2011

Посетите nokogiri. С их страницы:

Nokogiri — это парсер HTML, XML, SAX и Reader. Среди множества функций Nokogiri — возможность поиска документов с помощью селекторов XPath или CSS3».

Это на Ruby, но вы сказали, что вам нужен алгоритм, а Ruby отлично подходит для чтения. Или просто вызовите его из того, над чем вы работаете.

person studgeek    schedule 13.06.2011

Что делает браузер для создания DOM из потока? Я предполагаю, что в этом и заключается ответ на ваш вопрос, потому что он должен хранить обнаруженные элементы в форме, которая облегчает запрос селектора CSS. Если вы можете позволить себе прочитать исходный код парсера браузера с открытым исходным кодом, я думаю, вы можете использовать его повторно.

Я бы не стал так делать, честно. Скорее я бы повторно использовал существующий парсер на основе SAX (возможно, вы перепишите другой с помощью perl), и это пройдет по всей строке. Когда обработчики срабатывают, используйте их для создания в памяти базы данных для элементов. Создайте виртуальную «таблицу» для каждого элемента с его #number [для ссылок] , tagName, родительским #number, next #number и его смещением символа открывающего тега в исходной материнской строке. Кроме того, создайте таблицу для каждого найденного атрибута и заполните ее записью для каждого тега со значением этого атрибута.

Теперь все о процессе создания базы данных, таблиц и индексов.

person doc_id    schedule 22.01.2011
comment
Я настроен скептически. Разве это не просто изобретение колеса DOM? - person DSimon; 22.01.2011
comment
Вот почему предлагается прочитать исходный код парсера браузера с открытым исходным кодом. - person doc_id; 22.01.2011
comment
Тем не менее, используя прежнюю реализацию, сделанную производителями браузеров в прошлом. - person doc_id; 22.01.2011
comment
Этот ответ не очень полезен для тех, кто действительно хотел бы реализовать такую ​​​​вещь. - person Jakub Narębski; 24.01.2011
comment
Мне нравится идея заново изобретать колесо - вы можете сделать его круглее, а если нет, то, по крайней мере, получите опыт, почему это так оно и есть... НО - это совершенно не так. границ! Не внедряйте анализатор DOM/HTML повторно, пока не будете хорошо знакомы со всеми фоновыми технологиями - person Kamil Tomšík; 24.01.2011
comment
Это не то же самое, что заново изобретать DOM, потому что объекты DOM не нужно создавать. Как правило, это преимущество подхода SAX, а недостатком является то, что вам нужно пить из пожарного шланга (поток синтаксического анализа), а не смотреть на красиво построенное дерево DOM. - person studgeek; 13.06.2011