RegEx匹配没有特定属性的<a> HTML标记

11

在Java中,我需要匹配字符串中没有href属性的<a>标签。例如在以下字符串中:

text <a class="aClass" href="#">link1</a> text <a class="aClass" target="_blank">link2</a> text

它不应匹配<a class="aClass" href="#">link1</a>(因为它包含href),但它应该匹配<a class="aClass" target="_blank">link2</a>(因为它不包含href)。

我成功构建了一个正则表达式来匹配我的<a>标签:

<a[^>]*>(.*?)</a>

但我不知道如何使用正则表达式消除带有href属性的<a>标签。

(我知道可以使用HTML解析器等工具,但我需要使用正则表达式来完成这个任务。)


5
为什么需要使用正则表达式?HTML不是一种正则语言 - David Cain
4
使用正则表达式解析HTML的原因有很多。一些编辑器允许使用正则表达式进行搜索和替换,而同一个编辑器不支持“插入你的HTML解析代码”。尝试从非常糟糕格式的HTML代码中提取数据可能会导致解析错误。或者作业要求使用正则表达式而不是解析引擎。另外如果文档根本不是HTML而只包含HTML示例,那该怎么办呢?无论赞成还是反对,使用解析引擎解析HTML并不总是最好的选择。 - Ro Yo Mi
1
我非常确定有人在运行类似机器人的脚本,每当有一个包含“html”和“regex”这两个词的问题时,他们自动发布一个指向那个“你不能使用正则表达式解析HTML...”答案的链接。太滑稽了。 - acdcjunior
LOL,他们可能也使用解析引擎来完成这个。 - Ro Yo Mi
4个回答

32

描述

小心使用像<a[^>]*这样的正则表达式,因为它们也会匹配其他有效的以a开头的HTML标签,例如<abbr><address>。仅查找字符串href的存在也不够,因为该字符串可能在另一个属性的值内,例如<a class="thishrefstuff"...或作为另一个属性的一部分,如<a hreflang="en"...

此表达式将:

  • 匹配所有不包含href属性的锚点标签<a...</a>
  • 它将强制标签名称为a而不是以字母a开头的标签,如<address>
  • 忽略包含属性名称中嵌入子字符串href的属性,如有效的hreflang='en'或虚构的Attributehref="some value"
  • 忽略所有正确格式化属性值内的字符,如bogus='href=""'

<a(?=\s|>)(?!(?:[^>=]|=(['"])(?:(?!\1).)*\1)*?\shref=['"])[^>]*>.*?<\/a>

enter image description here

详细解释

  • <a(?=\s|>)匹配开放标签并确保在标签名称后面的下一个字符是空格或关闭括号,这强制名称为a而不是其他东西
  • (?!开始负向前瞻,如果我们在此标记中找到href,则此类型的标记不是我们要找的标记。
    • (?:开始非捕获组以通过标记中的所有字符
    • [^>=]匹配所有非标记闭合字符,从而防止正则表达式引擎离开标记,并且不等于号可以防止引擎盲目匹配所有字符
    • |或者
    • =(['"])匹配后跟开单引号或双引号的等号。 引号捕获到第2组中,以便稍后正确匹配
    • (?:(?!\1).)*匹配所有不是与打开引号匹配的关闭引号的字符
    • \1匹配正确的关闭引号
    • )*?关闭非捕获组并尽可能多次重复它,直到
    • \shref=['"]匹配所需的href属性。 \s=["']确保属性名称仅为href
    • )关闭负向前瞻
  • [^>]*>.*?<\/a>匹配从开放到关闭的整个字符串

Java代码示例:

输入文本

<abbr>RADIO</abbr> text <a class="aClass"

<code>import java.util.regex.Pattern;
import java.util.regex.Matcher;
class Module1{
  public static void main(String[] asd){
  String sourcestring = "source string to match with pattern";
  Pattern re = Pattern.compile("<a(?=\\s|>)(?!(?:[^>=]|=(['\"])(?:(?!\\1).)*\\1)*?\\shref=['\"])[^>]*>.*?<\\/a>
",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
  Matcher m = re.matcher(sourcestring);
  int mIdx = 0;
    while (m.find()){
      for( int groupIdx = 0; groupIdx < m.groupCount()+1; groupIdx++ ){
        System.out.println( "[" + mIdx + "][" + groupIdx + "] = " + m.group(groupIdx));
      }
      mIdx++;
    }
  }
}
</code>

匹配

<code>$matches Array:
(
    [0] => Array
        (
            [0] => <a bogus='href=""' class="aClass" target="_blank">link2</a>
        )

    [1] => Array
        (
            [0] => 
        )

)
</code>

2
完美的答案。它非常适合我正在尝试做的事情。唯一的问题(对于其他人)可能是,如果存在没有引号的href属性,它将匹配它。例如:<a href=http://www.java.com></a> - user2287359

9

我觉得你使用正则表达式来做这个事情有点奇怪,不过你可以使用负向先行断言。

<a(?![^>]+href).*?>(.*?)</a>

@Raedwald 可以使用 DOTALL。 - Explosion Pills

0

我不是Java专家,但你可以尝试这样做:

String regex = new String("(?i)<a(?>[^h>]++|(?<! )h++|h++(?!ref\\s*+=))*>((?>[^<]++|<(?!/a>))*)</a>");
String replacement = new String("$1");
str.replaceAll(regex,replacement);

-1

你可以选择先匹配所有标签,然后使用正则表达式匹配那些带有<script>标签的内容,以便忽略它们。因此,你的伪代码应该如下:

<a>tags = html.find(all<a>tags);
for(String <a>tag : <a>tags){
    if(<a>tag.isHref()) continue;
    //do proccessing
}

-1 OP指定使用正则表达式而不是HTML解析引擎来完成这个。 - Ro Yo Mi
@Denomales 这是伪代码...不是HTML解析引擎。我没有链接或说它是解析器,我只是建议使用正则表达式的另一种方式。 - David says Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接