Используйте селекторы CSS для сбора HTML-элементов из потокового парсера (например, SAX-поток) - PullRequest
9 голосов
/ 11 января 2011

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

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

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

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

Ответы [ 3 ]

8 голосов
/ 20 января 2011

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

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

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

И так далее. Обратите внимание, что все регулярные выражения заканчиваются на $, но не начинаются на ^; это соответствует тому, как селекторы CSS не должны совпадать с корнем документа. Также обратите внимание на то, что в коде сопоставления классов есть некоторые вещи типа lookbehind и lookahead, которые необходимы, чтобы вы случайно не сравнивали с «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 spec атрибуты class и id чувствительны к регистру (обратите внимание на маркер 'CS' в соответствующих разделах). Таким образом, вы должны использовать регистрозависимое сопоставление регулярных выражений. Однако в HTML имена элементов не чувствительны к регистру, хотя они представлены в XML. Если требуется сопоставление имен элементов без учета регистра в HTML-формате, канонизируйте имена элементов либо в верхнем, либо в нижнем регистре как в регулярном выражении селектора, так и в строке стека состояний.

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

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

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

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

0 голосов
/ 13 июня 2011

Выезд нокогири .С их страницы:

Nokogiri - это анализатор HTML, XML, SAX и Reader.Среди многих функций Nokogiri - возможность поиска документов с помощью селекторов XPath или CSS3. "во что бы вы ни работали.

0 голосов
/ 22 января 2011

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

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

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

...