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

<p>
<a href="foo">

但不是这些:

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

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

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

我相信上面写着:

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

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


当前回答

无法使用正则表达式解析[X]HTML。因为正则表达式无法解析HTML。Regex不是一个可以用来正确解析HTML的工具。正如我之前多次在这里回答的HTML和正则表达式问题一样,正则表达式的使用不允许您使用HTML。正则表达式是一种不够复杂的工具,无法理解HTML所使用的构造。HTML不是一种正则语言,因此不能由正则表达式解析。Regex查询无法将HTML分解为有意义的部分。很多次,但我都不明白。即使是Perl使用的增强型不规则正则表达式,也无法完成解析HTML的任务。你永远不会让我崩溃。HTML是一种非常复杂的语言,它不能被正则表达式解析。甚至Jon Skeet也无法使用正则表达式解析HTML。每次你试图用正则表达式解析HTML时,这个邪恶的孩子都会痛哭流涕,而俄罗斯黑客则会在你的网络应用程序上进行攻击。用正则表达式解析HTML会将受污染的灵魂召唤到活人的领域。HTML和正则表达式就像爱情、婚姻和仪式性的杀婴。<center>无法保持它太晚了。正则表达式和HTML在同一概念空间中的合力会像水一样摧毁你的思想。如果你用正则表达式解析HTML,你就屈服于他们和他们的亵渎方式,这让我们所有人都要为一个名字无法在基本多语言平面中表达的人付出不人道的努力,他来了。HTML加正则表达式将使n​当你观察时,你的心灵在恐惧的冲击中枯萎。基于x的HTML解析器是杀死StackOverflow的癌症,为时已晚,为时不晚,我们无法得救,因为一个chi͡ld的犯罪确保了regex将吞噬所有的活组织(除了它不能消耗的HTML,如前所预言的那样)亲爱的主,请帮助我们,任何人如何能在这场灾难中幸存下来,使用regex来解析HTML已经注定了人类将遭受永恒的可怕折磨使用正则表达式作为处理HTML的工具的安全漏洞在这个世界和c͒ͪo͛ͫ腐败实体(如SGML实体,但更腐败)的可怕领域之间建立了一个漏洞,这仅仅是对reg世界的一瞥​用于HTML的ex解析器将​他突然把一个程序员的意识带入了一个不断尖叫的世界,瘟疫般的slithy regex感染会​我吞噬你的HT​ML解析器、应用程序和存在一直像Visual Basic一样,但更糟糕的是他来了,他来了就不来了​对他来说​s un̨hoğly radiańcé; destro҉ying all enli̍̈́س\836收紧,HTML标签lea͠ki̧n͘g fr ǫm̡yo​͟我们的眼睛͢s̸̛l̕ik͏e liq​uid pain,re̸gular exp之歌​压缩解析将退出​用英语表达摩尔的声音​来自sp的塔尔曼​在这里我可以看到它,你可以看到它吗​他终于揭穿了谎言​人的全部都是LOS͖̩͇̗̪̏̈́T ALL I​当他来的时候,他就失去了机会​或渗透到我的脸上ᵒ天啊不不不​O NΘ停止​*̶͑̾̾​ͫ͏̙̤g͛838̾ͫ;͇̫̑͆l​ot rȇͧ̌aͨl̘̝̙ͤ͂̾̆ZA̡͊͠LGΌISͮ;҉̛̯͈͕̹ͼ̱TO̶͇̺ͅƝȳ̳TH̘; Ë͖́̉\864 P͍̭O̚​N̐Y̡Hͨ͊̽س̾̎801;̸̪̯E̾;̧̲̬͛ͪ̈́͘809;ͧ⁲̨̦̱̹̭̰C \877̙̝͖̏Oͮ͏7;͍M͊̒ͪ̚873; 876;Ỿ860;̲̖Ȇ∱̛̟͌S̨̥̫͎ͯ̿̔̀ͅ


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


主持人说明此帖子已锁定,以防止对其内容进行不当编辑。这篇文章看起来和它应该看起来完全一样——它的内容没有问题。请不要为我们的注意标记。

其他回答

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

我确信您现在已经知道不应该为此使用正则表达式。

在我看来,你试图匹配结尾没有“/”的标签。试试看:

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

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

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

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

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

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

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

这是一个神奇的模式:

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

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

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

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

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

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

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

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

解决方案如下:

<?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]);
?>

为了深入测试,我输入了字符串自动关闭标记,如:

<hr/><br/><br>

我还输入了标记:

一个属性多个属性值绑定到单引号或双引号的属性分隔符为双引号时包含单引号的属性,反之亦然在“=”符号之前、之后以及前后都有空格的“unputy”属性。

如果你在上面的概念证明中发现了不起作用的东西,我可以分析代码来提高我的技能。

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

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

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

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

</EDIT>

了解模式

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

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

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

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*(?=>)(*跳过)(*失败))|(?:[\w:]+\b(?=((?:“[\s\s]*?”|'[\s\s]*?'|[^>]?)*)>)\2(?<!/))|/?)|\?[\S\S]*?\|(?:

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)
 )
 >