正则表达式从html文件中删除重复的脚本

时间:2011-10-14 14:41:09

标签: regex

假设您有一个带有几个重复脚本的HTML文件,这意味着同一资源有多个外部脚本标记,例如在页面上加载jquery 3次。是否有一个有效的正则表达式,可以删除重复项,但保留第一个。重复项将具有完全相同的src名称。

语言是PHP,这是一个很好的例子:

在:

<script src="js/jquery.js" type="text/javascript"></script>
    some content
<script src="js/jquery.js" type="text/javascript"></script>
    more content
<script src="js/jquery.js" type="text/javascript"></script>

后:

<script src="js/jquery.js" type="text/javascript"></script>
    some content
    more content

3 个答案:

答案 0 :(得分:2)

声明:

许多人会理所当然地说,使用正则表达式解析非常规语言(如HTML)会充满危险。他们是对的。 可靠地解析这些语言的唯一方法是使用专门为该任务设计的解析器。使用正则表达式的解决方案通常会有很多主题文本的特殊情况会导致它失败,从而导致误报和丢失匹配。

那说......

如果一个人坚持使用正则表达式来处理HTML / XML标记,并且他们意识到固有的局限性,那么有办法制定一个可以最大限度地减少这些潜在陷阱的正则表达式解决方案,并做一个“相当不错的“工作(取决于问题的具体要求)。但是,要正确处理许多罕见(但有效且可能)的边缘情况(例如,正确处理包含<>尖括号的HTML标记属性),正确的正则表达式通常可能相当复杂,而不适用于微弱的情况 - 的-心脏。

理解以下正则表达式解决方案需要对正则表达式语言和正则表达式引擎的基础机制有相当深入的理解。肯定会有标记文本的例子导致它失败,但是对于许多典型标记的情况,以下解决方案应该做得很好。

这是一个经过测试的PHP函数,用于删除具有重复SCRIPT属性值的SRC个元素:

// Strip all SCRIPT elements having duplicate SRC URLs.
function stripDuplicateScripts($text) {
    $re = '%
        # Match duplicate SCRIPT element having same SRC attribute URL.
        (                   # $1: Everything up to duplicate SCRIPT element.
          <script           # literal start of script open tag
          (?:               # Zero or more attributes before SRC.
            \s+             # Whitespace required before attribute.
            (?!src\b)       # Assert this attribute is not "SRC".
            [\w\-.:]+       # Non-SRC attribute name.
            (?:             # Attribute value is optional.
              \s*=\s*       # Value separated by =, optional ws.
              (?:           # Group attribute value alternatives.
                "[^"]*"     # Either a double quoted value,
              | \'[^\']*\'  # or a single quoted value,
              | [\w\-.:]+   # or an unquoted value.
              )             # End group of value alternatives.
            )?              # Attribute value is optional.
          )*                # Zero or more attributes before SRC.
          \s+               # Whitespace required before SRC attrib.
          src               # Required SRC attribute name.
          \s*=\s*           # Value separated by =, optional ws.
          ([\'"])           # $2: Attrib value opening quote.
          ((?:(?!\2).)+)    # $3: SRC attribute value (a URL).
          \2                # Attrib value closing quote.
          (?:               # Zero or more attributes after SRC.
            \s+             # Whitespace required before attribute.
            [\w\-.:]+       # Attribute name.
            (?:             # Attribute value is optional.
              \s*=\s*       # Value separated by =, optional ws.
              (?:           # Group attribute value alternatives.
                "[^"]*"     # Either a double quoted value,
              | \'[^\']*\'  # or a single quoted value,
              | [\w\-.:]+   # or an unquoted value.
              )             # End group of value alternatives.
            )?              # Attribute value is optional.
          )*                # Zero or more attributes after SRC.
          \s*               # Optional whitespace before tag close.
          >                 # End of SCRIPT open tag.
          </script\s*>      # SCRIPT close tag.
          .*?               # Stuff up to duplicate script element.
        )                   # End $1: Everything up to duplicate SCRIPT.
        <script             # literal start of script open tag
        (?:                 # Zero or more attributes before SRC.
          \s+               # Whitespace required before attribute.
          (?!src\b)         # Assert this attribute is not "SRC".
          [\w\-.:]+         # Non-SRC attribute name.
          (?:               # Attribute value is optional.
            \s*=\s*         # Value separated by =, optional ws.
            (?:             # Group attribute value alternatives.
              "[^"]*"       # Either a double quoted value,
            | \'[^\']*\'    # or a single quoted value,
            | [\w\-.:]+     # or an unquoted value.
            )               # End group of value alternatives.
          )?                # Attribute value is optional.
        )*                  # Zero or more attributes before SRC.
        \s+                 # Whitespace required before SRC attrib.
        src                 # Required SRC attribute name.
        \s*=\s*             # Value separated by =, optional ws.
        ([\'"])             # $4: Attrib value opening quote.
        \3                  # This script must have duplicate SRC URL.
        \4                  # Attrib value closing quote.
        (?:                 # Zero or more attributes after SRC.
          \s+               # Whitespace required before attribute.
          [\w\-.:]+         # Attribute name.
          (?:               # Attribute value is optional.
            \s*=\s*         # Value separated by =, optional ws.
            (?:             # Group attribute value alternatives.
              "[^"]*"       # Either a double quoted value,
            | \'[^\']*\'    # or a single quoted value,
            | [\w\-.:]+     # or an unquoted value.
            )               # End group of value alternatives.
          )?                # Attribute value is optional.
        )*                  # Zero or more attributes after SRC.
        \s*                 # Optional whitespace before tag close.
        >                   # End of SCRIPT open tag.
        </script\s*>        # SCRIPT close tag.
        \s*                 # Strip whitespace following duplicate.
        %six';
    while (preg_match($re, $text)) {
        $text = preg_replace($re, '$1', $text);
    }
    return $text;
}

上面的函数使用一个递归应用的正则表达式,直到找不到匹配项。虽然乍一看正则表达式看起来像一个怪物,但它实际上非常简单(如果你精通正则表达式语法),并且大多数文本都包含描述性注释。需要此正则表达式的复杂性来处理HTML允许的各种属性/值格式。例如,SCRIPT标记可能在SRC属性之前和之后具有任意数量的属性。 SRC属性值可以是单引号或双引号。所有其他属性可能具有引用或不引用的值,并且可能根本没有任何值。带引号的属性可能包含<>尖括号。

答案 1 :(得分:1)

你的问题的简单答案“是否有一个有效的正则表达式,可以删除重复但保留第一个副本”是:AFAIK,不 - 没有一个有效的正则表达式。

基本表达式(根据源文本可能非常低效)如下:

(<script\s+type="text/javascript"\s+src="[^"]*">\s*</script>)([\s\S]*?)\1

替换为:

$1$2

这并没有处理标准格式中任何一个标签(在这种情况下必须相同)的偏差:

<script type="text/javascript" src="javascript.js"></script>

它应该基本匹配并删除脚本标记的第二个实例 - 它与前一个脚本标记完全匹配。如果您需要更灵活地使用脚本标记的精确格式,并且只知道文件名(URL)将是相同的,则可以使用以下表达式:

(<script\s+type="text/javascript"\s+src="([^"]*)"></script>)([\s\S]*?)<script\s+type="text/javascript"\s+src="\2"></script>

替换为

$1$3

它将处理空白的差异,但效率可能更低(接近一半),具体取决于源HTML。

效率受到两个标签副本之间的文本量的影响(每个标签的处理量大约是匹配的标签的三倍)

编辑我相信它必须为每个副本运行一次(三次出现的脚本标记将需要两次运行此替换以将其减少到一次),尽管我不是能够在此刻完全测试PHP。

答案 2 :(得分:0)

如果使用正则表达式解析html,请不要打扰你,简单的事情 可能有用:

$samp = '
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jOOPquery.js" type="text/javascript"></script>
some content
<script type="text/javascript" src="js/jOOPquery.js"></script>
<script src="js/jquery.js" type="text/javascript"></script>
more content
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jOOPquery.js" type="text/javascript"></script>
';

$regex = 
'(?xs)
 (<script (?=\s)[^>]* (?i:(?<=\s)src\s*=\s* "\s*([^"]*?)\s*") [^>]* (?<!/)>\s*</script\s*>
  .*?
 )<script (?=\s)[^>]* (?i:(?<=\s)src\s*=\s* "\s*\2\s*") [^>]* (?<!/)>\s*</script\s*>\s*
';

while ($samp =~ s/$regex/$1/g) {}


print "$samp\n";

输出:

<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jOOPquery.js" type="text/javascript"></script>
some content
more content