我需要匹配所有这些开头标记:
<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一次或多次,然后找到零个或多个空格,然后查找任何字符零次或多次,贪婪,除了/,然后查找大于
我有这个权利吗?更重要的是,你怎么看?
当前回答
解决方案如下:
<?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特殊字符转义。
其他回答
如果您只是试图查找这些标记(没有解析的野心),请尝试以下正则表达式:
/<[^/]*?>/g
我在30秒内写下了它,并在这里进行了测试:http://gskinner.com/RegExr/
它匹配您提到的标记类型,而忽略您所说的要忽略的类型。
的确,在编程时,在处理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*)>
当然,这些不考虑周围的上下文和一些边缘情况,但如果您真的想处理这些问题(例如,通过在另一个正则表达式的匹配项之间进行搜索),也可以处理这些问题。
在一天结束时,使用最适合该作业的工具,即使该工具恰好是正则表达式。
<?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库中,抓取所有元素,循环并过滤掉不自关闭的并对其进行操作。
我确信您现在已经知道不应该为此使用正则表达式。
首先,回答一个直接的问题:正则表达式有一个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不是一种常规语言,这会使解析变得非常困难。我的解决方案是使用整洁的程序将其转换为常规语言,然后使用XML解析器来使用结果。有很多好的选择。我的程序使用Java和jtidy库编写,将HTML转换为XML,然后Jaxen将其展开为结果。