【问题标题】:Javascript replace() regular expression too greedyJavascript replace() 正则表达式太贪心
【发布时间】:2014-01-24 02:01:03
【问题描述】:

我正在尝试清理 HTML 输入字段。我想保留一些标签,但不是全部,所以在读取元素值时不能只使用.text()。我在 Safari 中的 JavaScript 中使用正则表达式时遇到了一些问题。这是代码的 sn-p(我从另一个 SO 线程答案中复制了这段正则表达式):

aString.replace (/<\s*a.*href=\"(.*?)\".*>(.*?)<\/a>/gi, '$2 (Link->$1)' ) ;

这是失败的示例输入:

<a href="http://blar.pirates.net/black/ship.html">Go here please.</a></p><p class="p1"><a href="http://blar.pirates.net/black/ship.html">http://blar.pirates.net/black/ship.html</a></p>

这个想法是,href 将被拉出并作为纯文本输出到将被链接的文本旁边。所以上面的输出最终应该是这样的:

Go here please (Link->http://blar.pirates.net/black/ship.html)
http://blar.pirates.net/black/ship.html (Link->http://blar.pirates.net/black/ship.html)

但是,正则表达式在第一场比赛中一直抓取到第二个 &lt;/a&gt; 标记,所以我丢失了第一行输出。 (实际上,只要锚元素相邻,它就会抓取到列表的最下方。)输入是一个长字符串,而不是用 CR/LF 或任何东西分割成行。

我尝试过使用这样的非贪婪标志(注意第二个问号):

/<\s*a.*href=\"(.*?)\".*?>(.*?)<\/a>/ig

但这似乎并没有改变任何东西(至少在我尝试的少数测试器/解析器中没有,其中之一在这里:http://refiddle.com)。还尝试了 /U 标志,但这没有帮助(或者这些解析器没有识别它)。

有什么建议吗?

【问题讨论】:

  • 只是让您知道,您的正则表达式不足以防止 a 标记,它们可以在 href 属性中使用单引号或不使用引号。或者他们可以使用内联 onclick 或其他事件处理程序。
  • 幸好这不是公共系统,所以我不关心安全性。这只是试图从粘贴到此字段中的文本中删除格式。 (这是一个内部评论系统——他们在数据库中的父记录上输入 cmets。)

标签: javascript regex non-greedy


【解决方案1】:

模式中有几个错误和可能的改进:

/<
\s*    #  not needed (browsers don't recognize "< a" as an "a" tag)

a      #  if you want to avoid a confusion between an "a" tag and the start
       # of an "abbr" tag, you can add a word boundary or better, a "\s+" since
       # there is at least one white character after.

.      #  The dot match all except newlines, if you have an "a" tag on several
       # lines, your pattern will fail. Since Javascript doesn't have the 
       # "singleline" or "dotall" mode, you must replace it with `[\s\S]` that
       # can match all characters (all that is a space + all that is not a space)

*      #  Quantifiers are greedy by default. ".*" will match all until the end of
       # the line, "[\s\S]*" will match all until the end of the string!
       # This will cause to the regex engine a lot of backtracking until the last
       # "href" will be found (and it is not always the one you want)

href=  # You can add a word boundary before the "h" and put optional spaces around
       # the equal sign to make your pattern more "waterproof": \bhref\s*=\s*

\"     #  Don't need to be escaped, as Markasoftware notices it, an attribute
       # value is not always between double quotes. You can have single quotes or
       # no quotes at all. (1)
(.*?)
\"     # same thing
.*     # same thing: match all until the last >
>(.*?)<\/a>/gi

(1) -> 关于引号和href属性值:

要处理单引号、双引号或无引号,您可以使用捕获组和反向引用:

\bhref\s*=\s*(["']?)([^"'\s>]*)\1

详情:

\bhref\s*=\s*
(["']?)     # capture group 1: can contain a single, a double quote or nothing 
([^"'\s>]*) # capture group 2: all that is not a quote to stop before the possible
            # closing quote, a space (urls don't have spaces, however javascript
            # code can contain spaces) or a ">" to stop at the first space or
            # before the end of the tag if quotes are not used. 
\1          # backreference to the capture group 1

请注意,您使用此子模式添加捕获组,a 标记之间的内容现在位于捕获组 3. 考虑将替换字符串 $2 更改为 $3

好吧,你可以这样写你的模式:

aString.replace(/<a\s+[\s\S]*?\bhref\s*=\s*(["']?)([^"'\s>]*)\1[^>]*>([\s\S]*?)<\/a>/gi,
               '$3 (Link->$1)');

【讨论】:

  • 哇,好东西!我很欣赏这种彻底性。我会查看这些并进行一些更改。
【解决方案2】:

使用

href="[^"]+"

而不是

href=\"(.*?)\"

基本上这会抓取任何字符,直到遇到下一个"

虽然实现类似 markdown 语法的东西可能会更容易,这样您就不必担心删除错误的标签,只需在显示文本时将所有标签都删除并用对应的 html 标签替换 markdowns。

例如,在 SO 上,您只需使用

即可建立链接

[link text](http://linkurl.com)

进行替换的正则表达式是

var displayText = "This is just some text [and this is a link](http://example.com) and then more text";
var linkMarkdown = /\[([^\]]+)\]\(([^\)]+)\)/;
displayText.replace(linkMarkdown,'<a href="$2">$1</a>');

或者使用已经建立的库来进行转换。

【讨论】:

  • href="[^"]++" 将比href="[^"]+" 更优化,因为如果匹配失败,它不会尝试回溯到不可能的匹配(regular-expressions.info/possessive.html 获取信息)。但是 Markasoftware 有一点,这个正则表达式可能没有足够的保护。
  • @Robin:Javascript 正则表达式没有所有格量词,也没有原子组。但是,您可以使用以下技巧模拟原子组((?&gt;a+) &lt;=&gt; a++):"(?=([^"]+))\1",因为前瞻是原子的。
  • 我会尝试建议的更改。我认为降价对于这种用法来说有点多。这是一个跟踪 cmets 的内部系统;我只需要剥离大部分来自人们可能复制/粘贴信息的格式化 HTML。我正在努力从他们可能复制的链接中提取 URL。
  • @Patrick:这似乎对我不起作用(至少在 reFiddle 的测试器/解析器中)。这是我用于正则表达式的字符串(带有您建议的编辑):&lt;\s*a.*href="[^"]+".*&gt;(.*?)&lt;\/a&gt;
  • 啊,是的,它似乎确实有效。谢谢你的小提琴。我也让它在 ReFiddle [link] (refiddle.com) 中工作。 (一定是我的 ReFiddle 出现了一个小故障,没有正确刷新。我在重新加载整个页面后让它在那里工作。)
【解决方案3】:

谢谢大家的建议;它对我帮助很大,并且有很多改进它的想法。

但我想我找到了原始正则表达式失败的具体原因。 Casimir 的回答涉及到它,但直到我遇到这个修复时我才理解它。

我一直在错误的地方寻找问题,这里:

/<\s*a.*href=\"(.*?)\".*>(.*?)<\/a>/gi
                       ^

我能够通过在a.*hre 区域之后插入一个问号来修复我的原始查询,如下所示:

/<\s*a.*?href=\"(.*?)\".*>(.*?)<\/a>/gi
        ^

我确实计划使用此处的其他建议来进一步改进我的陈述。

--C

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多