正则表达式匹配开放标记(XHTML 独立标记除外)

RegEx match open tags except XHTML self-contained tags

提问人: 提问时间:11/14/2009 最后编辑:11 revs, 7 users 58%Jeff 更新时间:9/15/2023 访问量:3723641

问:

我需要匹配所有这些开始标签:

<p>
<a href="foo">

但不是这些:

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

我想出了这个,并想确保我做对了。我只是捕获了.a-z

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

我相信它说:

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

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

HTML 正则表达式 XHTML

评论


答:

141赞 2 revs, 2 users 89%Jherico #1

您希望第一个前面不要有 .请在此处查看有关如何执行此操作的详细信息。这被称为负面回溯。>/

但是,该实现的幼稚实现最终将在此示例文档中匹配<bar/></foo>

<foo><bar/></foo>

您能提供有关您要解决的问题的更多信息吗?您是否正在以编程方式遍历标签?

193赞 3 revs, 2 users 77%Kobi #2

尝试:

<([^\s]+)(\s[^>]*?)?(?<!/)>

它与你的类似,但最后一个不能在斜杠之后,并且也接受.>h1

评论

115赞 Gareth 11/14/2009
<a href=“foo” title=“5>3”>哎呀 </a>
68赞 bobince 11/14/2009
>在属性值中有效。事实上,在“规范 XML”序列化中,您不能使用 .(这并不完全相关,只是要强调在属性中值根本不是一件不寻常的事情。&gt;>
5赞 Marco Demaio 5/1/2011
@Kobi:在正则表达式中,感叹号(您放在末尾的那个)是什么意思?
6赞 Marco Demaio 5/1/2011
@bobince:你确定吗?我不明白了,这个有效的 HTML 也是如此:<div title="this tag is a <div></div>">hello</div>
3赞 Daniel Haley 10/13/2016
@MarcoDemaio - 不必在属性值中转义,但会转义。所以这将是有效的 HTML:><<div title="this tag is a &lt;div>&lt;/div>">hello</div>
4407赞 10 revs, 6 users 24%bobince #3
已锁定。目前,关于此答案的内容正在解决中存在争议。它目前不接受新的交互。

不能使用正则表达式解析 [X]HTML。因为 HTML 不能被正则表达式解析。正则表达式不是可用于正确解析 HTML 的工具。正如我之前在 HTML 和正则表达式问题中多次回答的那样,使用正则表达式将不允许你使用 HTML。正则表达式是一种不够复杂的工具,无法理解 HTML 使用的构造。HTML 不是常规语言,因此不能通过正则表达式解析。正则表达式查询无法将 HTML 分解为有意义的部分。这么多次了,但它没有找到我。即使是 Perl 使用的增强的不规则正则表达式也无法完成解析 HTML 的任务。你永远不会让我崩溃。HTML 是一种足够复杂的语言,它不能被正则表达式解析。即使是 Jon Skeet 也无法使用正则表达式解析 HTML。每次您尝试使用正则表达式解析 HTML 时,邪恶的孩子都会流着处女的鲜血,俄罗斯黑客会攻击您的 Web 应用程序。使用正则表达式解析 HTML 会召唤被污染的灵魂进入活人的领域。HTML 和正则表达式就像爱情、婚姻和仪式杀婴一样相辅相成。<中心>守不住为时已晚。正则表达式和 HTML 在同一个概念空间中的力量会像那么多水腻子一样摧毁你的思想。如果你用正则表达式解析 HTML,你就是在屈服于他们和他们的亵渎方式,这些方式注定了我们所有人为那个名字无法在基本多语言平面中表达的人进行不人道的辛劳,他来了。HTML-plus-regexp 会在你观察时液化有知觉者的神经,你的心灵在恐怖的冲击中枯萎。基于 Rege̿̔̉x 的 HTML 解析器是杀死 StackOverflow 的癌症,为时已晚,为时已晚,我们无法得救 chi͡ld 的违规行为确保正则表达式将消耗所有活组织(除了 HTML,它不能,正如之前预言的那样) 亲爱的主,帮助我们 任何人怎么能在这个祸害中幸存下来 使用正则表达式来解析 HTML 注定了人类将永远遭受可怕的折磨和安全漏洞 使用 regex 作为处理 HTML 的工具在这个世界和 c͒ͪo͛ͫrrupt 实体(如 SGML 实体,但更腐败)的可怕领域之间建立了一个 brea ch,仅仅瞥见了 HTML 的正则解析器世界,就会在不停地尖叫中传递一个 programmer 的意识,他来了, 瘟疫 slithy regex 感染将吞噬您的 HTML 解析器、应用程序和存在,就像 Visual Basic 一样,只会更糟的是,他来了,他来了,他来不适应he com̡e̶s, ̕h̵is un̨ho͞ly radiańcé destro҉ying all enli̍̈́̂̈́ghtenment, HTML tags lea͠ki̧n͘g fr̶ǫm ̡yo͟ur eye͢s̸ ̛l̕ik͏e liquid pain, re̸gular expression parsing 的歌曲将熄灭mor 的声音tal man from the sphere I can see it can you see ̲͚̖͔̙î̩́t̲͎̩̱͔́̋̀ it is beautifult he ffing ofthe lies s of Man ALL IS LOŚ͖̩͇̗̪̏̈́T ALL IS LOST the pon̷y he comes he c̶̮om eshe comes t he ich or permeates all MY FACE MY FACE oh god no no NOO̼OO NΘ stop thean*̶͑̾̾̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͉̗̩̳̟̍ͫͥͨe̠̅s not rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ to͇̹̺ͅƝ̴ȳ̳ TH̘ë͖́̉ ͠P̯͍ ̭O̚N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝ S̨̥̫͎̭ͯ̿̔̀ͅinal snuf ͎a̧͈͖r̽̾̈́͒͑e


您是否尝试过改用 XML 解析器?


主持人注

此帖子被锁定以防止对其内容进行不当编辑。该帖子看起来与它应该看起来完全一样 - 它的内容没有问题。请不要标记它以引起我们的注意。

评论

179赞 bobince 11/14/2009
Kobi:我认为是时候辞去 Assistant Don't Parse HTML With Regex Officer 的职位了。无论我们说多少次,他们每天都不会停止来......甚至每小时。这是一个失败的事业,其他人可以争取一点。因此,如果必须的话,请继续使用正则表达式解析 HTML。它只是破损的代码,而不是生与死。
2赞 Andrew Keeton 11/19/2009
如果你看不到这篇文章,这里有一个屏幕截图,它的所有荣耀: imgur.com/gOPS2.png
267赞 4 revs, 4 users 57%John Fiala #4

我建议使用 QueryPath 在 PHP 中解析 XML 和 HTML。它与jQuery的语法基本相同,只是它在服务器端。

评论

9赞 RobG 10/31/2013
@Kyle - jQuery 不解析 XML,它使用客户端的内置解析器(如果有的话)。因此,你不需要jQuery来做到这一点,只需要两行普通的旧JavaScript。如果没有内置的解析器,jQuery将无济于事。
2赞 Qix - MONICA WAS MISTREATED 9/22/2014
@RobG 实际上,jQuery使用的是DOM,而不是内置的解析器。
12赞 RobG 9/22/2014
@Qix,您最好告诉文档的作者:“jQuery.parseXML使用浏览器的本机解析函数......”。资料来源: jQuery.parseXML()
6赞 Jorn 4/2/2016
从模因问题(meta.stackexchange.com/questions/19478/the-many-memes-of-meta/...)来到这里,我喜欢其中一个答案是“使用 jQuery”
3562赞 10 revs, 10 users 36%Kaitlin Duck Sherwood #5

虽然只有正则表达式的任意 HTML 是不可能的,但有时使用它们来解析一组有限的已知 HTML 是合适的。

如果您有一小组 HTML 页面,您想从中抓取数据,然后将其塞入数据库,则正则表达式可能工作正常。例如,我最近想从议会的网站上获得澳大利亚联邦众议员的姓名、政党和选区。这是一项有限的一次性工作。

正则表达式对我来说效果很好,而且设置速度非常快。

评论

159赞 Michael Johnston 4/18/2012
此外,通过明智地使用扫描和正则表达式,从大型文档中抓取相当规则格式的数据将比任何通用解析器都快得多。如果您习惯于编写正则表达式,那么编码速度比编写 xpath 要快得多。而且几乎可以肯定,它对你所抓取的东西的变化不那么脆弱。所以咩咩。
314赞 Charles Duffy 7/12/2012
@MichaelJohnston“不那么脆弱”?几乎可以肯定不是。正则表达式关心文本格式的详细信息,而 XML 解析器可以静默忽略。在编码和部分之间切换?使用 HTML 压缩器删除文档中浏览器未呈现的所有空格?XML 解析器不会在乎,编写良好的 XPath 语句也不会在乎。另一方面,基于正则表达式的“解析器”......&foo;CDATA
46赞 quantum 7/12/2012
@CharlesDuffy对于一次性工作,这是可以的,对于空格,我们使用 \s+
80赞 Charles Duffy 7/13/2012
@xiaomao确实,如果必须知道所有的陷阱和变通办法才能获得 80% 的解决方案,而该解决方案在其余时间“适合您”,我无法阻止您。同时,我站在我这边,使用适用于100%语法有效XML的解析器。
438赞 Paul A Jungwirth 9/7/2012
我曾经不得不从 ~10k 页面中提取一些数据,所有数据都使用相同的 HTML 模板。它们充斥着导致解析器窒息的 HTML 错误,并且它们的所有样式都是内联的或带有等的:没有类或 ID 来帮助导航 DOM。在与“正确”方法斗争了一整天之后,我终于切换到正则表达式解决方案,并在一个小时内让它工作。<font>
1172赞 5 revs, 2 users 92%itsadok #6

免责声明:如果可以选择,请使用解析器。可是。。。

这是我使用 (!) 来匹配 HTML 标签的正则表达式:

<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>

它可能并不完美,但我通过很多 HTML 运行了这段代码。请注意,它甚至会捕捉到一些奇怪的东西,例如,这些东西出现在网络上。<a name="badgenerator"">

我想要使它与自包含的标签不匹配,您要么想使用 Kobi 的负面后视:

<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+(?<!/\s*)>

或者只是结合 if 和 if not。

致反对者:这是来自实际产品的工作代码。我怀疑任何阅读此页面的人都会觉得在 HTML 上使用正则表达式在社会上是可以接受的。

注意:我应该注意,在存在 CDATA 块、注释以及脚本和样式元素的情况下,此正则表达式仍然会崩溃。好消息是,您可以摆脱那些使用正则表达式的...

评论

123赞 prajeesh kumar 5/10/2012
我会选择一些对理智的事情有用的东西,而不是为不普遍完美而哭泣:-)
23赞 mishmashru 4/19/2013
因此,您实际上并不能仅使用正则表达式解决解析问题,但作为解析器的一部分,这可能会起作用。PS:工作产品并不意味着好的代码。没有冒犯,但这就是工业编程的运作方式和赚钱的方式
43赞 5/2/2014
您的正则表达式开始在尽可能短的有效 HTML 上失败:。简单返回,而应该。<!doctype html><title><</title>'<!doctype html><title><</title>'.match(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g)["<!doctype html>", "<title>", "<</title>"]["<title>", "</title>"]
3赞 imma 5/23/2014
如果我们只是想匹配和不匹配给出的例子,/<。([^r>][^>]*)?>/g works :-) // javascript: '<p> <a href=“foo”> <br /> <hr class=“foo” />'.match(/<.([^r>][^>]*)?>/g)
2赞 cweiske 10/14/2015
“有人在 HTML 中使用 CDATA 吗?”——是的,我有。如果在标记中显示 HTML 源代码,则占用的字节数更少。<pre>
161赞 2 revsmeder #7
<?php
$selfClosing = explode(',', 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed');

$html = '
<p><a href="#">foo</a></p>
<hr/>
<br/>
<div>name</div>';

$dom = new DOMDocument();
$dom->loadHTML($html);
$els = $dom->getElementsByTagName('*');
foreach ( $els as $el ) {
    $nodeName = strtolower($el->nodeName);
    if ( !in_array( $nodeName, $selfClosing ) ) {
        var_dump( $nodeName );
    }
}

输出:

string(4) "html"
string(4) "body"
string(1) "p"
string(1) "a"
string(3) "div"

基本上只需定义自闭合的元素节点名称,将整个 html 字符串加载到 DOM 库中,抓取所有元素,循环并过滤掉那些不自闭合的元素并对其进行操作。

我相信你现在已经知道你不应该为此目的使用正则表达式。

评论

1赞 meder omuraliev 11/15/2009
如果您正在处理真正的 XHTML,则在 getElementsByTagName 后面附加并指定命名空间。NS
53赞 manixrock #8

在我看来,您正在尝试匹配末尾没有“/”的标签。试试这个:

<([a-zA-Z][a-zA-Z0-9]*)[^>]*(?<!/)>

评论

10赞 ceving 5/5/2011
这是行不通的。对于输入 '<x a=“<b>”/><y>',匹配项为 x 和 y,尽管 x 已终止。
91赞 wen #9

我以前使用过一个名为 HTMLParser 的开源工具。它旨在以各种方式解析 HTML,并且可以很好地达到目的。它可以将 HTML 解析为不同的树节点,您可以轻松地使用其 API 从节点中获取属性。检查一下,看看这是否能帮到你。

110赞 4 revs, 2 users 98%SamGoody #10

如果你在PHP中需要这个:

除非 PHP DOM 函数格式正确,否则它将无法正常工作。无论它们的使用对其他人有多大好处。

simplehtmldom 很好,但我发现它有点错误,而且内存很重 [会在大页面上崩溃。

我从未使用过查询路径,所以无法评论它的用处。

另一个可以尝试的是我的 DOMParser,它对资源非常少,我已经愉快地使用了一段时间。简单易学且功能强大。

对于 Python 和 Java,也发布了类似的链接。

对于反对者 - 我只在XML解析器被证明无法承受实际使用时才编写我的类。宗教反对票只会阻止发布有用的答案 - 请保持问题的角度。

153赞 GONeale #11

我不知道你对此的确切需求,但如果你也在使用 .NET,你不能使用 Html Agility Pack 吗?

摘录:

它是一个 .NET 代码库,允许 你要解析“网络外”的HTML 文件。解析器非常宽容 与“现实世界”格式错误的 HTML。

评论

0赞 Peter Mortensen 8/14/2020
CodePlex 已关闭(但此存档在 CodePlex 存档中)。也许更新?
94赞 2 revs, 2 users 67%Amal Murali #12

每当我需要从 HTML 文档中快速提取内容时,我都会使用 Tidy 将其转换为 XML,然后使用 XPath 或 XSLT 来获取我需要的内容。 在你的情况下,是这样的:

//p/a[@href='foo']
2316赞 10 revs, 9 users 19%Vlad Gudim #13

我认为这里的缺陷是 HTML 是 Chomsky Type 2 语法(上下文无关语法),而正则表达式是 Chomsky Type 3 语法(常规语法)。由于 Type 2 语法从根本上比 Type 3 语法更复杂(参见 Chomsky 层次结构),因此您不可能做到这一点。

但许多人会尝试,有些人甚至会声称成功——但直到其他人发现错误并完全搞砸你。

评论

276赞 LarsH 3/2/2012
OP 要求解析一个非常有限的 XHTML 子集:开始标记。(X)HTML 之所以成为 CFG,是因为它有可能在其他元素的开始和结束标记之间包含元素(如在语法规则中)。(十)HTML 在开始标记中没有此属性:开始标记不能包含其他开始标记。OP 尝试解析的子集不是 CFG。A -> s A e
130赞 Adam Mihalcin 3/20/2012
在CS理论中,正则语言上下文无关语言的严格子集,但主流编程语言中的正则表达式实现更强大。正如 noulakaz.net/weblog/2007/03/18/ 所描述的,所谓的“正则表达式”可以检查一元中的素数,这当然是CS理论中的正则表达式无法完成的。
18赞 LarsH 5/22/2012
@eyelidlessness:同样的“仅当”适用于所有 CFG,不是吗?也就是说,如果 (X)HTML 输入的格式不正确,即使是成熟的 XML 解析器也无法可靠地工作。也许如果你举例说明你所指的“在现实世界的用户代理中实现的(X)HTML语法错误”,我会更好地理解你得到什么。
101赞 dubiousjim 5/31/2012
@AdamMihalcin是完全正确的。大多数现存的正则表达式引擎比 Chomsky Type 3 语法更强大(例如非贪婪匹配、backrefs)。一些正则表达式引擎(例如Perl的)是图灵完备的。诚然,即使是这些工具也是解析 HTML 的糟糕工具,但这个经常被引用的论点并不是原因。
7赞 AaronLS 11/25/2015
说语言 A 的语法决定了它根据语法解析另一种语言 B 的能力,这是无效的。例如,仅仅因为 HTML 是 Chomsky Type 2 语言,并不意味着您可以编写可以解析任何 Chomsky Type 3 语言的纯 HTML。HTML 本身不是一种具有任何功能的语言,使其能够解析其他语言。请不要说“Javascript”,因为 javascript 不是用 HTML 编写的东西解析的。
236赞 moritz #14

虽然你不能用正则表达式解析 HTML 的答案是正确的,但它们在这里并不适用。OP 只想解析一个带有正则表达式的 HTML 标签,这是可以使用正则表达式完成的。

但是,建议的正则表达式是错误的:

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

如果你在正则表达式中添加一些东西,通过回溯,它可能会被迫匹配愚蠢的东西,比如 ,太宽松了。另请注意,这是多余的,因为 can 也可以匹配空格。<a >>[^/]<space>*[^/]*[^/]*

我的建议是

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

哪里是(在Perl正则表达式中)否定的后视。它读作“一个<,然后是一个单词,然后是任何不是>的东西,最后一个可能不是 /,然后是 >”。(?<! ... )

请注意,这允许类似的东西(就像原始正则表达式一样),所以如果你想要一些更严格的内容,你需要构建一个正则表达式来匹配用空格分隔的属性对。<a/ >

评论

31赞 LarsH 9/8/2012
+1 表示问题不是关于解析完整的 (X)HTML,而是关于匹配 (X)HTML 开放标签。
11赞 Thayne 3/27/2015
大多数答案似乎忽略了另一件事,即 HTML 解析器可以在其部分 HTML 的实现中很好地使用正则表达式,如果大多数解析器不这样做,我会感到惊讶。
2赞 kasperd 11/22/2015
@Thayne 没错。在解析单个标签时,正则表达式是完成作业的正确工具。非常荒谬的是,人们必须向下滚动页面的一半才能找到合理的答案。接受的答案是不正确的,因为它混淆了词法和解析。
4赞 Martin L 4/21/2016
当属性值包含“>”或“/”字符时,此处给出的答案将失败。
1赞 JacquesB 7/30/2017
这在包含注释或 CData 部分的 HTML 上将无法正常工作。如果带引号的属性包含字符,它也将无法正常工作。我同意 OP 建议的正则表达式可以完成,但这里介绍的正则表达式太简单了。>
63赞 Corey Sanders #15

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

51赞 Emre Yazici #16

虽然为此目的使用正则表达式并不合适且有效,但有时正则表达式可以为简单的匹配问题提供快速解决方案,在我看来,将正则表达式用于琐碎的工作并不那么可怕。

Steven Levithan 撰写了一篇关于匹配最内层 HTML 元素的权威博客文章

318赞 10 revs, 7 users 43%kenorb #17

在 shell 中,您可以使用 sed 解析 HTML

  1. 图灵.sed
  2. 编写HTML解析器(作业)
  3. ???
  4. 利润!

相关(为什么不应该使用正则表达式匹配):

评论

4赞 Palec 10/13/2015
恐怕你没有听懂这个笑话,@kenorb。请再次阅读问题和接受的答案。这与一般的 HTML 解析工具无关,也与 HTML 解析 shell 工具无关,而是关于通过正则表达式解析 HTML。
3赞 Palec 3/24/2017
不,@Abdul。这是完全的,可证明的(在数学意义上)是不可能的。
6赞 Palec 3/24/2017
是的,这个答案很好地总结了它,@Abdul。然而,请注意,正则表达式实现并不是数学意义上的真正正则表达式——它们具有使它们更强大的结构,通常是图灵完备的(相当于 0 型语法)。这个论点打破了这一事实,但从某种意义上说,正则表达式从来都不是能够完成这样的工作,但仍然在某种程度上是有效的。
2赞 Palec 3/24/2017
顺便说一句,我提到的笑话是 kenorb 的(激进)编辑之前这个答案的内容,特别是修订版 4,@Abdul。
12赞 Paralife 3/29/2018
有趣的是,OP 从未要求使用正则表达式解析 html。他要求使用正则表达式匹配文本(恰好是 HTML)。这是完全合理的。
32赞 3 revs, 3 users 69%Paul #18

这可能做:

<.*?[^/]>

或者不带结束标签:

<[^/].*?[^/]>

HTML 解析器上的火焰战争是怎么回事?HTML 解析器必须先解析(并重新构建)整个文档,然后才能对搜索进行分类。在某些情况下,正则表达式可能更快/更优雅。我的 2 美分......

评论

5赞 Qwertie 7/18/2020
<a href="foo" title="5>3"> Oops </a>(引自@Gareth)
71赞 4 revs, 4 users 88%kenorb #19

这里有一些不错的正则表达式可以用 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;

评论

20赞 maletor 9/3/2015
别这样。请。
72赞 4 revs, 4 users 64%Emanuele Del Grande #20

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

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

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

不,天哪,没有找到匹配项。由于没有人提到构建解析器的主要机制,我很快意识到没有人明白这一点。

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

正则表达式的黑色艺术很难掌握,所以也许我们在尝试和测试我们的个人解决方案以一只手捕获整个网络时遗漏了更多的可能性......好吧,我确信它:)

这是神奇的模式:

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

试试吧。它被写成一个PHP字符串,所以“s”修饰符使类包含换行符。

这是我在一月份写的PHP手册的示例说明参考资料

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

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

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

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

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

评论

40赞 Gareth 7/6/2010
我会把它放在“不允许大于属性的正则表达式”箱中。根据 <input value=“is 5 > 3?” /> 检查它
74赞 aehiilrs 7/6/2010
如果你在生产代码中加入类似的东西,你很可能会被维护者枪毙。陪审团永远不会判他有罪。
32赞 Welbog 7/7/2010
正则表达式不能工作,因为根据定义,它们不是递归的。在正则表达式中添加递归运算符基本上使 CFG 的语法更差。为什么不首先使用设计为递归的东西,而不是猛烈地将递归插入已经充斥着无关功能的东西中呢?
20赞 Oorang 7/10/2010
我的反对意见不是功能问题,而是时间投入问题。正则表达式的问题在于,当你发布可爱的小家伙衬里时,你似乎做了一些更有效的事情(“看一行代码!当然,没有人提到他们花在备忘单上的半小时(或三个小时),并(希望)测试所有可能的输入排列。一旦你克服了所有这些问题,当维护者去弄清楚或验证代码时,他们就不能只是看它并看到它是正确的。必须剖析表达式并基本上重新测试它......
17赞 Oorang 7/10/2010
...要知道它是好的。即使那些擅长正则表达式的人也会发生这种情况。老实说,我怀疑绝大多数人不会很清楚。因此,你把最臭名昭著的维护噩梦之一与递归结合起来,这是另一个维护噩梦,我对自己说,在我的项目中,我真正需要的是一个不那么聪明的人。目标是编写糟糕的程序员可以在不破坏代码库的情况下维护的代码。我知道编码到最小公分母是很困难的。但是招聘优秀的人才是很困难的,而且你经常......
43赞 5 revs, 2 users 60%morja #21

如果您只想要标签名称,应该可以通过正则表达式来实现。

<([a-zA-Z]+)(?:[^>]*[^/] *)?>

应该做你需要的。但我认为“莫里茨”的解决方案已经很好了。我一开始没有看到它。

对于所有反对者:在某些情况下,使用正则表达式是有意义的,因为它可能是最简单、最快捷的解决方案。我同意,一般来说,您不应该使用正则表达式解析 HTML。

但是,当您有一个 HTML 子集时,正则表达式可能是一个非常强大的工具,您知道格式并且只想提取一些值。我这样做了数百次,几乎总是能实现我想要的。

39赞 2 revs, 2 users 62%Jonathan Wood #22

OP 似乎没有说他需要对标签做什么。例如,他是否需要提取内部文本,或者只是检查标签?

我坚定地认为正则表达式不是万能的、最终的文本解析器。我编写了大量的文本解析代码,包括用于解析 HTML 标记的代码

虽然我确实不太擅长正则表达式,但我认为正则表达式太僵化了,很难进行这种解析。

593赞 13 revs, 11 users 68%xanatos #23

有些人会告诉你地球是圆的(或者如果他们想用奇怪的词,地球可能是一个扁球体)。他们在撒谎。

有些人会告诉你,正则表达式不应该是递归的。他们在限制你。他们需要征服你,他们通过让你处于无知状态来做到这一点。

你可以活在他们的现实中,也可以服用红色药丸。

像元帅勋爵(他是元帅.NET类的亲戚吗?)一样,我看过基于Underverse Stack Regex-Verse,并带着你无法想象的力量知识回来了。是的,我认为有一两个老家伙保护他们,但他们在电视上看足球,所以这并不困难。

我认为XML的情况非常简单。正则表达式(在 .NET 语法中)经过缩减并以 base64 编码,以使您虚弱的头脑更容易理解,应该是这样的:

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
+r/vD34mUADO1P4/AQAA//8=

要设置的选项是 。您要查找的捕获组是 。如果捕获组不为空,则存在解析错误,正则表达式停止。RegexOptions.ExplicitCaptureELEMENTNAMEERROR

如果您在将其重新转换为人类可读的正则表达式时遇到问题,这应该会有所帮助:

static string FromBase64(string str)
{
    byte[] byteArray = Convert.FromBase64String(str);

    using (var msIn = new MemoryStream(byteArray))
    using (var msOut = new MemoryStream()) {
        using (var ds = new DeflateStream(msIn, CompressionMode.Decompress)) {
            ds.CopyTo(msOut);
        }

        return Encoding.UTF8.GetString(msOut.ToArray());
    }
}

如果你不确定,不,我不是在开玩笑(但也许我在撒谎)。它会起作用的。我已经构建了大量的单元测试来测试它,我甚至使用了(部分)一致性测试。它是一个分词器,而不是一个成熟的解析器,所以它只会将 XML 拆分为其组件标记。它不会解析/集成 DTD。

哦。。。如果你想要正则表达式的源代码,使用一些辅助方法:

正则表达式,用于标记 XML完整的纯正则表达式

评论

73赞 Brad Mace 3/8/2011
不确定是否认真.jpg - 希望这是精彩的讽刺
86赞 Justin Morgan 3/8/2011
天哪,它很大。我最大的问题是为什么?您知道所有现代语言都有 XML 解析器,对吧?您可以在 3 行中完成所有这些操作,并确保它会起作用。此外,你是否也意识到纯正则表达式可以证明不能做某些事情?除非您已经创建了一个混合正则表达式/命令式代码解析器,但它看起来不像您已经创建了。你也可以压缩随机数据吗?
155赞 xanatos 3/8/2011
@Justin我不需要理由。这是可以做到的(而且它不是非法/不道德的),所以我做到了。除了我们承认的那些(拿破仑·希尔)之外,思想没有任何限制......现代语言可以解析 XML 吗?真?我认为这是非法的!:-)
98赞 Justin Morgan 3/9/2011
先生,我深信不疑。我将把这段代码作为我的永动机内核的一部分——你能相信专利局的那些傻瓜一直拒绝我的申请吗?好吧,我会给他们看。我会向他们展示!
44赞 Scott Weaver 2/16/2012
不,没有什么是没有错误的:1)所有程序都包含至少一个错误。2)所有程序都包含至少一行不必要的源代码。3) 通过 #1 和 #2 并使用逻辑归纳法,证明任何程序都可以简化为带有错误的单行代码是一件简单的事情。(摘自 Learning Perl)
86赞 6 revs, 3 users 57%Sam Watkins #24

我喜欢用正则表达式解析 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 拆分为行,每行上有一个标签或文本块。然后可以使用其他文本工具和脚本(例如 grepsed、Perl 等)进一步处理这些行。我什至不是在开玩笑:)享受。

如果你想处理巨大的网页,将我的 slurp-everything-first Perl 脚本重新调整成一个不错的流媒体内容非常简单。但这并不是真的必要。

HTML 拆分


一些更好的正则表达式:

/(<.*?>|[^<]+)\s*/g    # Get tags and text
/(\w+)="(.*?)"/g       # Get attibutes

它们对 XML / XHTML 有好处。

通过微小的变化,它可以应对凌乱的 HTML......或先将 HTML -> XHTML 转换为 XHTML。


编写正则表达式的最佳方法是使用 Lex / Yacc 样式,而不是不透明的单行代码或注释的多行怪物。我还没有在这里这样做;这些人几乎不需要它。

评论

58赞 Kevin Panko 7/27/2011
“我不会试图解析故意破坏的白痴 HTML。”您的代码如何知道其中的区别?
5赞 David Andersson 9/11/2016
(获取属性错误 1)假定使用双引号。它将错过单引号中的值。在 html 版本 4 及更早版本中,如果它是一个简单的单词,则允许使用不带引号的值。/(\w+)="(.*?)"/
4赞 David Andersson 9/11/2016
(获取属性错误 2)可能会错误地匹配看起来像属性中的属性的文本,例如 .如果全局应用,它也会在普通文本或 html 注释中匹配这些内容。/(\w+)="(.*?)"/<img title="Nope down='up' for aussies" src="..." />
3赞 David Andersson 9/11/2016
(获取属性错误 3) 等号周围应允许可选的空格。/(\w+)="(.*?)"/
4赞 David Andersson 9/11/2016
(HTML 拆分错误 1) 由于数据中允许使用“>”,因此可能会拆分文本行并混淆后续处理。s/>/>\n/g
100赞 6 revs, 2 users 96%Emanuele Del Grande #25

解决方案如下:

<?php
// here's the pattern:
$pattern = '/<(\w+)(\s+(\w+)\s*\=\s*(\'|")(.*?)\\4\s*)*\s*(\/>|>)/';

// a string to parse:
$string = 'Hello, try clicking <a href="#paragraph">here</a>
    <br/>and check out.<hr />
    <h2>title</h2>
    <a name ="paragraph" rel= "I\'m an anchor"></a>
    Fine, <span title=\'highlight the "punch"\'>thanks<span>.
    <div class = "clear"></div>
    <br>';

// let's get the occurrences:
preg_match_all($pattern, $string, $matches, PREG_PATTERN_ORDER);

// print the result:
print_r($matches[0]);
?>

为了深入测试它,我在字符串中输入了自动关闭标签,例如:

  1. <小时/>
  2. <br/>
  3. <br>

我还输入了以下标签:

  1. 一个属性
  2. 多个属性
  3. 值绑定到单引号双引号中的属性
  4. 当分隔符为双引号时包含单引号的属性,反之亦然
  5. “unpretty”属性在“=”符号之前、之后以及之前和之后都有一个空格。

如果您发现上述概念验证中不起作用的内容,我可以分析代码以提高我的技能。

<编辑>我忘记了用户的问题是避免解析自闭合标签。 在这种情况下,模式更简单,变成这样:

$pattern = '/<(\w+)(\s+(\w+)\s*\=\s*(\'|")(.*?)\\4\s*)*\s*>/';

用户@ridgerunner注意到,该模式不允许使用不加引号的属性没有值的属性。在本例中,微调为我们带来了以下模式:

$pattern = '/<(\w+)(\s+(\w+)(\s*\=\s*(\'|"|)(.*?)\\5\s*)?)*\s*>/';

</编辑>

了解模式

如果有人有兴趣了解更多关于该模式的信息,我提供了一些行:

  1. 第一个子表达式 (\w+) 与标记名称匹配
  2. 第二个子表达式包含属性的模式。它由以下部分组成:
    1. 一个或多个空格 \s+
    2. 属性的名称 (\w+)
    3. 零个或多个空格 \s*(可能或不可能,在此处留空)
    4. “=”符号
    5. 同样,零个或多个空格
    6. 属性值的分隔符,单引号或双引号 ('|“)。在模式中,单引号被转义,因为它与 PHP 字符串分隔符重合。这个子表达式是用括号捕获的,因此可以再次引用它来解析属性的闭包,这就是为什么它非常重要。
    7. 属性的值,几乎与任何内容匹配:(.*?);在这种特定语法中,使用贪婪匹配(星号后面的问号),RegExp 引擎会启用类似“look-ahead”的运算符,该运算符匹配除此子表达式后面的内容之外的任何内容
    8. 有趣的是:\4 部分是一个反向引用运算符,它指的是之前在模式中定义的子表达式,在本例中,我指的是第四个子表达式,这是找到的第一个属性分隔符
    9. 零个或多个空格 \s*
    10. 属性子表达式到此结束,指定零个或多个可能的出现,由星号给出。
  3. 然后,由于标记可能以“>”符号前的空格结尾,因此零个或多个空格将与 \s* 子模式匹配。
  4. 要匹配的标记可以以简单的“>”符号结尾,也可以使用前面的斜杠的 XHTML 闭包:(/>|>)。当然,斜杠是转义的,因为它与正则表达式分隔符重合。

小提示:为了更好地分析此代码,有必要查看生成的源代码,因为我没有提供任何 HTML 特殊字符转义。

评论

12赞 ridgerunner 7/25/2011
与具有无值属性的有效标签(即 .也与具有未加引号的属性值(即 .<option selected><p id=10>
1赞 yodabar 7/26/2011
@ridgerunner:非常感谢您的评论。在这种情况下,模式必须改变一点:$pattern = '/<(\w+)(\s+(\w+)(\s*\=\s*(\'|“|)(.*?)\\5\s*)?)*\s*>/';我测试了它,并且在非引号属性或没有值的属性的情况下工作。
0赞 Floris 10/5/2013
在标签名称前加一个空格怎么样:我很确定它是合法的,但你不匹配它。< a href="http://wtf.org" >
7赞 yodabar 10/7/2013
不,对不起,标签名前的空格是非法的。除了“非常确定”之外,你为什么不提供一些证据来证明你的反对意见呢?这是我的,w3.org/TR/xml11/#sec-starttags 提到了 XML 1.1,你可以在 HTML 4、5 和 XHTML 中找到相同的内容,因为如果你进行测试,W3C 验证也会发出警告。和这里的许多其他废话诗人一样,我仍然没有得到任何聪明的论证,除了我的答案有几百个负数之外,以证明我的代码在问题中指定的契约规则中失败了。我只会欢迎他们。
0赞 Qwertie 7/18/2020
XML标签可以包含冒号,例如,在HTML中不是这样吗?<namespace:name>
294赞 Sam 9/27/2011 #26

我同意解析 XML 尤其是 HTML 的正确工具是解析器而不是正则表达式引擎。然而,正如其他人所指出的那样,有时使用正则表达式会更快、更容易,并且如果您知道数据格式,就可以完成工作。

Microsoft 实际上有一节是 .NET Framework 中正则表达式的最佳实践,专门讨论了考虑输入源

正则表达式确实有局限性,但您是否考虑过以下几点?

.NET Framework 在正则表达式方面是独一无二的,因为它支持平衡组定义

出于这个原因,我相信您可以使用正则表达式解析 XML。但请注意,它必须是有效的 XML(浏览器对 HTML 非常宽容,并且允许在 HTML 中使用错误的 XML 语法)。这是可能的,因为“平衡组定义”将允许正则表达式引擎充当 PDA。

引自上文引述的第1条:

.NET 正则表达式引擎

如上所述,适当平衡的结构不能用 正则表达式。但是,.NET 正则表达式引擎 提供了一些允许平衡构造的构造 认可。

  • (?<group>)- 将捕获的结果推送到捕获堆栈上 名称组。
  • (?<-group>)- 弹出最上面的捕获,名称组关闭 捕获堆栈。
  • (?(group)yes|no)- 如果存在组,则匹配 yes 部分 否则,名称组不匹配任何部分。

这些构造允许 .NET 正则表达式模拟 通过本质上允许堆栈的简单版本来限制 PDA 操作:推、爆和清空。简单的操作几乎 分别等同于递增、递减和比较为零。 这允许 .NET 正则表达式引擎识别 上下文无关语言的子集,特别是那些仅 需要一个简单的计数器。这反过来又允许非传统的 .NET 正则表达式,用于识别单个正确平衡的 构建。

请考虑以下正则表达式:

(?=<ul\s+id="matchMe"\s+type="square"\s*>)
(?>
   <!-- .*? -->                  |
   <[^>]*/>                      |
   (?<opentag><(?!/)[^>]*[^/]>)  |
   (?<-opentag></[^>]*[^/]>)     |
   [^<>]*
)*
(?(opentag)(?!))

使用以下标志:

  • 单线
  • IgnorePatternWhitespace(如果折叠正则表达式并删除所有空格,则不需要)
  • IgnoreCase(非必需)

正则表达式解释(内联)

(?=<ul\s+id="matchMe"\s+type="square"\s*>) # match start with <ul id="matchMe"...
(?>                                        # atomic group / don't backtrack (faster)
   <!-- .*? -->                 |          # match xml / html comment
   <[^>]*/>                     |          # self closing tag
   (?<opentag><(?!/)[^>]*[^/]>) |          # push opening xml tag
   (?<-opentag></[^>]*[^/]>)    |          # pop closing xml tag
   [^<>]*                                  # something between tags
)*                                         # match as many xml tags as possible
(?(opentag)(?!))                           # ensure no 'opentag' groups are on stack

可以在 A Better .NET Regular Expression Tester 中尝试此操作。

我使用了以下示例源:

<html>
<body>
<div>
   <br />
   <ul id="matchMe" type="square">
      <li>stuff...</li>
      <li>more stuff</li>
      <li>
          <div>
               <span>still more</span>
               <ul>
                    <li>Another &gt;ul&lt;, oh my!</li>
                    <li>...</li>
               </ul>
          </div>
      </li>
   </ul>
</div>
</body>
</html>

这找到了匹配项:

   <ul id="matchMe" type="square">
      <li>stuff...</li>
      <li>more stuff</li>
      <li>
          <div>
               <span>still more</span>
               <ul>
                    <li>Another &gt;ul&lt;, oh my!</li>
                    <li>...</li>
               </ul>
          </div>
      </li>
   </ul>

虽然它实际上是这样的:

<ul id="matchMe" type="square">           <li>stuff...</li>           <li>more stuff</li>           <li>               <div>                    <span>still more</span>                    <ul>                         <li>Another &gt;ul&lt;, oh my!</li>                         <li>...</li>                    </ul>               </div>           </li>        </ul>

最后,我真的很喜欢 Jeff Atwood 的文章:Parsing Html The Cthulhu Way。有趣的是,它引用了这个问题的答案,目前有超过 4k 的投票。

评论

19赞 John Saunders 2/3/2012
System.Text不是 C# 的一部分。它是 .NET 的一部分。
8赞 C0deH4cker 7/6/2012
在正则表达式 () 的第一行中,介于 <ul“ 和 ”id“ 之间应该是 ,而不是 ,除非您希望它匹配 <ulid=... ;)(?=<ul\s*id="matchMe"\s*type="square"\s*>) # match start with <ul id="matchMe"...\s+\s*
0赞 Sam 7/7/2012
@C0deH4cker 你是对的,表达式应该有而不是 .\s+\s*
4赞 Scheintod 9/28/2013
并不是说我真的理解它,但我认为你的正则表达式失败了<img src="images/pic.jpg" />
3赞 Sam 9/28/2013
@Scheintod 谢谢你的评论。我更新了代码。上一个表达式失败,因为自关闭标记内部的某个地方对您的 html 来说失败了。/<img src="images/pic.jpg" />
66赞 7 revs, 4 users 46%daghan #27
<\s*(\w+)[^/>]*>

零件解释:

<:起始字符

\s*:标签名称前可能有空格(丑陋,但可能)。

(\w+):标签可以包含字母和数字 (H1)。好吧,也匹配“_”,但我想这并没有什么坏处。如果好奇,请改用 ([a-zA-Z0-9]+)。\w

[^/>]*:除关闭外的任何内容>/>

>:关闭>

不相关的

对于那些低估正则表达式的研究员,他们说它们只和常规语言一样强大:

一个不规则甚至脱离上下文的n ban 可以匹配^(a+)b\1b\1$

反向引用 FTW

评论

0赞 alanaktion 2/2/2013
@GlitchMr,这就是他的观点。现代正则表达式在技术上不是规则的,也没有任何理由是规则的。
5赞 Kamila Borowska 2/15/2013
@alanaktion:“现代”正则表达式(阅读:带有Perl扩展)不能匹配(M是正则表达式长度,N是文本长度)。反向引用是造成这种情况的原因之一。awk 中的实现没有反向引用,并且在时间内匹配所有内容。O(MN)O(MN)
3赞 Qwertie 7/18/2020
<a href="foo" title="5>3"> Oops </a>(引用@Gareth - 奇怪的是,人们如何一遍又一遍地发布具有这种特定缺陷的答案。CDATA 有点容易被忽视,但这更基本)
1赞 Rohìt Jíndal 10/18/2022
如果 html 标签将包含介于两者之间,则此正则表达式将不起作用。例如:/<a href="example.com/test/example.html">
197赞 2 revs, 2 users 98%cytinus #28

孙子,中国古代战略家、将军和哲学家,说:

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

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

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

HTML has
complexity exceeding
regular language.

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

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

评论

0赞 LarsH 9/26/2023
有多少个音节??<([a-zA-Z]+)(?:[^>]*[^/]*)?>
20赞 5 revs, 3 users 65%Cylian #29

我认为这可能会奏效

<[a-z][^<>]*(?:(?:[^/]\s*)|(?:\s*[^/]))>

这可以在这里进行测试。


根据 W3Schools...

XML 命名规则

XML 元素必须遵循以下命名规则:

  • 名称可以包含字母、数字和其他字符
  • 名称不能以数字或标点符号开头
  • 名称不能以字母 xml(或 XMLXML 等)开头
  • 名称不能包含空格
  • 可以使用任何名称,并且不保留任何单词。

我使用的模式将遵守这些规则。

评论

80赞 Alan Moore 6/1/2012
警告:w3schools 不应被视为权威或可靠的参考 (ref)。无论如何,您列出的规则仅适用于元素和属性的名称;属性要灵活得多。你可能会侥幸逃脱禁止(这是合法的,但很少使用),但想象一下没有斜杠的 HREF 属性!;)>
7赞 JamieSee 8/25/2012
此表达式适用于许多元素名称,但是,XML 规范在 Unicode 意义上使用字母。有一些合法的元素名称与此不匹配。
0赞 Solomon Ucko 3/21/2018
@AlanMoore不带斜杠的 href 属性:href="some_other_page.html"
54赞 2 revsslevithan #30

诚然,在编程时,在处理 HTML 时,通常最好使用专用的解析器和 API,而不是正则表达式,尤其是在准确性至关重要的情况下(例如,如果您的处理可能具有安全隐患)。但是,我并不认为 XML 样式的标记永远不应该使用正则表达式进行处理。在某些情况下,正则表达式是完成这项工作的绝佳工具,例如在文本编辑器中进行一次性编辑、修复损坏的 XML 文件或处理看起来像但不完全是 XML 的文件格式时。有一些问题需要注意,但它们并非不可克服,甚至不一定相关。

在我刚才提到的情况下,一个简单的正则表达式通常就足够了。考虑到所有因素,这是一个幼稚的解决方案,但它确实正确地允许在属性值中使用未编码的符号。例如,如果您正在寻找标签,您可以将其改编为 .<([^>"']|"[^"]*"|'[^']*')*>>table</?table\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*)>

当然,这些没有考虑周围的上下文和一些边缘情况,但如果你真的愿意,即使是这样的事情也可以处理(例如,通过在另一个正则表达式的匹配项之间搜索)。

归根结底,使用最适合工作的工具,即使该工具恰好是正则表达式。

58赞 7 revs, 2 users 96%Lonnie Best #31

如果您只是想查找这些标签(没有解析的野心),请尝试以下正则表达式:

/<[^/]*?>/g

我在 30 秒内写完了它,并在这里进行了测试:https://regexr.com/

它与您提到的标签类型匹配,同时忽略您说要忽略的类型。

评论

2赞 Alan Moore 5/29/2016
仅供参考,您不需要转义尖括号。当然,无论如何逃避它们并没有什么坏处,但看看你本可以避免的混乱。;)
0赞 Lonnie Best 5/31/2016
当我不确定某件事是否具有特殊性时,我有时会不必要地逃避。我已经编辑了答案;它的工作原理相同,但更简洁。
7赞 2 revs, 2 users 88%b7kich #32

下面是 XML/XHTML 的 PCRE 正则表达式,它基于简化的 EBNF 语法定义构建:

/
(?(DEFINE)
(?<tag> (?&tagempty) | (?&tagopen) ((?&textnode) | (?&tag) | (?&comment))* (?&tagclose))
(?<tagunnested> (?&tagempty) | (?&tagopen) ((?&textnode) | (?&comment))* (?&tagclose))
(?<textnode> [^<>]+)
(?<comment> <!--([\s\S]*?)-->)
(?<tagopen> < (?&tagname) (?&attrlist)? (?&ws)* >)
(?<tagempty> < (?&tagname) (?&ws)* (?&attrlist)? (?&ws)* \/>)
(?<tagclose> <\/ (?&tagname) (?&ws)* >)
(?<attrlist> ((?&ws)+ (?&attr))+)
(?<attr> (?&attrunquoted) | (?&attrsinglequoted) | (?&attrdoublequoted) | (?&attrempty))
(?<attrempty> (?&attrname))
(?<attrunquoted> (?&attrname) (?&ws)* = (?&ws)* (?&attrunquotedvalue))
(?<attrsinglequoted> (?&attrname) (?&ws)* = (?&ws)* ' (?&attrsinglequotedvalue) ')
(?<attrdoublequoted> (?&attrname) (?&ws)* = (?&ws)* " (?&attrdoublequotedvalue) ")
(?<tagname> (?&alphabets) ((?&alphabets) | (?&digits))*)
(?<attrname>(?&alphabets)+((?&alphabets)|(?&digits)|[:-]) )
(?<attrunquotedvalue> [^\s"'=<>`]+)
(?<attrsinglequotedvalue> [^']+)
(?<attrdoublequotedvalue> [^"]+)
(?<alphabets> [a-zA-Z])
(?<digits> [0-9])
(?<ws> \s)
)
(?&tagopen)
/x

这说明了如何为上下文无关语法构建正则表达式。您可以通过将最后一行的匹配项从 更改为 来匹配定义的其他部分,例如(?&tagopen)(?&tagunnested)

真正的问题是:你应该这样做吗?

对于 XML/XHTML,共识是否的!

感谢 nikic 提供这个想法。

2赞 2 revsuser13843220 #33

正则表达式匹配开放标记,但 XHTML 自包含标记除外 所有其他标记
(和内容)都将被跳过。


这个正则表达式就是这样做的。如果您只需要匹配特定的 Open 标记,请交替列出一个列表
,并在下面的适当位置替换构造
(?:p|br|<whatever tags you want>)[\w:]+

<(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\s+(?>"[\S\s]*?"|'[\S\s]*?'|(?:(?!/>)[^>])?)+)?\s*>)[\S\s]*?</\1\s*(?=>)(*SKIP)(*FAIL))|(?:[\w:]+\b(?=((?:"[\S\s]*?"|'[\S\s]*?'|[^>]?)*)>)\2(?<!/))|(?:(?:/?[\w:]+\s*/?)|(?:[\w:]+\s+(?:"[\S\s]*?"|'[\S\s]*?'|[^>]?)+\s*/?)|\?[\S\s]*?\?|(?:!(?:(?:DOCTYPE[\S\s]*?)|(?:\[CDATA\[[\S\s]*?\]\])|(?:--[\S\s]*?--)|(?:ATTLIST[\S\s]*?)|(?:ENTITY[\S\s]*?)|(?:ELEMENT[\S\s]*?))))(*SKIP)(*FAIL))>

https://regex101.com/r/uMvJn0/1

 # Mix html/xml     
 # https://regex101.com/r/uMvJn0/1     
 
 <
 (?:
    
    # Invisible content gets failed
    
    (?:
       (?:
                               # Invisible content; end tag req'd
          (                    # (1 start)
             script
           | style
           | object
           | embed
           | applet
           | noframes
           | noscript
           | noembed 
          )                    # (1 end)
          (?:
             \s+ 
             (?>
                " [\S\s]*? "
              | ' [\S\s]*? '
              | (?:
                   (?! /> )
                   [^>] 
                )?
             )+
          )?
          \s* >
       )
       
       [\S\s]*? </ \1 \s* 
       (?= > )
       (*SKIP)(*FAIL)
    )
    
  | 
    
    # This is any open html tag we will match
    
    (?:
       [\w:]+ \b 
       (?=
          (                    # (2 start)
             (?:
                " [\S\s]*? " 
              | ' [\S\s]*? ' 
              | [^>]? 
             )*
          )                    # (2 end)
          >
       )
       \2 
       (?<! / )
    )
    
  | 
    # All other tags get failed
    
    (?:
       (?: /? [\w:]+ \s* /? )
     | (?:
          [\w:]+ 
          \s+ 
          (?:
             " [\S\s]*? " 
           | ' [\S\s]*? ' 
           | [^>]? 
          )+
          \s* /?
       )
     | \? [\S\s]*? \?
     | (?:
          !
          (?:
             (?: DOCTYPE [\S\s]*? )
           | (?: \[CDATA\[ [\S\s]*? \]\] )
           | (?: -- [\S\s]*? -- )
           | (?: ATTLIST [\S\s]*? )
           | (?: ENTITY [\S\s]*? )
           | (?: ELEMENT [\S\s]*? )
          )
       )
    )
    (*SKIP)(*FAIL)
 )
 >
6赞 JacquesB #34

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

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

<!-- <foo> -->

<![CDATA[ <foo> ]]>

特别是嵌入在脚本中的 html 字符串可能会触发误报,JavaScript 中经常使用 and 作为比较运算符也是如此。当然还有用 .<><!-- -->

因此,要仅匹配实际标签,您还需要能够跳过过去的注释和 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,则还有其他复杂性,例如 -tag。如果你还想处理无效的HTML,它会变得更加复杂。<script>

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

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

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

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

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

1赞 Ahmed Kolsi #35
<([a-z][^>\s]*)(?:\s+[^>]+)?>

此正则表达式将匹配由单个单词(例如 <p>、<a> 等)组成的开始标记,后跟零个或多个空格以及结束>字符之前的任意数量的字符(>或空格除外)。它还会将标签与属性进行匹配,并且不会将标签与名称中包含 a-z 以外的字符的标签进行匹配。但是,它仍然不匹配自闭合标记。

-2赞 Ehsan #36

若要匹配除 XHTML 自包含标记之外的打开标记(开始标记),可以使用以下正则表达式:

<[^/][^>]*>
  1. <:匹配左尖括号。
  2. [^/]:匹配除正斜杠以外的任何字符,确保标签不是结束标签。/
  3. [^>]*:匹配零个或多个字符,而不是右尖括号,允许存在任何属性。>
  4. >:匹配右尖括号,完成标记。

评论

0赞 Deven T. Corzine 11/18/2023
这个正则表达式是错误的。它确实排除了正常的结束 XML 标记,但也匹配了自关闭标记。例如,此正则表达式将成功匹配 ,因为匹配,然后匹配 ,然后匹配,最后匹配,因此对于自闭合标记,整体匹配成功。为了仅匹配开始标记并忽略结束和自关闭标记,下面是一个简单的正则表达式,它基本上可以正常工作: (但是,这不会将有效的开始 XML 标记与包含文本或字符的属性值匹配。<tag/><[^/]t[^>]*ag/><[^\/>]+>/>