如何在标记的属性中使此REGEX ignore =?

时间:2011-09-25 20:53:18

标签: regex

Alan Moore was very helpful解决了我之前的问题,但直到现在我还没有意识到,如果URL中有相同的符号,那么他为删除所有标签的属性而写的REGEX会过早破坏。我花了很长时间在这上面,尝试使用前瞻和后面的不同修改,但无济于事。

我需要这个正则表达式来打破:空间 + 单词 + = ,但即使没有空格,也只是一封信,它正在打破和一个=。

当我使用Javascript格式化具有onclick事件的标记时,这主要是一个问题,例如打开窗口或调用脚本(表单操作)。

正则表达式:

#(\s+[^\s=]+)\s*=\s*([^\s=]+(?>\s+[^\s=]+)*(?!\s*=))#i

要检查的代码:

 onClick=window.open('http%3A%2F%2Fwww.stackoverflow.com%2Ffakeindex.php%3Fsomevariable%3Dsomevalue','popup','scrollbars=yes,resizable=yes,width=716,height=540,left=0,top=0,ScreenX=0,ScreenY=0'); class=someclass

它的作用:

以上内容将在=之前的字母处中断,因此在这种情况下URL被编码,它会在“scrollbars = yes”中的“s”处中断。

我可以对URL进行编码以绕过=,但是javascript的其余部分仍然存在问题,因为存在需要=的变量和值。如果REGEX强制它允许=并且只打破空格后跟一个单词后跟那个=就像应该这样做,那么我应该可以让javascript在自定义标签中工作,因为整个onClick字符串将被捕获为价值。

2 个答案:

答案 0 :(得分:3)

声明:

正如其他人已经说过/强调的那样,使用带有HTML的正则表达式充满了潜在的问题。使用两种混合标记语言,就像你在这里一样,这样做更加危险。 很多这种解决方案(以及任何类似的解决方案)失败的方法。

那说......

回答这个问题需要了解您之前的问题(PHP PREG_REPLACE Returning wrong result depending on order checked)。请注意,我添加了对该问题的答案以及包含对原始代码的最小更改的解决方案。以下是另一个解决方案,有点改进的解决方案。 (这两个答案都解决了这两个具体问题。)

对原始代码的一些随机评论:

  • 表达式:[^\s]+可缩短为:\S+
  • 使用foreach语句,无法保证处理顺序。 (并且的顺序在这里很重要 - 虽然这可能不是问题,因为数组是一次声明的,所以应该有正确的顺序。)
  • 您正在使用([^\[]+)来捕获属性值。我认为你打算使用([^\]]+)(但即使这不是最好的表达方式)。
  • 使用([^\[]+)(或([^\]]+))捕获属性值不允许方括号显示在值中。
  • 正则表达式不是以自由间隔模式编写的,不包含任何注释。
  • 具有多个单词的不带引号的属性值引入了相当多的潜在歧义。如果你想拥有这样的title属性怎么办:title="CSS class is specified: class=myclass"?您应该真正划分这些属性值。

一个(有点)更好的解决方案:

假设:

  • 所有Ltags都将很好地形成。
  • Ltags永远不会嵌套。
  • Ltag属性由"SPACE+WORD+="序列分隔。
  • 其他[specialtags]可能会出现在Ltag内的任何位置,但"SPACE+WORD+="属性分隔符序列除外。
  • 所有Ltag属性值永远不会包含:"SPACE+WORD+="序列。这包括onClick内的多字标题和Javascript片段。

我假设您确切知道Ltag属性中将会发生什么,并且它们将符合上述要求。

这是replaceLTags()的一个稍微改进的版本,它使用回调函数用双引号解析和包装每个属性值。复杂的正则表达式已完全注释。

// Convert all Ltags to HTML links.
function replaceLTags($str){
    // Case 1: No URL specified in Ltag open tag: "[l]URL[/l]"
    $re1 = '%\[l\](.*?)\[/l\]%i';
    $str = preg_replace($re1, '<a href="$1">$1</a>', $str);

    // Case 2: URL specified in Ltag open tag: "[l=URL attr=val]linktext[/l]"
    $re2 = '%
        # Match special Ltag construct: [l=url att=value]linktext[/l]
        \[l=                 # Literal start-of-open-Ltag sequence.
        (\S+)                # $1: link URL.
        (                    # $2: Any/all optional attributes.
          [^[\]]*            # {normal*} = Zero or more non-[]
          (?:                # "Unroll-the-loop" (See: MRE3)
            \[[^[\]]*\]      # {special} = matching [square brackets]
            [^[\]]*          # More {normal*} = Zero or more non-[]
          )*                 # End {(special normal*)*} construct.
        )                    # End $2: Optional attributes.
        \]                   # Literal end-of-open-Ltag sequence.
        (.*?)                # $3: Ltag link text contents.
        \[/l\]               # Literal close-Ltag sequence.
        %six';
    return preg_replace_callback($re2, '_replaceLTags_cb', $str);
}
// Callback function wraps values in quotes and converts to HTML.
function _replaceLTags_cb($matches) {
    // Wrap each attribute value in double quotes.
    $matches[2] = preg_replace('/
        # Match one Ltag attribute name=value pair.
        (\s+\w+=)        # $1: Space, attrib name, equals sign.
        (                # $2: Attribute value.
          (?:            # One or more non-start-of-next-attrib
            (?!\s+\w+=)  # If this char is not start of next attrib,
            .            # then match next char of attribute value.
          )+             # Step through value one char at a time.
        )                # End $2: Attribute value.
        /sx', '$1"$2"', $matches[2]);
    // Put humpty back together again.
    return '<a href="'. $matches[1] .'"'.
        $matches[2] .'>'. $matches[3] .'</a>';
}

主函数regex $re2与Ltag元素匹配,但不会尝试解析单个开放标记属性 - 它将所有属性整合(并捕获到组$2中)到一个子字符串中。然后,回调函数中的正则表达式解析包含所有属性的子字符串,该函数使用所需的"SPACE+WORD+="表达式作为name=value对之间的分隔符。

请注意,此函数可以传递包含多个Ltags的字符串,并且所有内容都将一次性处理。它还将正确处理IPv6文字URL地址,例如:http://[::1:2:3:4:5:6:7](包含方括号)。

如果您坚持走这条路,我建议您使用分隔符作为属性值。我知道你说由于某些原因你不能使用双引号,但是你可以使用'\1'(ASCII 001)这样的特殊字符,然后用回调函数中的双引号替换它。这将大大削减失败的可能方式列表。

答案 1 :(得分:0)

如果可以保证模式永远不会出现在属性值中,则可以在此正则表达式中拆分字符串:

\s+(?=\w+=)

这实际上简化了问题。下面的代码假定URL(可能包含自定义[fill]标记)在第一个空格(如果存在)或[l]标记的结束括号处结束。假设第一个空格之后的所有内容都是一系列以空格分隔的name=value对,其中名称始终与^\w+$匹配,并且该值永远不会包含\s+\w+=的匹配项。值还可能包含[fill]个标记。

function replaceLTags($originalString)
{
  return preg_replace_callback(
    '#\[l=((?>[^\s\[\]]++|\[\w+\])+)(?:\s+((?>[^\[\]]++|\[\w+\])+))?\](.*?)\[/l\]#',
    replaceWithinTags, $originalString);
}

function replaceWithinTags($groups)
{
  $result = "<a href=\"$groups[1]\"";
  $attrs = preg_split('~\s+(?=\w+=)~', $groups[2]);
  foreach ($attrs as $a)
  {
    $result .= preg_replace('#\s*(\w+)=(.*)#', ' $1="$2"', $a);
  }
  $result .= ">$groups[3]</a>";
  return $result;
}

<强> demo

我还假设属性值中没有双引号。如果有,替换仍然有效,但生成的HTML将无效。如果您不能保证没有双引号,则在进行这些替换之前,您可能需要对它们进行URL编码。