【问题标题】:Match Methods with References to Global Variables匹配引用全局变量的方法
【发布时间】:2013-05-28 18:19:30
【问题描述】:

这个问题与this one 密切相关,但它与包含对全局变量(未注释掉)的引用的抓取方法有关。

我正在使用以下正则表达式和测试字符串来检查它是否有效,但它只是部分有效:

正则表达式

^((?:(?:Public|Private)\s+)?(?:Function|Sub).+)[\s\S]+?(GLOBAL_VARIABLE_1)[\s\S]+?End\s+(?:Function|Sub)$

(我需要以这种方式与捕获组一起使用正则表达式的一部分,以便我可以获取方法的名称作为子匹配项)。

测试字符串

'-----------------------------------------------------------------------------------------
'
'   the code:   Header
'
'-----------------------------------------------------------------------------------------

Dim GLOBAL_VARIABLE_1
Dim GLOBAL_VARIABLE_2
Dim GLOBAL_VARIABLE_3

Public Function doThis(byVal xml)
'' Created               : dd/mm/yyyy
'' Return                : string
'' Param            : xml- an xml blob

     return = replace(xml, "><", ">" & vbLf & "<")

     GLOBAL_VARIABLE_1 = 2 + 2

     doThis = return

End Function


msgbox GLOBAL_VARIABLE_1



Public Function doThat(byVal xPath)
'' Created               : dd/mm/yyyy
'' Return                : array
' 'Param            : xPath

     return = split(mid(xPath, 2), "/")

     GLOBAL_VARIABLE_2 = 2 + 2


     doThat = return

End Function


GLOBAL_VARIABLE_2 = 2 + 2


Public Sub butDontDoThis()
'' Created               : dd/mm/yyyy
'' Return                : string
' 'Param            : obj

     For i = 0 To 5
          return = return & "bye" & " "

     Next

End Sub


GLOBAL_VARIABLE_3 = 3 + 3


Public Sub alsoDoThis(byRef obj)
'' Created               : dd/mm/yyyy
'' Return                : string
' 'Param            : obj, an xml document object

     For i = 0 To 4
          return = return & "hi" & " "

     Next

     GLOBAL_VARIABLE_1 = 1 + 1

End Sub


GLOBAL_VARIABLE_3 = 3 + 3

使用http://www.regexpal.com/,我可以突出显示引用全局变量的第一个方法。但是,正则表达式并没有像我期望的那样使用其他方法。正则表达式还会选择其他没有引用特定全局变量的方法,并以最后一个实际使用全局变量的方法结束。我已经确定问题是 [\s\S]+?(GLOBAL_VARIABLE_1)[\s\S]+?End\s+(?:Function|Sub)$ 部分正在进行最小/非贪婪匹配,因此它会一直查找直到找到实际匹配。

总而言之,表达式应遵循以下规则:

  • 在看到方法声明的first结尾时停止扫描当前正在检查的方法。在这个例子中,只有doThisalsoDoThis 方法应该与GLOBAL_VARIABLE_1 匹配,但我不确定正则表达式应该是什么。
  • 正则表达式也应该只匹配实际使用全局变量的方法
  • 如果GLOBAL_VARIABLE_1 被注释掉,那么它确实没有被该方法使用。已评论的 GLOBAL_VARIABLE_1 不应触发对该方法的肯定匹配。

【问题讨论】:

  • 您想要的具有单个正则表达式的解决方案不容易实现,并且会产生难以维护的表达式。同样,像这样的复杂表达式可能会运行得更慢,然后简单地将逻辑包装在两个单独的正则表达式周围。这是正则表达式不是解析 HTML 等字符串的解决方案的原因之一。如果您真的对单个表达式感兴趣,那么建议您提交此内容并获得赏金,这需要您在 Stack Overflow 上获得 250 分。

标签: regex vbscript non-greedy


【解决方案1】:

说明

我将分两步执行此操作,首先确定您的每个函数和子项。在这里,我使用参考 \1 来确保我们匹配正确的结束函数或结束子。此正则表达式还获取函数名称并将其放入第 2 组。如果第 2 部分正确,则可以稍后使用

(?:Public|Private)\s+(Function|Sub)\s+([a-z0-9]*).*?End\s+\1

然后测试它们中的每一个以查看它们是否包含您的变量,请注意在此测试中我使用多行匹配来确保注释字符不会出现在同一行的Global_Variable 之前。这还检查GLOBAL_VARIABLE_1 前面没有以下任何内容

  • 带有或不带有_ 分隔符的字母数字。这需要使用您可能在变量名中找到的所有字符进行更新。在此处包含连字符 - 可能会与等式中使用的减号混淆。
  • 评论字符'

^[^']*?(?![a-z0-9][_]?|['])\bGLOBAL_VARIABLE_1

VB 第 1 部分

Imports System.Text.RegularExpressions
Module Module1
  Sub Main()
    Dim sourcestring as String = "replace with your source string"
    Dim re As Regex = New Regex("(?:Public|Private)\s+(Function|Sub)\s+([a-z0-9]*).*?End\s+\1",RegexOptions.IgnoreCase OR RegexOptions.Singleline)
    Dim mc as MatchCollection = re.Matches(sourcestring)
    Dim mIdx as Integer = 0
    For each m as Match in mc
      For groupIdx As Integer = 0 To m.Groups.Count - 1
        Console.WriteLine("[{0}][{1}] = {2}", mIdx, re.GetGroupNames(groupIdx), m.Groups(groupIdx).Value)
      Next
      mIdx=mIdx+1
    Next
  End Sub
End Module

$matches Array:
(
    [0] => Array
        (
            [0] => Public Function doThis(byVal xml)
'' Created               : dd/mm/yyyy
'' Return                : string
'' Param            : xml- an xml blob

     return = replace(xml, "><", ">" & vbLf & "<")

     GLOBAL_VARIABLE_1 = 2 + 2

     doThis = return

End Function
            [1] => Public Function doThat(byVal xPath)
'' Created               : dd/mm/yyyy
'' Return                : array
' 'Param            : xPath

     return = split(mid(xPath, 2), "/")

     GLOBAL_VARIABLE_2 = 2 + 2


     doThat = return

End Function
            [2] => Public Sub butDontDoThis()
'' Created               : dd/mm/yyyy
'' Return                : string
' 'Param            : obj

     For i = 0 To 5
          return = return & "bye" & " "

     Next

End Sub
            [3] => Public Sub alsoDoThis(byRef obj)
'' Created               : dd/mm/yyyy
'' Return                : string
' 'Param            : obj, an xml document object

     For i = 0 To 4
          return = return & "hi" & " "

     Next

     GLOBAL_VARIABLE_1 = 1 + 1

End Sub
        )

    [1] => Array
        (
            [0] => Function
            [1] => Function
            [2] => Sub
            [3] => Sub
        )

    [2] => Array
        (
            [0] => doThis
            [1] => doThat
            [2] => butDontDoThis
            [3] => alsoDoThis
        )

)

VB 第 2 部分

在本文中找到

Public Function doThis(byVal xml)
'' Created               : dd/mm/yyyy
'' Return                : string
'' Param            : xml- an xml blob

     return = replace(xml, "><", ">" & vbLf & "<")

     GLOBAL_VARIABLE_1 = 2 + 2

     doThis = return

End Function

例子

Imports System.Text.RegularExpressions
Module Module1
  Sub Main()
    Dim sourcestring as String = "replace with your source string"
    Dim re As Regex = New Regex("^[^']*?GLOBAL_VARIABLE_1",RegexOptions.IgnoreCase OR RegexOptions.Multiline)
    Dim mc as MatchCollection = re.Matches(sourcestring)
    Dim mIdx as Integer = 0
    For each m as Match in mc
      For groupIdx As Integer = 0 To m.Groups.Count - 1
        Console.WriteLine("[{0}][{1}] = {2}", mIdx, re.GetGroupNames(groupIdx), m.Groups(groupIdx).Value)
      Next
      mIdx=mIdx+1
    Next
  End Sub
End Module

$matches Array:
(
    [0] => Array
        (
            [0] =>  Param            : xml- an xml blob

     return = replace(xml, "><", ">" & vbLf & "<")

     GLOBAL_VARIABLE_1
        )

)

在此文本中找不到

Public Function doThis(byVal xml)
'' Created               : dd/mm/yyyy
'' Return                : string
'' Param            : xml- an xml blob

     return = replace(xml, "><", ">" & vbLf & "<")

  '   GLOBAL_VARIABLE_1 = 2 + 2

     doThis = return

End Function

例子

Imports System.Text.RegularExpressions
Module Module1
  Sub Main()
    Dim sourcestring as String = "replace with your source string"
    Dim re As Regex = New Regex("^[^']*?GLOBAL_VARIABLE_1",RegexOptions.IgnoreCase OR RegexOptions.Multiline)
    Dim mc as MatchCollection = re.Matches(sourcestring)
    Dim mIdx as Integer = 0
    For each m as Match in mc
      For groupIdx As Integer = 0 To m.Groups.Count - 1
        Console.WriteLine("[{0}][{1}] = {2}", mIdx, re.GetGroupNames(groupIdx), m.Groups(groupIdx).Value)
      Next
      mIdx=mIdx+1
    Next
  End Sub
End Module

Matches Found:
NO MATCHES.

在本文中也没有找到

Public Sub butDontDoThis()
'' Created               : dd/mm/yyyy
'' Return                : string
' 'Param            : obj

     For i = 0 To 5
          return = return & "bye" & " "

     Next

End Sub

例子

   Imports System.Text.RegularExpressions
    Module Module1
      Sub Main()
        Dim sourcestring as String = "Public Sub butDontDoThis()
    '' Created               : dd/mm/yyyy
     '' Return                : string
     ' 'Param            : obj

     For i = 0 To 5
          return = return & ""bye"" & "" ""

     Next

End Sub"
        Dim re As Regex = New Regex("^[^']*?GLOBAL_VARIABLE_1",RegexOptions.IgnoreCase OR RegexOptions.Multiline)
        Dim mc as MatchCollection = re.Matches(sourcestring)
        Dim mIdx as Integer = 0
        For each m as Match in mc
          For groupIdx As Integer = 0 To m.Groups.Count - 1
            Console.WriteLine("[{0}][{1}] = {2}", mIdx, re.GetGroupNames(groupIdx), m.Groups(groupIdx).Value)
          Next
          mIdx=mIdx+1
        Next
      End Sub
    End Module

    Matches Found:
    NO MATCHES.

免责声明

有很多边缘情况可能会导致这种情况发生,例如,如果您有 ' end function 的评论或者如果您将字符串值分配给像 thisstring = "end sub" 这样的变量

是的,我意识到 OP 是针对 VBscript 的,我已经包含了这些示例来演示整体逻辑以及正则表达式的工作原理。

【讨论】:

  • 问题是关于 VBScript,而不是关于 VB。
  • 我使用 VB 来展示整体逻辑,并且正则表达式按描述工作。
  • @Denomales 这有点帮助,但我正在寻找一种方法来优化收集包含对全局变量的引用的方法名称。我已经从逐行扫描(这需要非常长的时间)到将方法块作为一个字符串进行扫描。我认为如果我有一个只返回包含对全局变量的引用的方法名称的正则表达式会快得多。
  • 我更改了第 1 部分的正则表达式以捕获函数/子名称,以便您可以遍历第 1 部分中的所有匹配项,并提供第 2 部分的测试通过,然后您可以轻松地从第 1 部分中提取名称数组。
  • 第二个正则表达式存在问题,只要名称以我要查找的字符串结尾,名称相似的全局变量(但不完全相同)仍然匹配。例如,正则表达式^[^']*?GLOBAL_VARIABLE_1 也会找到全局变量ABCD_GLOBAL_VARIABLE_1,但我只想让它匹配GLOBAL_VARIABLE_1,只要它没有被注释掉。
【解决方案2】:

找到罪魁祸首。该问题是由正则表达式的突出显示部分引起的:

((?:(?:Public|Private)\s+)?(?:Function|Sub).+)<b>[\s\S]+?(GLOBAL_VARIABLE_1)</b>[\s\S]+?End\s+(?:Function|Sub)

[\s\S]+? 是非贪婪匹配,但这并不一定意味着它是最短匹配。简化示例:

Public Function doThis(byVal xml)
  GLOBAL_VARIABLE_1
End Function

Public Function doThat(byVal xPath)
  GLOBAL_VARIABLE_2
End Function

Public Sub butDontDoThis()
  GLOBAL_VARIABLE_3
End Sub

Public Sub alsoDoThis(byRef obj)
  GLOBAL_VARIABLE_1
End Sub

当正则表达式应用于示例文本时,它首先匹配第一个函数(标记为粗体文本的组):

Public Function doThis(byVal xml)
  GLOBAL_VARIABLE_1
End Function

但是,在匹配之后,表达式的第一部分 (((?:(?:Public|Private)\s+)?(?:Function|Sub).+)) 匹配下一个函数定义 (Public Function doThat(byVal xPath)),然后 [\s\S]+?(GLOBAL_VARIABLE_1) 匹配 所有 文本,直到 GLOBAL_VARIABLE_1的>下一次出现

Public Function doThat(byVal xPath)
  GLOBAL_VARIABLE_2
End Function

Public Sub butDontDoThis()
  GLOBAL_VARIABLE_3
End Sub

Public Sub alsoDoThis(byRef obj)
  GLOBAL_VARIABLE_1
End Sub

[\s\S]+? 中没有隐含的“不包含 End Function”。

解决您的问题的最简单方法可能是正则表达式和字符串匹配的组合:

Set fso = CreateObject("Scripting.FileSystemObject")
text = fso.OpenTextFile("C:\Temp\sample.txt").ReadAll

Set re = New RegExp
re.Pattern = "((?:(?:Public|Private)\s+)(Function|Sub).+)([\s\S]+?)End\s+\2"
re.Global  = True
re.IgnoreCase = True

For Each m In re.Execute(text)
  If InStr(m.SubMatches(2), "GLOBAL_VARIABLE_1") > 0 Then
    WScript.Echo m.SubMatches(0)
  End If
Next

它提取每个过程/函数的主体 (SubMatches(2)),然后使用 InStr() 检查主体是否包含 GLOBAL_VARIABLE_1

【讨论】:

  • 我已经将RegExp 对象中的Global 属性用于True,我仍然得到相同的结果。
  • 您是否从表达式中删除了^$
  • 该页面检查的是 JavaScript 正则表达式,而不是 VBScript 正则表达式。我在 VBScript 中针对您的测试字符串运行了您的表达式(以及我建议的修改),它产生了所需的 2 个(子)匹配项。
  • 这仍然没有帮助;它正在抓取所有没有全局变量的方法,并且匹配集中的最后一个方法。我只想要包含我正在寻找的全局变量的方法。
  • 我在这个例子中寻找的2个(子)匹配是Public Function doThis(byVal xml)Public Sub alsoDoThis(byRef obj),因为这些方法引用GLOBAL_VARIABLE_1。但是,我正在使用的当前正则表达式 (^((?:(?:Public|Private)\s+)(?:Function|Sub).+)[\s\S]+?(GLOBAL_VARIABLE_1)[\s\S]+?End\s+(?:Function|Sub)$) 在第二个匹配项中包含其他方法。这是因为非贪婪的正则表达式最终超出了它为当前函数看到的第一个 End (Sub|Function) 到具有 GLOBAL_VARIABLE_1End (Sub|Function) 的任何其他文本
【解决方案3】:

说明

此正则表达式会将文本分解为字符串,其中每个字符串包含一个函数或子函数。它还将通过在函数内查找在所需GLOBAL_VARIABLE_1 值之前没有' 的第一行代码来验证字符串是否具有未注释的GLOBAL_VARIABLE_1 .如果 ' 嵌入在像 variable = "sometext ' more text" + GLOBAL_VARIABLE_1 这样的双引号字符串中,则该表达式还将将 ' 作为常规字符处理

(?:Public|Private)\s+(Function|Sub)\s+([a-z0-9]*)(?:(?!^End\s+\1\s+(?:$|\Z)).)*^(?:[^'\r\n]|"[^"\r\n]*")*GLOBAL_VARIABLE_1.*?^End\s\1\b

组 0 将包含整个匹配的函数/子

  1. 将相应地包含functionsub
  2. 将包含函数/子的名称

示例

输入文字

Public Function ValidEdgeCase1(byRef obj)
  SomeVariable = "some text with an embedded ' single quote" + GLOBAL_VARIABLE_1
End Sub

Public Sub SkipEdgeCase(byRef obj)
  SomeVariable = "some text with an embedded ' single quote" ' + GLOBAL_VARIABLE_1
End Sub

Public Function FailCommented(byVal xml)
'  GLOBAL_VARIABLE_1
End Function

Public Function FAilWrongName1(byVal xPath)
  GLOBAL_VARIABLE_2
End Function

Public Sub FAilWrongName1()
  GLOBAL_VARIABLE_3
End Sub

Public Sub alsoDoThis(byRef obj)
  GLOBAL_VARIABLE_1
End Sub

Public Sub IHeartKitten(byRef obj)
  GLOBAL_VARIABLE_1
End Sub

Public Sub IHeartKitten2(byRef obj)
  GLOBAL_VARIABLE_1
End Sub

Public Function FailCommented(byVal xml)
'  GLOBAL_VARIABLE_1
End Function

示例代码

Imports System.Text.RegularExpressions
Module Module1
  Sub Main()
    Dim sourcestring as String = "replace with your source string"
    Dim re As Regex = New Regex("(?:Public|Private)\s+(Function|Sub)\s+([a-z0-9]*)(?:(?!^End\s+\1\s+(?:$|\Z)).)*^(?:[^'\r\n]|"[^"\r\n]*")*GLOBAL_VARIABLE_1.*?^End\s\1\b",RegexOptions.IgnoreCase OR RegexOptions.IgnorePatternWhitespace OR RegexOptions.Multiline OR RegexOptions.Singleline)
    Dim mc as MatchCollection = re.Matches(sourcestring)
    Dim mIdx as Integer = 0
    For each m as Match in mc
      For groupIdx As Integer = 0 To m.Groups.Count - 1
        Console.WriteLine("[{0}][{1}] = {2}", mIdx, re.GetGroupNames(groupIdx), m.Groups(groupIdx).Value)
      Next
      mIdx=mIdx+1
    Next
  End Sub
End Module

$matches 数组:

(
    [0] => Array
        (
            [0] => Public Function ValidEdgeCase1(byRef obj)
  SomeVariable = "some text with an embedded ' single quote" + GLOBAL_VARIABLE_1
End Sub
            [1] => Public Sub alsoDoThis(byRef obj)
  GLOBAL_VARIABLE_1
End Sub
            [2] => Public Sub IHeartKitten(byRef obj)
  GLOBAL_VARIABLE_1
End Sub
            [3] => Public Sub IHeartKitten2(byRef obj)
  GLOBAL_VARIABLE_1
End Sub
        )

    [1] => Array
        (
            [0] => Function
            [1] => Sub
            [2] => Sub
            [3] => Sub
        )

    [2] => Array
        (
            [0] => ValidEdgeCase1
            [1] => alsoDoThis
            [2] => IHeartKitten
            [3] => IHeartKitten2
        )

)

【讨论】:

  • 这个问题困扰了我一段时间,抱歉回复晚了。
猜你喜欢
  • 1970-01-01
  • 2023-03-18
  • 2012-12-19
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
  • 2014-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多