我需要匹配所有这些开头标记:

<p>
<a href="foo">

但不是这些:

<br />
<hr class="foo" />

我想出了这个,想确保我做得对。我只是在捕捉a-z。

<([a-z]+) *[^/]*?>

我相信上面写着:

找到一个小于,然后查找(并捕获)a-z一次或多次,然后找到零个或多个空格,然后查找任何字符零次或多次,贪婪,除了/,然后查找大于

我有这个权利吗?更重要的是,你怎么看?


当前回答

正如许多人已经指出的那样,HTML不是一种常规语言,这会使解析变得非常困难。我的解决方案是使用整洁的程序将其转换为常规语言,然后使用XML解析器来使用结果。有很多好的选择。我的程序使用Java和jtidy库编写,将HTML转换为XML,然后Jaxen将其展开为结果。

其他回答

关于解析(x)HTML的正则表达式方法的问题,所有提到一些限制的人的答案都是:你没有受过足够的训练来统治这一强大武器的力量,因为这里没有人谈到递归。

一位正则表达式不可知论的同事通知了我这次讨论,这肯定不是网络上第一次讨论这个古老而热门的话题。

在阅读了一些帖子后,我做的第一件事就是在这个线程中查找“?R”字符串。第二个是搜索“递归”。

不,天哪,找不到火柴。由于没有人提到解析器构建的主要机制,我很快就意识到没有人理解这一点。

如果(x)HTML解析器需要递归,那么仅使用没有递归的正则表达式解析器是不够的。这是一个简单的构造。

正则表达式的黑色艺术很难掌握,因此,在尝试和测试我们的个人解决方案以一手掌握整个网络时,我们可能还遗漏了其他可能性。。。嗯,我很确定:)

这是一个神奇的模式:

$pattern = "/<([\w]+)([^>]*?)(([\s]*\/>)|(>((([^<]*?|<\!\-\-.*?\-\->)|(?R))*)<\/\\1[\s]*>))/s";

试试看。它是以PHP字符串形式编写的,所以“s”修饰符使类包含换行符。

下面是我在一月份编写的PHP手册的示例注释:参考

(注意。在那个注释中,我错误地使用了“m”修饰符;它应该被删除,尽管它被正则表达式引擎丢弃,因为没有使用^或$锚定)。

现在,我们可以从一个更明智的角度来讨论这种方法的局限性:

根据正则表达式引擎的具体实现,递归在解析嵌套模式的数量上可能有限制,但这取决于所使用的语言尽管已损坏,(x)HTML不会导致严重错误。它没有经过消毒。

无论如何,它只是一个正则表达式模式,但它揭示了开发许多强大实现的可能性。

我编写这个模式是为了支持我在框架中构建的模板引擎的递归下降解析器,无论是在执行时间还是在内存使用方面,性能都非常出色(与使用相同语法的其他模板引擎无关)。

首先,回答一个直接的问题:正则表达式有一个bug,因为它会在任何地方排除带有斜线的标记,而不仅仅是在结尾。例如,它将排除这个有效的开头标记:<a href=“foo/bar.html”>,因为它在属性值中有一个斜杠。

我们可以解决这个问题,但更严重的是,这个正则表达式将导致误报,因为它还将匹配内部注释和cdata部分,其中相同的字符不表示有效的标记。例如:

<!-- <foo> -->

or

<![CDATA[ <foo> ]]>

尤其是嵌入脚本中的html字符串很可能会触发误报,JavaScript中经常使用<和>作为比较运算符也是如此。当然还有html的部分,这些部分用<!-->注释掉了。

因此,为了只匹配实际标记,您还需要能够跳过过去的注释和cdata部分。因此,您需要正则表达式来匹配注释和cdata,但只捕获开头标记。这仍然可以使用rexep,但它变得更加复杂,例如:

(  
    <!-- .*? --> # comment   
  | <!\[CDATA\[ .*? \]\]> # CData section
  | < \w+ ( "" [^""]* "" | ' [^']* ' | [^>/'""] )* /> # self-closing tag  
  | (?<tag> < \w+ ( "" [^""]* "" | ' [^']* ' | [^>/'""] )* > ) # opening tag - captured  
  | </ \w+ \s* > # end tag  
)

这仅适用于符合HTML兼容性准则的XHTML。如果您想处理任意XHTML,还应该处理处理指令和内部DTD,因为它们也可以嵌入误报。如果您还想处理HTML,还有其他复杂的问题,比如<script>-标记。如果您还想处理无效的HTML,则会变得更加复杂。

鉴于复杂性,我不建议走这条路。相反,寻找一个现成的(X)HTML解析库,它可以解决您的问题。

解析器通常在后台使用正则表达式(或类似的表达式)将文档拆分为“标记”(doctype、开始标记、结束标记、文本内容等)。但其他人会为您调试和测试这些正则表达式!根据解析器的类型,它可以通过匹配开始标记和结束标记来进一步构建元素的树结构。这几乎肯定会为您节省大量时间。

要使用的精确解析器库取决于您的语言和平台以及您正在解决的任务。如果您需要访问实际的标记子字符串(例如,如果您正在为HTML编写语法高亮),则需要使用SAX样式的解析器,该解析器直接公开语法标记。

如果您只执行标记匹配以手动构建元素的语法树,那么DOM解析器将为您完成这项工作。但是DOM解析器不公开底层的标记语法,因此无法解决您描述的确切问题。

您还应该考虑是否需要解析无效的HTML。这是一项复杂得多的任务,但在野生网络上,大多数HTML实际上是无效的。类似Pytons html5lib的东西可以解析无效的HTML。

中国古代军事家、将军和哲学家孙子说过:

据说,如果你了解你的敌人,了解你自己,你可以赢得百战百胜,而不会一败涂地。如果你只了解自己,而不了解对手,你可能会赢,也可能会输。如果你既不了解你自己也不了解你的敌人,你将永远危及自己。

在这种情况下,你的敌人是HTML,你要么是你自己,要么是正则表达式。您甚至可能是带有不规则正则表达式的Perl。了解HTML。了解自己。

我写了一首描述HTML本质的俳句。

HTML has
complexity exceeding
regular language.

我还写了一首俳句,描述Perl中正则表达式的性质。

The regex you seek
is defined within the phrase
<([a-zA-Z]+)(?:[^>]*[^/]*)?>

您希望第一个>前面不带/。请查看此处了解如何执行此操作的详细信息。这被称为消极的后顾。

然而,在这个示例文档中,这一天真的实现最终将与<bar/></foo>匹配

<foo><bar/></foo>

你能提供更多关于你试图解决的问题的信息吗?您是否以编程方式遍历标签?

我以前使用过一个名为HTMLParser的开源工具。它被设计为以各种方式解析HTML,并且很好地达到了目的。它可以将HTML解析为不同的树节点,您可以轻松地使用它的API从节点中获取属性。看看它,看看这对你有没有帮助。