你怎么写一个匹配除第一次出现以外的所有正则表达式?

时间:2015-02-17 18:00:06

标签: c# regex

我正在尝试编写一个正则表达式,它将匹配除html文件中第一个之外的所有图像标记。例如:

<html><body><img src="foo"><span><img src="bar></span><img src="foobar"></body></html>

到目前为止,我只设法创建一个匹配所有图像标记的表达式:

<img[^>]*>

2 个答案:

答案 0 :(得分:4)

只需使用像HtmlAgilityPack这样的真实html解析器来解析html

var html = @"html><body><img src=""foo""><span><img src=""bar""></span><img src=""foobar""></body></html>";
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);

var imgLinks = doc.DocumentNode
                    .Descendants("img")
                    .Skip(1)
                    .Select(x => x.Attributes["src"])
                    .ToList();

不要这样做

var pattern = @"<img[^>]*>"; //your pattern in question
var imgs = Regex.Matches(html, pattern)
                .Cast<Match>()
                .Skip(1)
                .Select(m => m.Value)
                .ToList();

答案 1 :(得分:0)

在这个答案中,我将证明标签可以从正则表达式中进行匹配,这与一些注释中相信标签无法识别但使用完整的HTML / XML解析器相反。

对于演示,我将使用XML的XML语法规则的子集,从那里可用的www.www.org规范,扩展到可以从STag和EmptyElemTag到达的所有规则,这些是我们想要的标签比赛。由于没有向后递归规则,我将证明这组规则可以转换为regexp以分别解析start和empty标记。

由于xml使用UTF字符编码并且它允许超出范围\ u0000 \ uffff的字符,我必须为扩展的UTF编码中的字符类选择一些表示法,因此我将对\ u表示法使用非标准扩展由五个十六进制数字而不是四个数字组成,以简化语法到正则表达式的转换(允许允许的字符在0x10000-0xeffff范围内)

借用XML版本1.1的xml规范是start和empty元素标记的语法:

STag ::= '<' Name (S Attribute)* S? '>'
EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
Name ::= (NameStartChar NameChar*)
NameChar ::= (NameStartChar | [-.0-9\u000b7\u00300-\u0036f\u0203f-\u02040])
NameStartChar ::= ([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])
S ::= ([\u00020\u00009\u0000d\u0000a]+)
Attribute ::= (Name Eq AttValue)
Eq ::= (S? '=' S?)
AttValue ::= ( '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'" )
Reference ::= (EntityRef | CharRef)
EntityRef ::= ('&' Name ';')
CharRef ::= ('&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')

要构造接受开始标记和空标记的正则表达式,我已经开始使用上面的语法并从中构造一个接受开始和空标记的简单开始规则:

Start ::= STag | EmptyElemTag

然后用每个规则的(正确括号)右边替换所有非终结符,直到我只有右侧的终端元素和正则表达式运算符:

Start ::= '<' Name (S Attribute)* S? '>' | '<' Name (S Attribute)* S? '/>'

我可以做一些操作来分组术语并获得

Start ::= '<' Name (S Attribute)* S? '/'?'>'

现在替换Attribute

Start ::= '<' Name (S Name Eq AttValue)* S? '/'? '>'

现在替换AttValue

Start ::= '<' Name (S Name Eq ('"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'" ))* S? '/'? '>'

现在替换Reference

Start ::= '<' Name (S Name Eq ('"' ([^<&"] | EntityRef | CharRef)* '"' | "'" ([^<&'] | EntityRef | CharRef)* "'" ))* S? '/'? '>'

现在替换EntityRef

Start ::= '<' Name (S Name Eq ('"' ([^<&"] | '&' Name ';' | CharRef)* '"' | "'" ([^<&'] | '&' Name ';' | CharRef)* "'" ))* S? '/'? '>'

现在替换CharRef

Start ::= '<' Name (S Name Eq ('"' ([^<&"] | '&' Name ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* '"' | "'" ([^<&'] | '&' Name ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* "'" ))* S? '/'? '>'

现在Eq

Start ::= '<' Name (S Name S? '=' S? ('"' ([^<&"] | '&' Name ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* '"' | "'" ([^<&'] | '&' Name ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* "'" ))* S? '/'? '>'

下一个S

Start ::= '<' Name (([\u00020\u00009\u0000d\u0000a]+) Name ([\u00020\u00009\u0000d\u0000a]+)? '=' ([\u00020\u00009\u0000d\u0000a]+)? ('"' ([^<&"] | '&' Name ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* '"' | "'" ([^<&'] | '&' Name ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* "'" ))* ([\u00020\u00009\u0000d\u0000a]+)? '/'? '>'

现在替换Name

Start ::= '<' (NameStartChar NameChar*) (([\u00020\u00009\u0000d\u0000a]+) (NameStartChar NameChar*) ([\u00020\u00009\u0000d\u0000a]+)? '=' ([\u00020\u00009\u0000d\u0000a]+)? ('"' ([^<&"] | '&' (NameStartChar NameChar*) ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* '"' | "'" ([^<&'] | '&' (NameStartChar NameChar*) ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* "'" ))* ([\u00020\u00009\u0000d\u0000a]+)? '/'? '>'

现在替换NameChar

Start ::= '<' (NameStartChar (NameStartChar | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) (([\u00020\u00009\u0000d\u0000a]+) (NameStartChar (NameStartChar | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) ([\u00020\u00009\u0000d\u0000a]+)? '=' ([\u00020\u00009\u0000d\u0000a]+)? ('"' ([^<&"] | '&' (NameStartChar (NameStartChar | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* '"' | "'" ([^<&'] | '&' (NameStartChar (NameStartChar | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* "'" ))* ([\u00020\u00009\u0000d\u0000a]+)? '/'? '>'

最后NameStartChar

Start ::= '<' (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) (([\u00020\u00009\u0000d\u0000a]+) (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) ([\u00020\u00009\u0000d\u0000a]+)? '=' ([\u00020\u00009\u0000d\u0000a]+)? ('"' ([^<&"] | '&' (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* '"' | "'" ([^<&'] | '&' (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) (([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff]) | [-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*) ';' | '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';')* "'" ))* ([\u00020\u00009\u0000d\u0000a]+)? '/'? '>'

最后,在用'c'替换c并删除不需要的空格后,正则表达式导致:

<(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])|[-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*)(([\u00020\u00009\u0000d\u0000a]+)(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])|[-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*)([\u00020\u00009\u0000d\u0000a]+)?=([\u00020\u00009\u0000d\u0000a]+)?(\"([^<&\"]|&(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])|[-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*);|&#[0-9]+;|&#x[0-9a-fA-F]+;)*\"|\'([^<&\']|&(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])(([:A-Za-z_\u000c0-\u000d6\u000d8-\u000f6\u000f8-\u002ff\u00370-\u0037d\u0037f-\u01fff\u0200c-\u0200d\u02070-\u0218f\u02c00-\u02fef\u03001-\u0d7ff\u0f900-\u0fdcf\u0fdf0-\u0fffd\u10000-\ueffff])|[-.0-9\u000b7\u00300-\u0036f\u0203f\u0203f\u02040])*);|&#[0-9]+;|&#x[0-9a-fA-F]+;)*\'))*([\u00020\u00009\u0000d\u0000a]+)?/?>

当然,您可以使用更多的正则表达式来匹配开始/空标记,但这是我能够开发的简单方法之一,以应对评论中指出的方案。 / p>

更简单的可能是:

<[iI][mM][gG][ \t\n\r]+([^>"']|"[^"]*"|'[^']*')*>

如果您不处理范围\ u0000 - \ u007f(ascii范围)之外的UTF字符,并且您知道HTML文件有效。 (这最后一个可能是错误的,谨慎使用我已经把它构建在我的头脑中并且可能会错误地采取一些奇怪的情况)