VBScript正则表达式即使不需要匹配也可以填充子匹配项

3
我试图复制Google日历从叙述中创建约会的方法。我想输入“5pm Happy Hour for 1 hour”并将其解析为最终的Outlook AppointmentItem。
我的问题是,我认为末尾有一大块可选文本。因为它是可选的,所以正则表达式通过了,但子匹配没有被填充,因为它不是匹配的必需部分。我希望它能被填充,因为我想使用子匹配作为我的解析引擎。
我在列A中有许多测试用例(在Excel中工作,然后将其移动到Outlook),我的代码列出了右侧的子匹配。这是潜在输入的代表性样本。
1. 5pmCST Happy Hour for 1 hour
2. 5pm CST Happy Hour for 1 hour
3. 5pm Happy Hour for 1 hour
4. 5 pm Happy Hour for 1 hour
5. 5 pm CST Happy Hour for 1 hour
6. 5 Happy Hour for 1 hour
7. 5 Happy Hour
8. 5pmCST Happy Hour
9. 5pm CST Happy Hour
10. 5pm Happy Hour
11. 5:00CST Happy Hour for 1 hour
12. 5:00 CST Happy Hour for 1 hour

以下是运行测试的代码:
Sub testest()

    Dim RegEx As VBScript_RegExp_55.RegExp
    Dim Matches As VBScript_RegExp_55.MatchCollection
    Dim Match As VBScript_RegExp_55.Match
    Dim rCell As Range
    Dim SubMatch As Variant
    Dim lCnt As Long
    Dim aPattern(1 To 8) As String

    Set RegEx = New VBScript_RegExp_55.RegExp
    aPattern(1) = "(1?[0-9](:[0-5][0-9])?)" 'time
    aPattern(2) = "( ?)" 'optional space
    aPattern(3) = "([ap]m)?" 'optional ampm
    aPattern(4) = "( ?)" 'optional space
    aPattern(5) = "([ECMP][DS]T)?" 'optional time zone
    aPattern(6) = "( ?)" 'optional space
    aPattern(7) = "(.+?)" 'event description
    aPattern(8) = "(( for )([1-2]?[0-9](.[0-9]?[0-9])?)( hours?))?" 'optional duration

    RegEx.Pattern = Join(aPattern, vbNullString)
    Debug.Print RegEx.Pattern

    Sheet1.Range("C1").Resize(1000, 100).ClearContents

    For Each rCell In Sheet1.Range("A1").CurrentRegion.Columns(1).Cells
        lCnt = 0
        rCell.Offset(0, 2).Value = RegEx.test(rCell.Text)
        If RegEx.test(rCell.Text) Then
            Set Matches = RegEx.Execute(rCell.Text)

            For Each Match In Matches
                For Each SubMatch In Match.SubMatches
                    lCnt = lCnt + 1
                    rCell.Offset(0, 2 + lCnt).Value = SubMatch
                Next SubMatch
            Next Match
        End If
    Next rCell

End Sub

这个模式是:
(1?[0-9](:[0-5][0-9])?)( ?)([ap]m)?( ?)([ECMP][DS]T)?( ?)(.+?)(( for )([1-2]?[0-9](.[0-9]?[0-9])?)( hours?))?

#1的子匹配项为:
1        2          3        4      5       6       7
5                   pm              CST             H

在“Happy Hour”的“H”处停止匹配,因为从“for”开始的所有内容都是可选的。如果我去掉可选部分,我的模式就变成了

(1?[0-9](:[0-5][0-9])?)( ?)([ap]m)?( ?)([ECMP][DS]T)?( ?)(.+?)( for )([1-2]?[0-9](.[0-9]?[0-9])?)( hours?)

但是#7-#10没有通过,因为它们没有持续时间。不过#1的子匹配符合我的要求。

1     2     3     4     5     6     7             8     9     10     11
5           pm          CST         Happy Hour     for  1            hour

我希望每个可能的子匹配都能填充,即使VBScript不需要它也能使正则表达式通过。我担心这就是它的工作原理,而我正在试图让正则表达式为我完成解析工作。我考虑运行它通过越来越严格的模式,直到它无法通过,然后使用最后一个通过的模式,但这似乎很笨拙。
是否有可能让正则表达式填充这些子匹配?

虽然您可以使用正则表达式来解决这个特定问题,但我认为正则表达式并不是复制Google日历行为的解决方案。如果您想要这样做,您需要将字符串拆分成原子列表,获取每个原子的“组”类型,并获取这些原子组合的上下文。使用正则表达式方法,您将难以处理其他格式,例如_Happy Hour from 17 to 1800_。 - AutomatedChaos
那是我的第一种方法,看起来代码很多。我不太擅长正则表达式,认为这可能会简化代码。感谢您的反馈。 - Dick Kusleika
1个回答

2

我假设每行都是单元格中的所有内容。因此,我可以使用锚点。

我认为您不需要那么多捕获组。我使用以下正则表达式进行设置:

Group 1        Time
Group 2        am/pm
Group 3        Time Zone
Group 4        Description
Group 5        Hours (and fractions of hours)

使用A2:An中的数据,以下程序将数据解析到相邻列中。如果Submatch“未填充”,也没有关系。您还可以填充数组中的元素或进行其他操作。如果需要更多的子匹配项,您始终可以添加捕获组来捕获可选空格,或者将相关的非捕获组更改为捕获组。

此外,由于“for”是可选的,因此我选择使用前瞻来确定“description”的结尾。“description”将以\s + for \ s +序列或“行尾”结束。由于我假设每个单元格只有一个条目和一行,因此多行和全局属性无关紧要。

必须在“for”之前和之后包含空格,以避免如果Description中包含该序列而出现问题。

Option Explicit
'set Reference to Microsoft VBScript Regular Expressions 5.5
Sub ParseAppt()
    Dim R As Range, C As Range
    Dim RE As RegExp, MC As MatchCollection
    Dim I As Long
Set R = Range("a2", Cells(Rows.Count, "A").End(xlUp))
Set RE = New RegExp
With RE
    .Pattern = "((?:1[0-2]|0?[1-9])(?::[0-5]\d)?)\s*([ap]m)?\s*([ECMT][DS]T)?\s*(.*?(?=\s+for\s+|$))(?:\s+for\s+(\d+(?:\.\d+)?)\s*hour)?"
    .IgnoreCase = True
    For Each C In R
        If .Test(C.Text) = True Then
            Set MC = .Execute(C.Text)
            For I = 0 To 4
                C.Offset(0, I + 1) = MC(0).SubMatches(I)
            Next I
        End If
    Next C
End With
End Sub

谢谢Ron。它没有获取“12 pm午餐1小时”的整个时间 - 它只获取时间中的1。我将研究模式意味着什么,并可能在此过程中学到一些东西。我创建了所有这些捕获,以确保我在可预测的子匹配中拥有可预测的信息,但是你的已经做到了这一点,所以希望一旦我理解了模式,我就能够掌握它。 - Dick Kusleika
@DickKusleika 没错。修复问题只需要反转交替顺序,这样正则表达式就会先查找10-12小时,然后再查找带有可选前导零的早些小时。我会在我的答案中进行更改。 - Ron Rosenfeld
很好。我现在正在学习正则表达式,并且正在逐步掌握它。由于某种原因,a|pm 匹配的是 a 或 pm 而不是 a 或 p 然后 m。我已经在你的答案中进行了编辑,请检查我的修改。 - Dick Kusleika
你的编辑很好。顺便说一下,有一个相对便宜的程序——RegexBuddy——我强烈推荐用于这些目的。 - Ron Rosenfeld

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