【问题标题】:Specific VBA / VBScript regex related issue (MS Access)特定的 VBA / VBScript 正则表达式相关问题(MS Access)
【发布时间】:2018-03-13 03:03:17
【问题描述】:

更新:2018 年 3 月 12 日下午 1:44 CST

创建一个名为http://vbfiddle.net 的网站以在浏览器中实施和测试@ctwheel 的VBScript 解决方案(MS IE 10+ 安全设置为“中”,该网站上的说明如何设置它以供您使用如果你想要 - 从 jsfiddle.net 的此链接获取代码以复制并粘贴到 vbfiddle.net:https://jsfiddle.net/hcwjhmg9/ [vbfiddle.net 目前没有“保存”功能]),我发现@ctwheel 的 VBScript RegEx 运行成功,即使是我给出的第三个示例行,但是当 @ctwheel 的 VBScript RegEx 用于 Microsoft Access 2016 的 VBA 的 VBScript 中针对从具有“相同”值的数据库读取的记录时,对于我给出的第三个示例行,第三个子组只返回“Ray”,它应该像在 vbfiddle.net 中一样返回“Ray, CFP”。

我终于想到遍历数据库返回的字符串值的每个字符(在 Microsoft Access 中的 VBA 中),并将其与我直接键入的视觉等效字符串值的每个字符的迭代进行比较代码(在 Microsoft Access 中的 VBA 中)。我得到以下结果:

First Name and Last Name: "G.L. (Pete) Ray, CFP"
--- 1st Text chars: "71 46 76 46 32 40 80 101 116 101 41 32 82 97 121 44" 
(Read value from database, appears same as below when Debug.Print is called on it)
--- 2nd Text chars: "71 46 76 46 32 40 80 101 116 101 41 32 82 97 121 44 32 67 70 80" (Typed by keyboard into a string within the code)
'G.L. (Pete) Ray,'
    strProperName>objSubMatch: "G.L."
    strProperName>objSubMatch: "Pete"
    strProperName>objSubMatch: "Ray,"
 Matching record(s): 3 of 1132 record(s).

我正在运行的 RegEx 针对“1st Text Chars”示例运行,并为先前给出的第三个示例行的第三个子组返回“Ray”:“G.L. (Pete) Ray, CFP”。但是,如果我针对第 2 个直接输入代码的“2nd Text chars”示例运行 RegEx,则第 3 个子组返回“Ray,CFP”,如 Microsoft Access 2016 的 VBA 中所预期的那样。

我现在正在使用@ctwheels 提供的正则表达式:

^([^(]+?)\s*\(\s*([^)]*?)\s*\)\s*(.*)

有人能解释一下这里发生了什么吗? 1)为什么从数据库返回的字符与使用键盘通过视觉读取和复制输入字符串返回的字符不同? 2)当直接从数据库中读取值时,如何使适用于“1st Text Chars”字符/字符串序列的正则表达式返回正确的第三个子组:“Ray, CFP”?


原始问题(以上更新问题):

我在使用带有正则表达式引擎的 Microsoft Access 2016 的 VBA 中遇到问题,我相信 5.5 for VBScript。

这是我目前使用的正则表达式:

"(.*)\((.*)(\))(.*)"

我正在尝试解析字符串(分别在每个新行上):

Lawrence N. (Larry) Solomon
James ( Jim ) Alleman
G.L. (Pete) Ray, CFP

进入:

"Lawrence N.", "Larry", ")", "Solomon"
"James", "Jim", ")", "Alleman"
"G.L.", "Pete", ")", "Ray, CFP"

或者(最好)进入:

"Lawrence N.", "Larry", "Solomon"
"James", "Jim", "Alleman"
"G.L.", "Pete", "Ray, CFP"

引号内用逗号分隔的部分是子匹配中返回的部分(不带引号)

我正在使用以下代码:

           ' For Proper Name (strProperName):
            With objRegex
                .Global = False
                .MultiLine = False
                .IgnoreCase = True
                .Pattern = "(.*)\((.*)(\))(.*)"

                    '([\s|\S]*) work around to match every character?

                        '".*\(([^\s]*)[\s]*\(" '_
                        ''& "[\"
                        '[\(][\s]*([.|^\w]*)[\s]*\)"
                    ' "[\s]*(.*)[\s]*\("
                        ' does same as below except matches any or no whitespace preceding any characters,
                        ' and returns the following characters up to an opening parenthesis ("(") but excluding it,
                        ' as the first subgroup
                    ' "(.*)[\s]*\("
                        ' does same as below except matches any whitespace or no whitespace at all followed by an opening parenthesis ("(")
                        ' and returns the preceding characters as the first subgroup
                    ' "(.*)\("
                        ' matches all characters in a row that end with an open parenthesis, and returns all of these characters in a row
                        ' excluding the following open parenthesis as the first subgroup
                    ' "(.*?\(\s)"
                    ' "[^\(]*"
                        ' this pattern returns every character that isn't an opening parenthesis ("("), and when
                        ' it matches an open parenthesis, it does not return it or any characters after it
                    ' "[\(\s](.*)\)"
                        ' this pattern extracts everything between parenthesis in a line as its first submatch
                    ' "(?<=\().*"
                    ' "[^[^\(]*][.*]"
                    ' "(\(.*?\))"
                    ' "(\(.*?\))*([^\(].*[^\)])"
            End With

            If objRegex.Test(strFirstNameTrimmed) Then
                'Set strsMatches = objRegex.Execute(rs.Fields("First Name"))
                Set strsMatches = objRegex.Execute(strFirstNameTrimmed)



                Debug.Print "2:'" & strsMatches(0).Value & "'"

                If strsMatches(0).SubMatches.Count > 0 Then

                    For Each objSubMatch In strsMatches(0).SubMatches

                        Debug.Print "    strProperName>objSubMatch: """ & objSubMatch & """" 'Result: 000, 643, 888"

                        strProperName = objSubMatch

                    Next objSubMatch

                End If
            Else
                strProperName = "*Not Matched*"
            End If

在调试窗口/“立即窗口”中产生以下输出,正如它在 VBA 中已知的那样,由 (Ctrl+G) 调出:

------------------------
First Name and Last Name: "Lawrence N. (Larry) Solomon"
2:'Lawrence N. (Larry)'
    strProperName>objSubMatch: "Lawrence N. "
    strProperName>objSubMatch: "Larry"
    strProperName>objSubMatch: ")"
    strProperName>objSubMatch: ""
Extracted Nick Name: "Larry"
Extracted Proper Name: ""
First Name and Last Name: "James ( Jim ) Alleman"
2:'James ( Jim )'
    strProperName>objSubMatch: "James "
    strProperName>objSubMatch: " Jim "
    strProperName>objSubMatch: ")"
    strProperName>objSubMatch: ""
Extracted Nick Name: "Jim"
Extracted Proper Name: ""
First Name and Last Name: "G.L. (Pete) Ray, CFP"
2:'G.L. (Pete) Ray,'
    strProperName>objSubMatch: "G.L. "
    strProperName>objSubMatch: "Pete"
    strProperName>objSubMatch: ")"
    strProperName>objSubMatch: " Ray,"
Extracted Nick Name: "Pete"
Extracted Proper Name: " Ray,"
Matching record(s): 3 of 1132 record(s).

【问题讨论】:

    标签: regex vba debugging vbscript


    【解决方案1】:

    See regex in use here

    ^([^(]+?)\s*\(\s*([^)]*?)\s*\)\s*(.*)
    
    • ^ 在行首断言位置
    • ([^(]+?) 将除( 之外的任何字符捕获一次或多次,但尽可能少,进入捕获组 1
    • \s* 匹配任意数量的空白字符
    • \( 匹配 ( 字面意思
    • \s* 匹配任意数量的空白字符
    • ([^)]*?) 捕获除) 之外的任何字符一次或多次,但尽可能少,进入捕获组 2
    • \s* 匹配任意数量的空白字符
    • \( 匹配 ( 字面意思
    • \s* 匹配任意数量的空白字符
    • (.*) 将其余行捕获到捕获组 3

    结果:

    ["Lawrence N.", "Larry", "Solomon"]
    ["James", "Jim", "Alleman"]
    ["G.L.", "Pete", "Ray, CFP"]
    

    【讨论】:

    • 非常感谢您抽出宝贵时间。我现在正在审查这个。
    • 在 VBA 中通过 Microsoft Access 2016,使用“Microsoft VBScript Regular Expressions 5.5”作为引用库(通过菜单:[Tools] > [References...])并选择“Microsoft VBScript Regular Expressions 5.5 " 排他地,而不是 1.0.查看下一条评论...
    • “Ray”被捕获为输入 3 的子组 3,而不是“Ray,CFP”......这实际上是我问这个问题的主要原因,因为我想捕获“Ray, CFP”完全在 VBA 中,引擎 5.5 通过 MS Access 2016。
    • 另外,感谢您提供“正则表达式 101”在线正则表达式评估器的精彩资源。为了把这个想法付诸实践,如果这个工具或类似工具自动生成给定 RegEx 的行项目分解,就像你给出的分解一样,那不是很酷吗?此外,如果这个工具或类似工具能在各种条件下使用各种引擎显示各种语言的结果,并且可能一次运行它们,那就太好了。
    • ctwheels @ctwheels 我会尝试将代码放入 rextester.com/l/vb 。请在中部标准时间下午 3:50 之前给我。
    【解决方案2】:

    你应该能够避免使用正则表达式,如果那是你的事。

    我对测试数据做了一些假设,昵称包含在“()”中。除此之外,我希望代码应该简单明了。如果没有,请随时提出问题。还包括一个名为Test 的测试例程。

    Public Function ParseString(InputString As String) As String
        On Error GoTo ErrorHandler:
    
        Dim OutputArray   As Variant
        Const DoubleQuote As String = """"
    
        'Quick exit, if () aren't found, then just return original text
        If InStr(1, InputString, "(") = 0 Or InStr(1, InputString, ")") = 0 Then
            ParseString = InputString
            Exit Function
        End If
    
        'Replace the ) with (, then do a split
        OutputArray = Split(Replace(InputString, ")", "("), "(")
    
        'Check the array bounds and output accordingly
        'If there can only ever be 3 (0 - 2) elements, then you can change this if statement
        If UBound(OutputArray) = 2 Then
            ParseString = DoubleQuote & Trim$(OutputArray(0)) & DoubleQuote & ", " & _
                          DoubleQuote & Trim$(OutputArray(1)) & DoubleQuote & ", " & _
                          DoubleQuote & Trim$(OutputArray(2)) & DoubleQuote
        ElseIf UBound(OutputArray) = 1 Then
            ParseString = DoubleQuote & Trim$(OutputArray(0)) & DoubleQuote & ", " & _
                          DoubleQuote & Trim$(OutputArray(1)) & DoubleQuote
        Else
            ParseString = DoubleQuote & Trim$(OutputArray(LBound(OutputArray))) & DoubleQuote
        End If
    
    CleanExit:
        Exit Function
    
    ErrorHandler:
        ParseString = InputString
        Resume CleanExit
    End Function
    
    Sub Test()
        Dim Arr() As Variant: Arr = Array("Lawrence N. (Larry) Solomon", "James ( Jim ) Alleman", "G.L. (Pete) Ray, CFP")
    
        For i = LBound(Arr) To UBound(Arr)
            Debug.Print ParseString(CStr(Arr(i)))
        Next
    End Sub
    

    结果

    "Lawrence N.", "Larry", "Solomon"
    "James", "Jim", "Alleman"
    "G.L.", "Pete", "Ray, CFP"
    

    【讨论】:

    • 是的,我考虑过使用 split 的方法,但我认为在 ")" 和 "(" 上分别拆分字符串两次,然后重新组合生成的字符串有点复杂。我喜欢你的方法,使用替换 - 非常优雅。如果我可以接受两个答案,我会接受你的答案和一个通过 RegEx 的答案。如果具有 RegEx 答案的受访者特别满足我对 VBScript RegEx 的担忧(他的 RegEx 不处理我希望为 VBScript RegEx 解决的最困难和主要的案例),那么我将授予您接受的答案。如果他在接下来的时间内解决了它...
    • ... 3 天(我创建了一个类似于 jsfiddle.net 的在线 VBScript 网页,我将其称为 vbfiddle.net,这应该允许他让他的 RegEx 在 VBScript 5.5 中工作)然后我将授予他接受的答案,并为你投票。非常感谢您的时间和精力。
    • 另外,我正在尝试学习 RegEx 及其不同风格的怪癖,因为 RegEx 在软件开发中对我来说是一个弱点,并且有许多(更复杂的)用例非常了解 RegEx好会很有价值。
    • @blueuser 是的,我听说你是用 Regex 的。它们肯定是一个强大的工具,但可能会被过度使用。有时很难知道何时使用正则表达式,而不是手动处理某些东西。如果可以像这样编写一个小函数,我会倾向于避免使用 Regex。
    • Ryan,@ctwheels 的答案在我创建 vbfiddle.net 并针对我提供的用例测试他的解决方案时有效。如果您想在运行 VBScript 的 MS IE 10+ 中自己尝试他的解决方案,请参阅他提出的解决方案的最后一个 cmets。但是,在 VBA 或 Microsoft Access 2016 使用的 RegEx 引擎 5.5 中似乎有一个很大的问题,这导致他的 RegEx 在该环境中运行时,只为第 3 行测试数据的第 3 个子组返回“Ray”,即使它应该为第三个子组返回“Ray,CFP”,并且在使用 vbfiddle.net 时这样做。
    【解决方案3】:

    正则表达式\s*[()]\s*

    详情:

    • \s* 匹配任何空白字符零次和无限次
    • [()] 匹配列表中存在的单个字符 ()

    VBA 代码

    Dim str As String
    str = "Lawrence N. (Larry) Solomon"
    
    Set re = CreateObject("VBScript.RegExp")
    re.Global = True
    re.Pattern = "\s*[()]\s*"
    re.MultiLine = True
    
    Dim arr As Variant
    arr = Strings.Split(re.Replace(str, vbNullChar), vbNullChar)
    
    For Each Match In arr
        Debug.Print (Match)
    Next
    

    输出:

    Lawrence N.
    Larry
    Solomon
    

    【讨论】:

    • 第 3 行/示例失败。
    • ... 在 Microsoft Access 2016 VBA VbScript,RegEx 引擎我相信 5.5。
    猜你喜欢
    • 2011-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多