Perl RegEx:将模式限制为仅出现第一个字符

时间:2010-07-27 13:04:10

标签: regex perl

我正在尝试从许多格式错误的sgml文档中提取日期元素的内容。例如,文档可以包含一个简单的日期元素,如

<DATE>4th July 1936</DATE>

<DATE blaAttrib="89787adjd98d9">4th July 1936</DATE>

但也可以像毛茸茸一样:

<DATE blaAttrib="89787adjd98d9">4th July 1936
<EM>spanned across multiple lines and EM element inside DATE</EM></DATE>

目标是获得“1936年7月4日”。 由于文件不大,我选择将整个内容读入变量并执行正则表达式。以下是我的Perl代码的片段:

{
    local $/ = undef;
    open FILE, "$file" or die "Couldn't open file: $!";
    $fileContent = <FILE>;
    close FILE;

    if ( $fileContent =~ m/<DATE(.*)>(.*)<\/DATE>/)
    {
        # $2 should contain the "4th July 1936" but it did not.
    }
}

不幸的是,正则表达式不适用于毛茸茸的例子。这是因为<DATE>内部有一个<EM>元素,它也跨越多行。

任何善良的灵魂都可以给我一些指示,指示或线索吗?

谢谢堆!

7 个答案:

答案 0 :(得分:3)

Use an XML parser if you can

但是从你的例子中,你可能会尝试

if ($fileContent =~ m/<DATE[^>]*>([^<]+)/) {
  # use $1 here
  # you may need to strip new lines
}

答案 1 :(得分:3)

使用HTML解析器。

使用HTML解析器。

请使用HTML解析器。

但对于正则表达式,我会尝试

<DATE(.*?)>(.*)<\/DATE>

这应该比KennyTM的替代方案更快......那么,为什么要捕获第二组?

答案 2 :(得分:3)

如果日期格式是固定的,您可能希望使用以下内容:

m/<DATE(.*)>([0-9]+(st|nd|rd|th)\s(January|February|March|April|May|June|July|August|September|October|November|December)\s[0-9]+)(.*)<\/DATE>/

答案 3 :(得分:3)

而不是匹配。* ,您应匹配“不是锚点的所有内容”

即:


 if($string =~ /^<DATE[^>]*>([^<]+)</){

那里,$ 1是你的约会

答案 4 :(得分:2)

你应该使用非贪婪匹配和修饰符来制作。匹配换行符

my @l = (
'<DATE>4th July 1936</DATE>',
'<DATE blaAttrib="89787adjd98d9">4th July 1936</DATE>',
'<DATE blaAttrib="89787adjd98d9">4th July 1936
<EM>spanned across multiple lines and EM element inside DATE</EM></DATE>'
);

foreach(@l) {
  /^<DATE.*?>(.*?)</s && print $1;
}

输出:

4th July 1936
4th July 1936
4th July 1936

答案 5 :(得分:0)

即使你的“毛茸茸”的例子也可以简化为类似的类型。如果你总是希望1)与开始标记在同一行上的实际日期 - 和2)这就是你想要的 - 那么结束标记的位置并不重要。

$fileContent =~ m/<DATE([^>]*)>\s*(\d+\p{Alpha}+\s+\p{Alpha}+\s+\d{4})/

总是会起作用。 (如果你不想在标签中找到'>',那么在.*吃掉你的整条线之后不要引起这么多的回溯是一个好主意,导致表达式失败然后必须回馈并检查,退回并检查,......)

答案 6 :(得分:-4)

没有任何方法可以在多行上使用正则表达式,但你可以使用一点技巧。如果文件不大,正如您所提到的,您可以先用一些值替换所有'\ n'字符(NEW_LINE或类似的东西),或者您可以删除它们然后使用您的模式。