我需要匹配所有这些开头标记:
<p>
<a href="foo">
但不是这些:
<br />
<hr class="foo" />
我想出了这个,想确保我做得对。我只是在捕捉a-z。
<([a-z]+) *[^/]*?>
我相信上面写着:
找到一个小于,然后查找(并捕获)a-z一次或多次,然后找到零个或多个空格,然后查找任何字符零次或多次,贪婪,除了/,然后查找大于
我有这个权利吗?更重要的是,你怎么看?
我需要匹配所有这些开头标记:
<p>
<a href="foo">
但不是这些:
<br />
<hr class="foo" />
我想出了这个,想确保我做得对。我只是在捕捉a-z。
<([a-z]+) *[^/]*?>
我相信上面写着:
找到一个小于,然后查找(并捕获)a-z一次或多次,然后找到零个或多个空格,然后查找任何字符零次或多次,贪婪,除了/,然后查找大于
我有这个权利吗?更重要的是,你怎么看?
当前回答
首先,回答一个直接的问题:正则表达式有一个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时,最好使用专用解析器和API,而不是正则表达式,尤其是在准确性至关重要的情况下(例如,如果您的处理可能涉及安全问题)。然而,我并不认为XML风格的标记永远不应该用正则表达式来处理。在某些情况下,正则表达式是一个很好的工具,例如在文本编辑器中进行一次性编辑,修复损坏的XML文件,或者处理看起来像但不太像XML的文件格式。有一些问题需要注意,但它们并非不可逾越,甚至不一定相关。
像<([^>“']|”[^“]*”|'[^']*')*>这样的简单正则表达式通常足够好,例如我刚才提到的那些情况。这是一个天真的解决方案,但它确实允许在属性值中使用未编码的>符号。如果您正在查找,例如,表标签,您可以将其调整为</?表\b([^>“']|”[^“]*”|'[^']*')*>。
为了了解更“高级”的HTML正则表达式会是什么样子,以下内容在模拟真实世界的浏览器行为和HTML5解析算法方面做了相当出色的工作:
</?([A-Za-z][^\s>/]*)(?:=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)|[^>])*(?:>|$)
以下内容与相当严格的XML标记定义相匹配(尽管它没有考虑XML名称中允许的全部Unicode字符集):
<(?:([_:A-Z][-.:\w]*)(?:\s+[_:A-Z][-.:\w]*\s*=\s*(?:"[^"]*"|'[^']*'))*\s*/?|/([_:A-Z][-.:\w]*)\s*)>
当然,这些不考虑周围的上下文和一些边缘情况,但如果您真的想处理这些问题(例如,通过在另一个正则表达式的匹配项之间进行搜索),也可以处理这些问题。
在一天结束时,使用最适合该作业的工具,即使该工具恰好是正则表达式。
您希望第一个>前面不带/。请查看此处了解如何执行此操作的详细信息。这被称为消极的后顾。
然而,在这个示例文档中,这一天真的实现最终将与<bar/></foo>匹配
<foo><bar/></foo>
你能提供更多关于你试图解决的问题的信息吗?您是否以编程方式遍历标签?
这里有一些很好的正则表达式,可以用BBCode替换HTML。对于所有反对者来说,请注意,他并不是试图完全解析HTML,只是为了净化它。他可能可以消除那些简单的“解析器”无法理解的标记。
例如:
$store =~ s/http:/http:\/\//gi;
$store =~ s/https:/https:\/\//gi;
$baseurl = $store;
if (!$query->param("ascii")) {
$html =~ s/\s\s+/\n/gi;
$html =~ s/<pre(.*?)>(.*?)<\/pre>/\[code]$2\[\/code]/sgmi;
}
$html =~ s/\n//gi;
$html =~ s/\r\r//gi;
$html =~ s/$baseurl//gi;
$html =~ s/<h[1-7](.*?)>(.*?)<\/h[1-7]>/\n\[b]$2\[\/b]\n/sgmi;
$html =~ s/<p>/\n\n/gi;
$html =~ s/<br(.*?)>/\n/gi;
$html =~ s/<textarea(.*?)>(.*?)<\/textarea>/\[code]$2\[\/code]/sgmi;
$html =~ s/<b>(.*?)<\/b>/\[b]$1\[\/b]/gi;
$html =~ s/<i>(.*?)<\/i>/\[i]$1\[\/i]/gi;
$html =~ s/<u>(.*?)<\/u>/\[u]$1\[\/u]/gi;
$html =~ s/<em>(.*?)<\/em>/\[i]$1\[\/i]/gi;
$html =~ s/<strong>(.*?)<\/strong>/\[b]$1\[\/b]/gi;
$html =~ s/<cite>(.*?)<\/cite>/\[i]$1\[\/i]/gi;
$html =~ s/<font color="(.*?)">(.*?)<\/font>/\[color=$1]$2\[\/color]/sgmi;
$html =~ s/<font color=(.*?)>(.*?)<\/font>/\[color=$1]$2\[\/color]/sgmi;
$html =~ s/<link(.*?)>//gi;
$html =~ s/<li(.*?)>(.*?)<\/li>/\[\*]$2/gi;
$html =~ s/<ul(.*?)>/\[list]/gi;
$html =~ s/<\/ul>/\[\/list]/gi;
$html =~ s/<div>/\n/gi;
$html =~ s/<\/div>/\n/gi;
$html =~ s/<td(.*?)>/ /gi;
$html =~ s/<tr(.*?)>/\n/gi;
$html =~ s/<img(.*?)src="(.*?)"(.*?)>/\[img]$baseurl\/$2\[\/img]/gi;
$html =~ s/<a(.*?)href="(.*?)"(.*?)>(.*?)<\/a>/\[url=$baseurl\/$2]$4\[\/url]/gi;
$html =~ s/\[url=$baseurl\/http:\/\/(.*?)](.*?)\[\/url]/\[url=http:\/\/$1]$2\[\/url]/gi;
$html =~ s/\[img]$baseurl\/http:\/\/(.*?)\[\/img]/\[img]http:\/\/$1\[\/img]/gi;
$html =~ s/<head>(.*?)<\/head>//sgmi;
$html =~ s/<object>(.*?)<\/object>//sgmi;
$html =~ s/<script(.*?)>(.*?)<\/script>//sgmi;
$html =~ s/<style(.*?)>(.*?)<\/style>//sgmi;
$html =~ s/<title>(.*?)<\/title>//sgmi;
$html =~ s/<!--(.*?)-->/\n/sgmi;
$html =~ s/\/\//\//gi;
$html =~ s/http:\//http:\/\//gi;
$html =~ s/https:\//https:\/\//gi;
$html =~ s/<(?:[^>'"]*|(['"]).*?\1)*>//gsi;
$html =~ s/\r\r//gi;
$html =~ s/\[img]\//\[img]/gi;
$html =~ s/\[url=\//\[url=/gi;
我喜欢用正则表达式解析HTML。我不会试图解析故意破坏的白痴HTML。这段代码是我的主解析器(Perl版):
$_ = join "",<STDIN>; tr/\n\r \t/ /s; s/</\n</g; s/>/>\n/g; s/\n ?\n/\n/g;
s/^ ?\n//s; s/ $//s; print
它被称为htmlsplit,将HTML拆分为多行,每行上有一个标记或文本块。然后可以使用其他文本工具和脚本(如grep、sed、Perl等)进一步处理这些行。
如果您希望处理巨大的网页,将我的slurp一切第一个Perl脚本重新设置为一个不错的流式处理就足够简单了。但这不是真的必要。
HTML拆分
一些更好的正则表达式:
/(<.*?>|[^<]+)\s*/g # Get tags and text
/(\w+)="(.*?)"/g # Get attibutes
它们适用于XML/XTML。
通过小的变化,它可以处理杂乱的HTML。。。或者先转换HTML->XHTML。
编写正则表达式的最佳方式是使用Lex/Yacc样式,而不是不透明的单行或注释的多行怪物。我还没有在这里这样做;这些人几乎不需要它。
我建议在PHP中使用QueryPath解析XML和HTML。它的语法与jQuery基本相同,只是在服务器端。