如果未捕获后的组,则在第一个捕获组后忽略空间

Posted

技术标签:

【中文标题】如果未捕获后的组,则在第一个捕获组后忽略空间【英文标题】:Ignoring space after first capturing group if the group after does not get captured 【发布时间】:2013-07-01 15:43:45 【问题描述】:

我想捕获按字母顺序指定的日期。可能是以下形式之一

2013 年 1 月 1 日 2013 年 1 月 1 日 1 月 1 日 1 月 1 日 2013 年 1 月 1 日 一月

此外,它们将出现在句子中。 例如

“我们可以在一月的某个下午见面吗?”

我在 java 中使用以下正则表达式

((?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t?|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)((\\s+)?(?<date>\\d+)?(st|nd|rd|th))?(\\s+)?,?(\\s+)?(?<year>(20)\\d\\d)?)

((?<date>\\d+)?(st|nd|rd|th)?\\s+(?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t?|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)(\\s+)?,?(?<year>(19|20)\\d\\d)?)

捕获正则表达式后,我需要指出标记在字符串中的确切位置。

当我查看 Matcher.end() 返回的索引时,我的表达式似乎也捕获了 一月之后的空间。我确实想捕获像“Jan 1st”这样的表达式,但前提是下一个捕获组匹配是可能的。

是否可以修改上面的正则表达式来做到这一点?

【问题讨论】:

乍一看,您使用 似乎做对了。您将包括 (\\s+)? 在内的整个内容包含在另一个带括号的组中,后跟 ?,因此如果 不匹配,则 (\\s+) 也不会被吸入。但是,您没有为 之间的逗号做同样的事情。尝试在 (\\s+)?,? 周围加上一组额外的括号和周围 (\\s+)?(?...) [with ?遵循新的括号],看看是否可以解决您的问题。如果没有,我会尝试更仔细地查看它。 明确一点,我的意思是改变 (\\s+)?,?到 ((\\s+?),)?,即移动最后一个 ?在您添加的新组之外。 但我注意到,即使只指定了月份,空间也会被吸入。就像“jan”一样,空间被吸入了。 我收回了。我认为一旦我将所有空间表达式更正为一个组的一部分,它就会起作用。谢谢 ajb :) 我刚刚想到的另一件事:您不需要使用像(\\s+)? 这样的模式。匹配可选一个或多个空格字符。但是匹配零个或多个空格字符的\\s* 是等效的并且更易于阅读。唯一的区别是(\\s+)?(\\s*) 如果有零个空格并且您使用Matcher.group()start()end() 查询组,则返回不同的内容,但对于大多数情况而言,这不应该是相关的。 【参考方案1】:

扩展模式以提高可读性:

(
    (?<month>
        jan(uary)?
      | feb(ruary)?
      | mar(ch)?
      | apr(il)?
      | may
      | jun(e)?
      | jul(y)?
      | aug(ust)?
      | sep(t?|tember)?
      | oct(ober)?
      | nov(ember)?
      | dec(ember)?
    )
    (
        (\\s+)?
        (?<date>\\d+)?
        (st|nd|rd|th)
    )?
    (\\s+)?
    ,?
    (\\s+)?
    (?<year>(20)\\d\\d)?
)

年份之前的空格可以匹配,即使年份不匹配。此外,即使日期不匹配,日期后缀也可以匹配。

清理和修复模式我得到了这个:

\\b
(?<month>
    jan(uary)?
  | feb(ruary)?
  | mar(ch)?
  | apr(il)?
  | may
  | jun(e)?
  | jul(y)?
  | aug(ust)?
  | sep(t|tember)?
  | oct(ober)?
  | nov(ember)?
  | dec(ember)?
)
(
    \\s*
    (?<date>\\d+)
    (st|nd|rd|th)?
)?
(
    \\s*
    ,?
    \\s*
    (?<year>(19|20)\\d\\d)
)?
\\b

我删除了外部组,因为无论如何您都将它作为组 0。 sep(t?|tember)? 中的 t? 已更改为 t。所有(\\s+)? 都更改为等效的\\s*。我将?(?&lt;date&gt;\\d+)? 移动到(st|nd|rd|th)。我将这一年放在一个组中,并将?(?&lt;year&gt;20\\d\\d) 移到那个位置。我添加了单词边界 (\\b),这样它就不会在单词中间开始或结束。

一行:

\\b(?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)(\\s*(?<date>\\d+)(st|nd|rd|th)?)?(\\s*,?\\s*(?<year>(19|20)\\d\\d))?\\b

将它与您的第二个模式结合起来:

\\b
(
    (?<month1>
        jan(uary)?
      | feb(ruary)?
      | mar(ch)?
      | apr(il)?
      | may
      | jun(e)?
      | jul(y)?
      | aug(ust)?
      | sep(t|tember)?
      | oct(ober)?
      | nov(ember)?
      | dec(ember)?
    )
    (
        \\s*
        (?<date1>\\d+)
        (st|nd|rd|th)?
    )?
  |
    (?<date2>\\d+)
    (st|nd|rd|th)?
    \\s*
    (?<month2>
        jan(uary)?
      | feb(ruary)?
      | mar(ch)?
      | apr(il)?
      | may
      | jun(e)?
      | jul(y)?
      | aug(ust)?
      | sep(t|tember)?
      | oct(ober)?
      | nov(ember)?
      | dec(ember)?
    )
)
(
    \\s*
    ,?
    \\s*
    (?<year>(19|20)\\d\\d)
)?
\\b

一行:

\\b((?<month1>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)(\\s*(?<date1>\\d+)(st|nd|rd|th)?)?|(?<date2>\\d+)(st|nd|rd|th)?\\s*(?<month2>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?))(\\s*,?\\s*(?<year>(19|20)\\d\\d))?\\b

【讨论】:

【参考方案2】:

另一个版本:

static private String month = "(?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)";
static private String suffix = "(?:st|nd|rd|th)";
static private String date = "(?<date>\\d1,2)";
static private String year = "(?<year>\\d4)";

// A month name (optionally followed by space followed by a date (optionally
// followed by a suffix or space and a comma) (optionally followed by space
// followed by a year))
static private String order1 = String.format(
        "%s(?:\\s+%s(?:%s|\\s+,)?(?:\\s+%s)?)?", month, date, suffix,
        year);

// A date followed by a suffix followed by a month (optionally followed by
// space and a comma) optionally followed by space and a year
static private String order2 = String.format(
        "%s%s\\s+%s(?:\\s+,)?(?:\\s+%s)?", date, suffix, month, year);

是的,String.format 没有太多理由,但因为它是 static,所以它在性能方面不应该是残酷的,而且它使正则表达式比我能想到的任何其他方式都更容易阅读在 Java 中。

它匹配您所有的示例模式(并获得正确的输出,IIRC),包括句子中的版本。您可能遇到的唯一问题是,它会在“让我们在 1 月 1 日见面,好吗?”形式的日期之后立即吃掉逗号,尽管如果写成“让我们在 1 月 1 日见面,好吗?”它不会匹配逗号?” (当我说“匹配逗号”时,我的意思是整个正则表达式将使用逗号,尽管命名的捕获是正确的)。我确实将年份更改为仅匹配四位数。我还将日期更改为仅匹配一位或两位数字。像@MarkusJarderot 一样,我将“september”更改为没有可选的“t”,因为整个后缀是可选的。我尝试编写这两个正则表达式,以便添加和删除逻辑块——与下面的版本进行比较,并注意我如何能够在不重写整个表达式的情况下更改它。 注意事项: 在某些情况下,两个正则表达式都会匹配(order1 仅匹配单个月份,order2 匹配“1st Jan”形式的日期)。您可能想弄清楚在这种情况下如何选择要遵循的表达式。

现在,编写这些正则表达式是为了尽量避免匹配任何不符合所提供格式的日期。我建议修改它们以允许以下形式(# 表示原始列表中的项目):

2013 年 1 月 1 日 # 2013 年 1 月 1 日 // 注意逗号 2013 年 1 月 1 日 2013 年 1 月 1 日 # 2013 年 1 月 1 日 // 注意逗号前没有空格 1 月 1 日 # 1 月 #

Jan //(已被原始示例支持)

1 月 1 日

1 月 1 日 # 2013 年 1 月 1 日 2013 年 1 月 1 日 2013 年 1 月 1 日 2013 年 1 月 1 日 2013 年 1 月 1 日 2013 年 1 月 1 日 #

此版本的代码支持上述形式。这也更好:月份已转换为使用所有非捕获模式(因此不会无缘无故地创建额外的捕获),并且我已经根据@MarkusJarderot 的回答删除了围绕整个正则表达式的捕获。日期格式的扩展数量还允许使用较少扭曲的正则表达式。这些表单引入的一个小问题是,现在v1 将尝试将“2013 年 1 月 1 日”形式的日期匹配为“1 月 20 日”,而 v2 正确匹配它们。这与我上面提到的“要小心”的问题相同;您可能想弄清楚如何决定使用哪个正则表达式更好(尝试两者并使用可能匹配更多日期片段的那个)。

static private String month = "(?<month>jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t|tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)";
static private String suffix = "(?:st|nd|rd|th)";
static private String date = "(?<date>\\d1,2)";
static private String year = "(?<year>\\d4)";

// A month name (optionally followed by space followed by a date (optionally
// followed by a suffix)(optionally followed by a comma, possibly with space
// before it)(optionally followed by space followed
// by a year))
static private String v1 = String.format(
        "%s(?:\\s+%s%s?(?:\\s*,)?(?:\\s+%s)?)?", month, date, suffix, year);

// A date (optionally followed by a suffix) followed by space followed by a
// month (optionally followed by
// a comma, possibly with space before it) optionally followed by space and
// a year
static private String v2 = String.format(
        "%s%s?\\s+%s(?:\\s*,)?(?:\\s+%s)?", date, suffix, month, year);

或者,作为没有 Java 的正则表达式(format 的输出):

(?<month>jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t|tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)(?:\s+(?<date>\d1,2)(?:st|nd|rd|th)?(?:\s*,)?(?:\s+(?<year>\d4))?)?
(?<date>\d1,2)(?:st|nd|rd|th)?\s+(?<month>jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t|tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)(?:\s*,)?(?:\s+(?<year>\d4))?

【讨论】:

感谢您的回答。我将无法在代码中执行此操作,因为我必须将它们保持在配置中。但感谢您抽出时间指出警告。 @user1411335 我已经添加了没有 Java 代码的正则表达式,以防万一。另外,我已经修复了代码块——它们显示不正确。

以上是关于如果未捕获后的组,则在第一个捕获组后忽略空间的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Xcode 11 的查找和替换中引用捕获组?

AWK:从线型访问捕获的组

在正则表达式的可选部分中捕获的组

如果路由参数无效,则在 Ktor 位置捕获异常

来自第 3 方静态库的回调中未捕获的异常

致命错误:未捕获的 ArgumentCountError