除了XHTML自包含标记之外,RegEx匹配开放标记

时间:2009-11-13 22:38:26

标签: html regex xhtml

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

<p>
<a href="foo">

但不是这些:

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

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

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

我相信它说:

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

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

37 个答案:

答案 0 :(得分:4420)

您无法使用正则表达式解析[X] HTML。因为正则表达式无法解析HTML。正则表达式不是可用于正确解析HTML的工具。正如我之前在HTML-and-regex问题中回答过很多次,使用正则表达式不允许您使用HTML。正则表达式是一种不太复杂的工具,无法理解HTML所使用的构造。 HTML不是常规语言,因此无法通过正则表达式进行解析。正则表达式查询无法将HTML分解为有意义的部分。这么多次,但它没有找到我。即使是Perl使用的增强的不规则正则表达式也不能解析HTML。你永远不会让我破解。 HTML是一种足够复杂的语言,无法通过正则表达式进行解析。甚至Jon Skeet也无法使用正则表达式解析HTML。每当你试图用正则表达式解析HTML时,邪恶的孩子就会为处女的鲜血哭泣,而俄罗斯的黑客会把你的webapp用来。用正则表达式解析HTML会将被污染的灵魂召唤到生活的领域。 HTML和正则表达式就像爱情,婚姻和仪式杀婴一样。 &lt; center&gt;不能忍住为时已晚。正则表达式和HTML一起在同一个概念空间中的力量会像肮脏的腻子一样摧毁你的思想。如果你用正则表达式解析HTML,你就会屈服于他们以及他们亵渎神灵的方式,这些方式使我们所有人都为不能在基本多语言平面中表达的那个人付出了不人道的辛劳,他来了。当你观察时,HTML-plus-regexp将会使有感情的人流动,你的心灵在恐怖的冲击中萎缩。基于Rege̿̔̉x的HTML解析器是杀死StackOverflow的癌症现在为时已晚,我们无法保存chi͡ld的传统确保正则表达式将消耗所有活组织(除了HTML,它不能,正如先前所预言的那样)亲爱的领主帮助我们如何能够幸存这个祸害使用正则表达式来解析HTML已经注定了人类永远的恐惧折磨和安全漏洞使用rege x一个处理HTML的工具在这个世界和c͒ͪo͛ͫrrupt实体的恐惧领域(比如SGML实体,但更腐败)之间建立了一个简单的领域,仅仅是一个世界的瞥见世界。 reg ex解析器的HTML将会不断地传递给rogrammer的意识我不断尖叫,他来, pestilent sl ithy regex-infection w b 吞噬你的HT ML解析器,应用程序和存在一直都像Visual Basic一样只会更糟他来了他的com es 不要 ght h ecom̡e̶s,̕h̵isun̨ho͞lyradiańcédestro҉yingallenli̍̈̈ghtenment,HTML tags lea͠ki̧n͘gfr̶ǫm̡yo͟ureye͢s̸̸l̕ik͏eliq uid p ain ,re̸gularexp re ssion parsing 的歌曲将exti 从这里扼杀mor tal man的声音在这里我可以看到你能看到它吗? ͚̖͔̙î̩t͎̩͔̋很漂亮t 他是inal snuf fing o 这是男人的谎言 LL I SL OST th

他来了他<罢工>他> <罢工>我他 > ich 或渗透其他我的FAC E MY FACEᵒhgod n o NONOO̼ O ON Θ停止他是一个*̶̶͑̾̾͑̾̾g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨ e ͎a̧͈͖r̽̾̈́͒͑e n otrè̑ͧ̌aͨl̘̝̙ͤ̾̆ZA̡͊͠͝LGΌISͮ҉̯͈͕̹̘TO͇̹̺Ɲ̴ȳ̳TH̘Ë͖͖̉̉P̯͍̭O̚N̐Y̡ H̸̡̪̯ͨ͊̽̅̾Ȩ̸̡̬̩̪̯̾͛ͪ̈ͨ͊̽̅̾͘Ȩ̬̩̾͛ͪ̈͘ ̹̭̯C̷̙̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝š


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


  

主持人注意事项

     

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

答案 1 :(得分:3096)

虽然确实要求正则表达式解析任意 HTML就像要求初学者编写操作系统一样,但有时候解析一个有限的,已知的集合是合适的。 HTML。

如果你有一小组HTML页面要从中抓取数据然后填充到数据库中,那么正则表达式可能正常工作。例如,我最近想获得澳大利亚联邦代表的名称,政党和地区,我从议会的网站上获取了这些名称,政党和地区。这是一项有限的一次性工作。

Regexes对我来说效果很好,设置起来非常快。

答案 2 :(得分:1933)

我认为这里的缺陷是HTML是Chomsky Type 2 grammar (context free grammar)而RegEx是Chomsky Type 3 grammar (regular grammar)。由于Type 2语法基本上比Type 3语法更复杂(参见Chomsky hierarchy),因此在数学上不可能使用RegEx解析XML。

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

答案 3 :(得分:1252)

不要听这些家伙。如果你将任务分解成更小的部分,你完全可以用正则表达式解析无上下文的语法。您可以使用脚本按顺序生成正确的模式:

  1. 解决停机问题。
  2. 摆一圈。
  3. 计算O(log n)或更少的旅行商问题。如果它不止于此,你将耗尽RAM并且引擎将挂起。
  4. 模式会很大,所以请确保你有一个无损压缩随机数据的算法。
  5. 几乎就是 - 将整个事物除以零。易于peasy。
  6. 我自己还没有完成最后一部分,但我知道我已经接近了。由于某种原因它不断抛出CthulhuRlyehWgahnaglFhtagnException,因此我将把它移植到VB 6并使用On Error Resume Next。一旦我调查刚刚在墙上打开的这扇奇怪的门,我就会用代码更新。 HMM。

    P.S。 Pierre de Fermat也想出了如何做到这一点,但他写的边距对于代码而言还不够大。

答案 4 :(得分:1046)

免责声明:如果您有选项,请使用解析器。那说......

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

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

它可能不完美,但我通过HTML的 lot 运行此代码。请注意,它甚至可以捕获奇怪的内容,例如<a name="badgenerator"">,它会显示在网络上。

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

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

或者只是结合,如果没有。

对于downvoters:这是来自实际产品的工作代码。我怀疑读这个页面的人会得到这样的印象:在HTML上使用正则表达式是社会可接受的。

警告:我应该注意到,在存在CDATA块,注释以及脚本和样式元素的情况下,此正则表达式仍会出现故障。好消息是,你可以摆脱使用正则表达式的那些......

答案 5 :(得分:476)

有些人会告诉你地球是圆的(如果他们想要使用奇怪的词,或许地球是扁球体)。他们撒谎。

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

你可以生活在他们的现实中或者服用红色药丸。

就像元帅一样(他是Marshal .NET类的亲戚吗?),我看过 Underverse Stack Based Regex-Verse并带着 powers 知识返回无法想象。是的,我认为有一两个老人保护他们,但是他们在电视上看足球,所以这并不困难。

我认为XML案例非常简单。 RegEx(在.NET语法中),在base64中缩小和编码,以便让你更容易理解你的软弱思想,应该是这样的:

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

要设置的选项为RegexOptions.ExplicitCapture。您要查找的捕获组是ELEMENTNAME。如果捕获组ERROR不为空,则会出现解析错误并且正则表达式已停止。

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

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());
    }
}

如果你不确定,不,我不是在开玩笑(但也许我在说谎)。它会奏效。我已经构建了大量的单元测试来测试它,我甚至使用了conformance tests的(部分)。它是一个标记化器,而不是一个完整的解析器,因此它只会将XML拆分为其组件标记。它不会解析/集成DTD。

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

regex to tokenize an xmlthe full plain regex

答案 6 :(得分:289)

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

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

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

答案 7 :(得分:268)

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

Microsoft实际上有Best Practices for Regular Expressions in the .NET Framework部分,并专门讨论了Consider[ing] the Input Source

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

.NET框架在正则表达式方面是独一无二的,因为它支持Balancing Group Definitions

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

引用上文引用的第1条:

  

.NET正则表达式引擎

     

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

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

这些结构允许.NET正则表达式模拟a   通过基本上允许堆栈的简单版本来限制PDA   操作:推,弹和空。简单的操作非常多   相当于递增,递减和比较为零。   这允许.NET正则表达式引擎识别a   无上下文语言的子集,特别是那些只有的语言   需要一个简单的柜台。这反过来允许非传统的   .NET正则表达式识别个人正确平衡   构建体。

考虑以下正则表达式:

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

使用标志:

  • SINGLELINE
  • 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票的这个问题的答案。

答案 8 :(得分:255)

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

答案 9 :(得分:213)

虽然您无法使用正则表达式解析HTML的答案是正确的,但它们不适用于此处。 OP只想用正则表达式解析一个HTML标记,这可以用正则表达式完成。

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

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

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

我的建议是

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

其中(?<! ... )(在Perl正则表达式中)是负面的后卫。它写着“a&lt;,然后是一个单词,然后是任何不是&gt;的东西,最后一个可能不是/,后跟&gt;”。

请注意,这允许<a/ >之类的内容(就像原始正则表达式一样),因此如果您想要更具限制性的内容,则需要构建一个正则表达式来匹配由空格分隔的属性对。

答案 10 :(得分:177)

尝试:

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

它与您的相似,但最后一个>不能在斜杠之后,也接受h1

答案 11 :(得分:170)

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

  

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

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

我编写了一篇描述HTML本质的ha句。

HTML has
complexity exceeding
regular language.

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

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

答案 12 :(得分:153)

<?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库中,抓取所有元素,循环并过滤掉那些非自动关闭的元素并对其进行操作。

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

答案 13 :(得分:147)

我不知道您对此的确切需求,但如果您还使用.NET,则无法使用Html Agility Pack吗?

摘录:

  

它是一个允许的.NET代码库   你解析“out of the web”HTML   文件。解析器非常宽容   “真实世界”格式错误的HTML。

答案 14 :(得分:134)

您希望第一个>前面没有/。请查看here以获取有关如何执行此操作的详细信息。它被称为负面观察。

然而,一个简单的实现将最终匹配此示例文档中的<bar/></foo>

<foo><bar/></foo>

您能否提供一些有关您要解决的问题的更多信息?你是以编程方式迭代标记吗?

答案 15 :(得分:122)

W3C以伪正则表达式解释解析:
W3C Link

按照QNameSAttribute的var链接获得更清晰的图片。
基于此,您可以创建一个非常好的正则表达式来处理诸如剥离标记之类的事情。

答案 16 :(得分:104)

如果您需要PHP:

除非格式正确的XML,否则PHP DOM functions将无法正常运行。无论他们对人类其他人的使用有多好。

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

我从未使用querypath,因此无法对其有用性发表评论。

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

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

对于downvoters - 当XML解析器被证明无法承受实际使用时,我只编写了我的类。宗教贬低只会阻止发布有用的答案 - 请将问题视为问题的视角。

答案 17 :(得分:93)

以下是解决方案:

<?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. &lt; hr /&gt;
  2. &LT峰; br /&GT;
  3. &LT峰; br&GT;
  4. 我还输入了标签:

    1. 一个属性
    2. 多个属性
    3. 将哪些值绑定到单引号双引号
    4. 的属性
    5. 当分隔符是双引号时包含单引号的属性,反之亦然
    6. “不恰当”属性,带有“=”符号之前的空格,之后及之前和之后的空格。
    7. 如果您发现上述概念证明不起作用,我可以分析代码以提高我的技能。

      <强>&LT;编辑&gt; 我忘记了用户的问题是避免解析自闭标签。 在这种情况下,模式更简单,转变为:

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

      用户@ridgerunner注意到该模式不允许不带引号的属性没有值的属性。在这种情况下,微调会带来以下模式:

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

      <强>&LT; /编辑&gt;

      了解模式

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

      1. 第一个子表达式(\ w +)与标记名称
      2. 匹配
      3. 第二个子表达式包含属性的模式。它由以下组成:
        1. 一个或多个空格\ s +
        2. 属性的名称(\ w +)
        3. 零个或多个空格\ s *(可能与否,在此处留空)
        4. “=”符号
        5. 再次,零个或多个空格
        6. 属性值的分隔符,单引号或双引号('|“)。在模式中,单引号被转义,因为它与PHP字符串分隔符一致。这个子表达式用括号捕获,所以它可以再次引用来解析属性的闭包,这就是它非常重要的原因。
        7. 属性的值,由匹配任何东西:(。*?);在这个特定的语法中,使用贪婪匹配(星号后面的问号),RegExp引擎启用了一个“前瞻”类操作符,它匹配除此子表达式之后的任何内容
        8. 这里很有趣:\ 4部分是反向引用运算符,它指的是在模式之前定义的子表达式,在这种情况下,我指的是第四个子表达式,这是找到的第一个属性分隔符
        9. 零个或多个空格\ s *
        10. 属性子表达式在此结束,由星号给出零或更多可能出现的规范。
      4. 然后,由于标签可能以“&gt;”之前的空格结尾符号,零个或多个空格与\ s *子模式匹配。
      5. 要匹配的标记可能以简单的“&gt;”结尾符号或可能的XHTML闭包,它使用之前的斜杠:(/&gt; |&gt;)。当然,斜杠是逃逸的,因为它与正则表达式分隔符重合。
      6. 小提示:为了更好地分析此代码,有必要查看生成的源代码,因为我没有提供任何HTML特殊字符转义。

答案 18 :(得分:89)

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

答案 19 :(得分:89)

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

//p/a[@href='foo']

答案 20 :(得分:83)

我喜欢用正则表达式解析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 Split


根据我的期望,这得到了一些赞成,所以我会建议一些更好的正则表达式:

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

它们适用于XML / XHTML。

稍微变化,它可以应对凌乱的HTML ...或转换HTML - &gt;首先是XHTML。


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

答案 21 :(得分:73)

这是一个PHP based parser,它使用一些不正常的正则表达式来解析HTML。作为这个项目的作者,我可以告诉你可以用正则表达式解析HTML,但效率不高。如果您需要服务器端解决方案(就像我为wp-Typography WordPress plugin所做的那样),这可以。

答案 22 :(得分:69)

使用BBCode here替换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;

答案 23 :(得分:66)

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

与RegExp无关的同事通知了我这次讨论,这不是网上关于这个古老而热门话题的第一次讨论。

在阅读了一些帖子之后,我做的第一件事就是在这个帖子中寻找“?R”字符串。第二个是搜索“递归” 不,圣牛,没找到匹配。
由于没有人提到构建解析器的主要机制,我很快意识到没有人明白这一点。

如果(x)HTML解析器需要递归,则没有递归的RegExp解析器不足以达到目的。这是一个简单的结构。

RegExp的黑色艺术很难掌握,所以在尝试和测试我们的个人解决方案时,我们可能会遗漏更多的可能性,一方面捕获整个网络......好吧,我我很确定:)

这是神奇的模式:

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

试一试。
它被编写为PHP字符串,因此“s”修饰符使类包含换行符 这是我在1月份写的关于PHP手册的示例说明Reference

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

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

  1. 根据RegExp引擎的具体实现,递归可能在解析的嵌套模式数量上有限制,但这取决于使用的语言
  2. 虽然已损坏(x)HTML不会导致严重错误,但它不会清理
  3. 无论如何它只是一个RegExp模式,但它揭示了开发许多强大实现的可能性 我编写了这个模式来支持我在框架中构建的模板引擎的递归下降解析器,并且在执行时间或内存使用方面表现都非常好(与其他模板引擎无关)使用相同的语法)。

答案 24 :(得分:61)

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

答案 25 :(得分:59)

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

部分解释:

<:起始字符

\s*:它可能在标记名称之前有空格(丑陋但可能)。

(\w+):标签可以包含字母和数字(h1)。好吧,\w也匹配'_',但我认为它不会受到伤害。如果好奇使用([a-zA-Z0-9] +)而不是。

[^/>]*>/之外的任何内容,直至关闭>

>:关闭>

UNRELATED

那些低估正则表达的人会说他们只有普通语言一样强大:

n ba n ba n 这是非常规的,甚至没有上下文,可以与^(a+)b\1b\1$ <匹配/ p>

反向引用FTW

答案 26 :(得分:52)

如果你只是想找到那些标签(没有解析的野心),试试这个正则表达式:

/<[^/]*?>/g

我在30秒内写完了,并在此进行了测试: http://gskinner.com/RegExr/

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

答案 27 :(得分:52)

在我看来,你试图在最后没有“/”的情况下匹配标签。试试这个:

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

答案 28 :(得分:50)

在编程时,通常最好在处理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*)>

当然,这些并不考虑周围的情境和一些边缘情况,但即使这样的事情也可以处理,如果你真的想(例如,通过在另一个正则表达式的匹配之间进行搜索)。

在一天结束时,即使在该工具恰好是正则表达式的情况下,也可以使用最合适的工具。

答案 29 :(得分:48)

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

有一个definitive blog post关于匹配由Steven Levithan编写的最内层HTML元素。

答案 30 :(得分:41)

如果您只想要标记名称,则应该可以通过正则表达式执行此操作。

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

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

对于所有downvoters:在某些情况下,使用正则表达式是有意义的,因为它可以是最简单,最快捷的解决方案。我同意一般你不应该用正则表达式解析HTML。但是当你有一个HTML的子集,你知道格式并且你只想提取一些值时,正则表达式可以是一个非常强大的工具。我做了数百次,几乎总能达到我想要的效果。

答案 31 :(得分:39)

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

我坚定地认为RegEx不是最终的文本解析器。我写了大量的文本解析代码,包括this code to parse HTML tags

虽然这对RegEx来说并不是那么好,但我认为正则表达式过于僵化且难以维持这种解析。

答案 32 :(得分:34)

这可能会:

<.*?[^/]>

或没有结束标记:

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

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

答案 33 :(得分:20)

我认为这可能有用

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

可以测试here


根据w3schools ...

XML命名规则

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

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

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

答案 34 :(得分:5)

这是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

这说明了如何构建regular expressions for context-free grammars。您可以通过将最后一行的匹配从(?&tagopen)更改为例如来匹配定义的其他部分。 (?&tagunnested)

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

对于XML / XHTML,共识是

nikic提供信息。

答案 35 :(得分:1)

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

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

<!-- <foo> -->

<![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 解析库。

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

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

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

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

答案 36 :(得分:0)

RegEx匹配除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)
 )
 >