mil*_*fer 3 html php regex dom nested
我已经花了几天时间试图找到一个使用正则表达式的解决方案(在有人说之前:我知道我应该使用 PHP DOM 文档库或类似的东西,但让我们把它作为一个理论问题),寻找答案,我终于来了与我将在这个问题结束时展示的内容相提并论。
以下只是我之前尝试过的很多事情的总结。
首先,我所说的相同类型的嵌套标签是指:
Text outside any div
<div id="my_id"> bla bla
<div>
bla bla bla
<div style="some style here">
lalalalala
</div>
</div>
I'm trapped in a div!
</div>
more text outside divs
<div>more divs here!
<div id="justbeingannoying">radiohead rules</div>
</div>
Run Code Online (Sandbox Code Playgroud)
现在想象我想使用正则表达式删除所有 div及其内容。所以预期的结果是:
Text outside any div
more text outside divs
Run Code Online (Sandbox Code Playgroud)
第一个想法是匹配一切。以下正则表达式匹配具有属性(样式、id 等)的 div 标签:
/<div[^>]*>.*<\/div>/sig
Run Code Online (Sandbox Code Playgroud)
这个问题,当然,这将匹配一切的第一个“<DIV”的开始和最后一个“</ DIV>”之间,所以它将匹配“更多的文本之外的div”太(点击此处查看:HTTPS:/ /regex101.com/r/iR8mY2/1),这不是我们(我)想要的。
这可以使用U 修饰符(Ungreedy)解决
/<div[^>]*>.*<\/div>/sigU
Run Code Online (Sandbox Code Playgroud)
但是接下来我们会遇到比我们想要的少的问题:它只会从第一个“< div”到第一个“”匹配(因此,如果我们删除匹配项,除了一些不匹配的标签之外,还会有文本“我被困在一个 div 中!”,这是我们不想要的)。
所以,我找到了一个解决方案,它就像嵌套括号、方括号等的魅力:
/\[([^\[\]]*+|(?R))*\]/si
Run Code Online (Sandbox Code Playgroud)
基本上,它的作用是找到一个左方括号,然后匹配任何 * 既不是左方括号也不是右方括号 * 或它的递归结构,找到一个右方括号。
我现在的工作是一个糟糕的解决方案:基本上,首先我用方括号替换所有开始标签(由于其他原因,它不能在我的代码中),然后是结束方括号的结束标签,然后我使用之前的正则表达式。我知道,这不是一个非常优雅的解决方案。
问题是我真的很想知道如何只用一个正则表达式就可以做到这一点。这似乎比以前的正则表达式的“[”和“]”是由HTML标记替换明显有工作。但并没有那么容易。问题是字符的否定 ("[^.......]" 对像 "div" 这样的字符串不起作用。似乎可以通过以下方式实现类似的东西:
.+?(?=<div>)
Run Code Online (Sandbox Code Playgroud)
当然,结束标记也是如此
.+?(?=<\/div>
Run Code Online (Sandbox Code Playgroud)
这就是我或多或少地到达这个正则表达式的方式
/<div((.+?(?=<\/div>)|.+?(?=<div>))|(?R))*<\/div>/gis
Run Code Online (Sandbox Code Playgroud)
这与我之前介绍的第一个正则表达式完全一样:https : //regex101.com/r/yU8pV3/1
所以,这是我的问题:那个正则表达式有什么问题?
谢谢!
由于这个问题得到了积极的回应,我将发布一个答案,解释您的方法有什么问题,并将展示如何匹配不是特定文本的文本。
但是,我想强调的是:不要使用它来解析真实的、任意的 HTML 代码,因为正则表达式只能用于纯文本。
在匹配结束部分之前,您的正则表达式包含<div((.+?(?=<\/div>)|.+?(?=<div>))|(?R))*部分(与 相同<div((.+?(?=<\/?div>))|(?R))*)<\/div>。当您有一些分隔文本时,不要依赖简单的懒惰/贪婪点匹配(除非用于展开循环结构 - 当您知道自己在做什么时)。它的作用是这样的:
<div-<div字面匹配(同样,<diverse由于缺少单词边界或\s在它之后)( - 匹配的第 1 组:
(.+?(?=<\/div>)|.+?(?=<div>))- 匹配任何 1+ 个字符(尽可能少)直到第一个</div>或第一个<div>| (?R) - 递归(即插入和使用))* - 重复第 1 组零次或多次。问题很明显:该(.+?(?=<\/?div>))部分不排除匹配<div>or </div>,此分支必须仅将文本 NOT EQUAL 匹配到前导和尾随定界符。
要匹配某些特定文本以外的文本,请使用调和的贪婪标记。
<div\b[^<]*>((?:(?!<\/?div\b).)+|(?R))*<\/div>\s*
^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
请参阅正则表达式演示。请注意,您必须使用 DOTALL 修饰符才能跨换行符匹配文本。捕获组是多余的,您可以将其删除。
这里重要的是(?:(?!<\/?div\b).)+只匹配 1 个或多个不是 a<div....>或</div序列的起始字符的字符。请参阅我上面链接的线程,了解它是如何工作的。
至于性能,缓和的贪婪令牌是消耗资源的。展开循环技术来拯救:
<div\b[^<]*>(?:[^<]+(?:<(?!\/?div\b)[^<]*)*|(?R))*<\/div>\s*
Run Code Online (Sandbox Code Playgroud)
现在,该标记看起来像[^<]+(?:<(?!\/?div\b)[^<]*)*: 1+ 个字符而不是<后跟 0+ 个序列,<后面没有/div或div(作为一个完整的单词),然后是 0+ non- <s。
<div\b可能仍然匹配 in <div-tmp,因此也许<div(?:\s|>)是通过正则表达式处理此问题的更好方法。尽管如此,使用DOM解析 HTML还是容易得多。