如何将 DOMDocument 传递给子程序

How to pass DOMDocument to Subroutine

提问人:LuPi1801 提问时间:9/12/2023 最后编辑:JohnMLuPi1801 更新时间:9/13/2023 访问量:54

问:

我有一堆来自德国商业登记处的所谓“结构化数据”,这些数据以xml文件的形式出现(一个文件总是保存一家公司的数据)。数据结构符合德国“XJustiz”xml模式(一种专门为简化司法部门的电子数据交换而开发的方案)的规范。这个 xml 模式会定期更新,不幸的是,它会不时更改结构或使用的 ID 和标签名。此外,从商业登记处收集的数据与统一的XJustiz格式不符,目前数据有时以XJustiz第1版的结构提供,有时以XJustiz第3版的结构提供。

我已经有工作代码,用于循环遍历桌面文件夹中所有下载的xml文件,读取xml结构并在工作簿中写下所需的信息,以便进一步处理XJustiz版本1和3的法律分析,每个版本都在一个单独的文件中。

我现在想要实现的是,首先应用一个过程来组合这两个代码,该过程检查某个节点,该节点保存有关所用 XJustiz 版本的信息,然后调用与该特定结构相对应的子例程。

如下图所示,我当前的代码遍历一个指定的目录,将每个 xml 文件设置为新的 DOMDocument60,读取包含版本信息的节点,然后执行子过程,将引用传递给 xmlDoc - 好吧,理论上。这就是我请求你帮助的地方!在子例程中,尝试处理 DOM 的第一行总是抛出运行时错误“91”:对象变量或未设置块变量。

第一个例程的当前代码:

Sub Read_XML_Data()

 [...]

'########## MAIN LOOP START ##########
Do While Len(strFilePath) > 0

   Set xmlDoc = New MSXML2.DOMDocument60
   xmlDoc.async = False
   xmlDoc.Load (strFolder & strFilePath)
        
   'Checking the XJustiz version
   For Each xmlNode In xmlDoc.getElementsByTagName("*")
            
      If StrComp(xmlNode.Attributes(0).Name, "xjustizversion", vbTextCompare) = 0 Then
           
         Select Case Left(xmlNode.Attributes(0).Text, 1)
            
            'Found version 1 and execute corresponding subroutine
            Case 1
               Read_XJustiz_v1 xmlDoc
               Exit For
                    
            Case Else
               MsgBox (strFilePath & " verwendet XJustiz Version " & Left(xmlNode.Attributes(0).Text, 1))
               Exit For
           
         End Select
           
      End If
        
   Next
        
strFilePath = Dir
    
Loop
'########## MAIN LOOP END ##########

End Sub

和子程序:

Sub Read_XJustiz_v1(ByRef xmlDoc As DOMDocument60)

Dim strContent As String

   strContent = xmlDoc.Text
    
   'This line raises the Error No. 91: Object Variable or With Block Variable Not Set.
   If xmlDoc.getElementsByTagName("Beteiligter").Item(0).ChildNodes(1).ChildNodes(2).Text = "Gesellschaft mit beschränkter Haftung" Then
   [...]
   End If

End Sub
  

那么,为什么我不能从子例程访问 DOM,以及要更改哪些内容才能使其工作呢?

我检查了处理后的xml文件确实包含该项目,因此我认为这不可能是这里的问题。xmlDoc.getElementsByTagName("Beteiligter").Item(0).ChildNodes(1).ChildNodes(2).Text

我已经尝试通过传递当前的 strFolder 和 strFile ByVal 来避免传递 DOM,以便从第二个子例程内部重新设置和重新加载 DOM。但是,一旦在第二个子例程中访问 DOM,这样做也会导致运行时错误“91”的引发。

令人恼火的是,在这两种情况下,DOM 都可以通过设置来显示 - 因此信息以某种方式在手边,但似乎带有标签和东西的 xml 结构丢失了(无论如何,它没有显示在我用来测试是否至少传递了任何数据的字符串中)。我真的不知道出了什么问题。strContent = xmlDoc.text

我真的很想避免将两个版本的(独立)工作代码放在一个模块中,因为每个版本都很长,结果将不再可维护。

一种解决方法是将每个工作代码在开始时使用单独的 for-each-loop 放在单独的模块中,并在一个版本的循环完成后触发另一个版本的下一个循环(这会导致不必要的循环,特别是如果将来会有更多不同的版本)。

XML VBA 参数 按引用传递 DOMDocument

评论

0赞 Michael Kay 9/12/2023
我无法帮助您处理 VB 代码,因为我已经多年没有使用过该语言了,但您遇到了 DOM API 的那种问题,这些问题是所有过程式导航 API 所共有的。我的一般建议始终是:(a) 使用专为处理 XML 而设计的高级声明性语言:特别是 XSLT、XPath 和 XQuery;以及 (b) 通过在预处理步骤中将输入文件规范化为通用格式来处理版本和变体,以便将变体的处理限制在应用程序的一小部分。
0赞 LuPi1801 9/13/2023
@MichaelKay:非常感谢您抽出宝贵时间阅读和回答!我会在未来的项目中考虑您的建议。
1赞 Tim Williams 9/13/2023
如果将问题行移到(下)中,它仍然会失败吗?这是一长串属性访问,因此请尝试将其分解,看看哪个步骤失败。Read_XML_DataCase 1
0赞 LuPi1801 9/13/2023
@TimWilliams:这就是让我发疯的原因——如果我把它放在你建议的地方,它就会完美无缺。从我的角度来看,这实际上是关于(而不是)将有关 DOM 的信息传递给第二个子例程。
2赞 Tim Williams 9/13/2023
您是否能够共享可用于重现问题的 XML 文件?按照您在帖子中显示的内容进行操作应该完全没有问题......

答:

0赞 LuPi1801 9/13/2023 #1

经过几天对这个问题的反复试验,我现在偶然发现,如果我声明 just 而不是使用 ,我可以将 DOM ByRef 传递给子例程,而无需对现有代码进行任何其他更改。我甚至不再需要对“Microsoft XML”的引用。因此,工作代码现在如下所示:Dim xmlDoc As ObjectSet xmlDoc = New MSXML2.DomDocument60Set xmlDoc = CreateObject("MSXML.DOMDocument")

Sub Read_XML_Data()

Dim xmlDoc As Object
Dim xmlNode As Object
Dim strFilePath As String
Dim strFolder As String
Dim strContent As String

    'other code, doing stuff and assigning values to strFilePath and strFolder
    [...]

    Set xmlDoc = CreateObject("MSXML.DOMDocument")
    xmlDoc.async = False
    xmlDoc.Load (strFolder & strFilePath)
    
    'checking the XJustiz version
    For Each xmlNode In xmlDoc.getElementsByTagName("*")
        
        If StrComp(xmlNode.Attributes(0).Name, "xjustizversion", vbTextCompare) = 0 Then
       
            strContent = Left(xmlNode.Attributes(0).Text, 1)

            Select Case Left(xmlNode.Attributes(0).Text, 1)
                
                Case 1
                    Read_XJustiz_v1 xmlDoc
                    Exit For
                
                Case Else
                    MsgBox ("XJustiz Version: " & Left(xmlNode.Attributes(0).Text, 1))
                    Exit For
       
            End Select
       
        End If
    
    Next

End Sub


Sub Read_XJustiz_v1(ByRef xmlDoc As Object)
    
    'checking the entities legal form
    If xmlDoc.getElementsByTagName("Beteiligter").Item(0).ChildNodes(1).ChildNodes(2).Text = "Gesellschaft mit beschränkter Haftung" Then 'no more error!
    
    'do the rest of the code
    [...]

End Sub

不幸的是,我无法解释为什么东西会像它一样工作,但它确实有效。

我希望如果有人遇到类似的问题,这可能会有所帮助

0赞 Tim Williams 9/13/2023 #2

只是为了表明它可以正常工作:

Sub Read_XML_Data()

    Dim xmlDoc As MSXML2.DOMDocument60, vers, rootName As String, nd As Object

    Set xmlDoc = New MSXML2.DOMDocument60
    xmlDoc.async = False
    xmlDoc.SetProperty "SelectionLanguage", "XPath"  'to use XPath
    'document has a "default namespace", so add that with a dummy alias `xx`...
    xmlDoc.SetProperty "SelectionNamespaces", "xmlns:xx='http://www.xjustiz.de'"
    xmlDoc.Load "C:\Temp\xjustiz.xml"
    
    vers = xmlDoc.SelectSingleNode("//xx:nachrichtenkopf"). _
                  Attributes.getNamedItem("xjustizVersion").Text
    Debug.Print vers '3.2.1
    
    Select Case vers
        Case "3.2.1"
            Set nd = xmlDoc.SelectSingleNode("//xx:beteiligter")
            Debug.Print "Read_XML_Data:", nd.ChildNodes(0).Text '.ChildNodes(0).Text
            Read_XJustiz_v3_2_1 xmlDoc
        Case Else
            Debug.Print "Version:", vers
    End Select


End Sub

Sub Read_XJustiz_v3_2_1(xmlDoc As DOMDocument60)
    Dim nd As Object
    Set nd = xmlDoc.SelectSingleNode("//xx:beteiligter")
    Debug.Print "Read_XJustiz_v3_2_1:", nd.ChildNodes(0).Text '.ChildNodes(2).Text
End Sub

输出:

Read_XML_Data               Muster und Kollegen 002
Read_XJustiz_v3_2_1         Muster und Kollegen 002

评论

0赞 LuPi1801 9/13/2023
非常感谢您抽出宝贵时间分析文档结构!您能否解释一下,为什么您声明并使用“nd”作为访问 xmlDoc 的对象,它是否可以在没有的情况下工作?另外,与一起使用可能有什么好处?因为我刚刚发现后者允许我将 xmlDoc 作为 Object ByRef 传递给第二个子例程,并使用现有代码按原样遍历 DOM。Dim xmlDoc As MSXML2.DOMDocument60Set xmlDoc = New MSXML2.DOMDocument60Dim xmlDoc As ObjectSet xmlDoc = CreateObject("MSXML.DOMDocument")
0赞 Tim Williams 9/14/2023
nd只是一个泛型对象类型,用于分解原始代码中的一长串属性,以检查第一步是否找到匹配项。在将对象作为参数传递给其他方法时,早期绑定(第一个示例)和后期绑定(第二个示例)之间应该没有区别。有关早期与晚期绑定的更多信息:learn.microsoft.com/en-us/previous-versions/office/troubleshoot/...