使用命名捕获组的正则表达式问题

4

我有以下数值:

start=2011-03-10T13:00:00Z;end=2011-03-30T13:00:00Z;scheme=W3C-DTF

我使用以下正则表达式来剥离“开始”和“结束”日期,并将它们分配给它们自己的命名捕获对:
#^start=(?P<publishDate>.+);end=(?P<expirationDate>.+);#ix'

也许不是最好的正则表达式,但如果'开始'和'结束'值都存在,它足够好用。

现在,我需要做的是,如果'expirationDate'缺失,则仍然匹配'publishDate',反之亦然。

如何使用单个表达式实现此目的?我并不擅长正则表达式,并且我正在开始探索更高级的内容,因此任何对此的帮助将不胜感激。

谢谢!

更新:

感谢Mr. Chung,我已经解决了这个问题,并使用以下表达式:

 #^(start=(?P<publishDate>.*?);)?(end=(?P<expirationDate>.*?);)?#xi

感谢大家一如既往的帮助,非常感谢!:)


考虑分成两个正则表达式吗? - Orbling
我有这个功能,但是很不幸,由于系统内部的限制,我不能在没有额外开发的情况下实现它;目前这不是一个可选项。 :( - Wilhelm Murdoch
3个回答

4

使用 (...)? 来表示可选部分

^(start=(?P<publishDate>.+);)?(end=(?P<expirationDate>.+));)?

是的,这正是我想的。我猜我把括号放错了位置。你的例子中缺少一个括号,但它给了我足够的指导来解决问题。谢谢! - Wilhelm Murdoch

2

这两种方法都将指定的缓冲区设置为一个值(而不是null或undefined)我建议使用第一种方法。

1. 以任意顺序查找以下内容:
/^(?=.*\bstart=(?P<publishDate>.*?);|(?P<publishDate>))(?=.*\bend=(?P<expirationDate>.*?);|(?P<expirationDate>))/ix

/^(?=                                 # from beginning, look ahead for start
       .*\b                               # any character 0 or more times (backtrack to match 'start')
       start=(?P<publishDate>.*?);        # put start date in publish 
    |  (?P<publishDate>)                # OR, put empty string publish 
  )
  (?=                                 # from beginning, look ahead for end
       .*\b                               # same criteria as above ...
       end=(?P<expirationDate>.*?);
    |  (?P<expirationDate>)
  )
/ix

2. 查找以开始/结束顺序的任一/两个内容:
/^(?:.*\bstart=(?P<publishDate>.*?);|(?P<publishDate>))(?:.*\bend=(?P<expirationDate>.*?);|(?P<expirationDate>))/ix

编辑 -

@Josh Davis - 我不得不去PCRE.org搜索,那里有很棒的东西。

使用Perl没有重复名称的问题。
文档:“如果多个组具有相同的名称,则它引用当前匹配中最左侧定义的组。” 在交替使用时永远不会出现问题。

对于PCRE ..
如果与分支重置一起使用,则重复名称将在PHP中正常工作。
分支重置确保重复名称将占用相同的捕获组。
之后,使用dup名称常量,$match ['name']将包含一个值
或空字符串,但它将存在。

例如:

(?J) = PCRE_DUPNAMES
(?| ... | ...) = 分支重置

这个有效:
/(?Ji)^
(?= (?| .* end = (?P<expirationDate> .*? ); | (?P<expirationDate>)) )
(?= (?| .* start = (?P<publishDate> .*? ); | (?P<publishDate>)) )
/x

在这里尝试:http://www.ideone.com/zYd24

<?php 
$string = "start=2011-03-(start)10T13:00:00Z;end=2011-03-(end)30T13:00:00Z;scheme=W3C-DTF"; 
preg_match('/(?Ji)^
      (?= (?| .* end = (?P<expirationDate> .*? ); | (?P<expirationDate>)) )
      (?= (?| .* start = (?P<publishDate> .*? ); | (?P<publishDate>)) )
    /x', $string, $matches);
echo "Published = ",$matches['publishDate'],"\n";
echo "Expires   = ",$matches['expirationDate'],"\n"; 
print_r($matches);
?> 

输出

Published = 2011-03-(start)10T13:00:00Z
Expires   = 2011-03-(end)30T13:00:00Z
Array
(
    [0] => 
    [expirationDate] => 2011-03-(end)30T13:00:00Z
    [1] => 2011-03-(end)30T13:00:00Z
    [publishDate] => 2011-03-(start)10T13:00:00Z
    [2] => 2011-03-(start)10T13:00:00Z
)

我认为默认情况下不允许重复命名子模式,你是否尝试在PHP中使用你的正则表达式? - Josh Davis
我确认这个正则表达式在PHP中不起作用。我认为可以通过在正则表达式开头使用内部选项(?J)来启用PCRE_DUPNAMES,但我没有完全测试过。 - Josh Davis
@Josh Davis,我进行了研究,如果使用(?J),它确实可以工作,但必须正确使用,并且这意味着与(?|)一起使用分支重置。如果使用正确,它非常有价值。我的修改中包含了一个可工作的PHP版本。感谢您发现这个问题。 - user557597

0
如果相应的日期不存在时没有出现'start=;',那么Stephen Chung的代码就可以了。
否则,我认为用'*'替换'+'就足够了:
#^start=(?P<publishDate>.*?);end=(?P<expirationDate>.*?);#ix'

顺便提一下,每个代码中'?'是必要的,以使点是不贪婪的。

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