使用CSS选择器从流式解析器收集HTML元素(例如SAX流)

Jak*_*ski 9 html css sax css-selectors

如何解析CSS(CSS3)选择器并使用它(以类似jQuery的方式)来收集不是来自DOM(来自树结构)的HTML元素,而是来自(例如SAX),即使用基于顺序访问事件的解析器?

顺便说一下,有没有需要访问DOM的CSS选择器(或它们的组合)(维基百科SAX页面说XPath选择器"需要能够在解析的XML树中随时访问任何节点")?

我最感兴趣的是实现选择器组合器,例如'AB'后代选择器.

我更喜欢描述算法的解决方案,或者更喜欢Perl(用于HTML :: Zoom).

DSi*_*mon 8

我会用正则表达式来做.

首先,将选择器转换为正则表达式,该表达式匹配表示给定解析器堆栈状态的开放标记的简单从上到下的列表.为了解释,这里有一些简单的选择器及其相应的regexen:

  • A/<A[^>]*>$/
  • A#someid/<A[^>]*id="someid"[^>]*>$/
  • A.someclass/<A[^>]*class="[^"]*(?<= |")someclass(?= |")[^"]*"[^>]*>$/
  • A > B/<A[^>]*><B[^>]*>$/
  • A B/<A[^>]*>(?:<[^>]*>)*<B[^>]*>$/

等等.请注意,正则表达式都以$结尾,但不以^开头; 这与CSS选择器不必从文档的根目录匹配的方式相对应.另请注意,类匹配代码中有一些lookbehind和lookahead东西,这是必要的,这样当你想要完全不同的类"someclass"时,你不会意外地匹配"someclass-super-duper".

如果您需要更多示例,请告诉我们.

一旦构造了选择器正则表达式,就可以开始解析了.在解析时,维护一堆当前适用的标签; 下降或上升时更新此堆栈.要检查选择器匹配,请将该堆栈转换为可与正则表达式匹配的标记列表.例如,请考虑以下文档:

<x><a>Stuff goes here</a><y id="boo"><z class="bar">Content here</z></y></x>
Run Code Online (Sandbox Code Playgroud)

在输入每个元素时,堆栈状态字符串将按顺序通过以下值:

  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规范,class和id属性区分大小写(注意相应部分的'CS'标记).因此,您必须使用区分大小写的正则表达式匹配.但是,在HTML中,元素名称区分大小写,尽管它们是XML格式.如果您想要类似HTML的不区分大小写的元素名称匹配,那么在选择器正则表达式和状态堆栈字符串中将元素名称规范化为大写或小写.

  • 需要额外的魔法来处理涉及元素兄弟的存在或不存在的选择器模式,即A:first-childA + B.您可以通过在包含紧接之前的标记名称的标记中添加特殊属性来实现这些,或者如果此标记是第一个子标记,则为"".还有一般的兄弟选择器A ~ B; 我不太清楚如何处理那一个.

编辑:如果您不喜欢正则表达式hackery,您仍然可以使用此方法来解决问题,只使用您自己的状态机而不是正则表达式引擎.具体来说,CSS选择器可以实现为非确定性有限状态机,这是一个令人生畏的声音术语,但实际上只是意味着以下内容:

  1. 任何给定的州都可能有多个可能的过渡
  2. 机器尝试其中一个,如果这不起作用,那么它会回溯并尝试另一个
  3. 实现这一目标的最简单方法是为机器保留一个堆栈,只要您按照路径进行操作,就可以在需要回溯时随时弹出.它归结为您用于深度优先搜索的同类事物.

几乎所有正则表达式都很棒的秘密在于它使用了这种状态机.