【问题标题】:using VBscript to extract data from XML from nodes and children使用 VBscript 从节点和子节点的 XML 中提取数据
【发布时间】:2013-10-24 16:31:43
【问题描述】:

第一次在这里发布海报,并且是 VBscript 的新手。我真的可以从你们知道这一点的人那里得到一些帮助,就像第二天性一样。 我已尝试包含一些相关信息,但希望不要太多。

我一直在努力让它发挥作用,经过几天的尝试和十几次代码迭代,我终于接触到了。我还没有找到从 XML 文档中的多个级别(noes 和 chidlren)提取数据的示例。

我的任务是使用 VBScript 从 XML 文件中提取数据。 具体项目是:年份、帐号、当前应付金额、是否拖欠? (true/false) 和格式化的仓单号。

XML 文件的格式如下,有 1,000 到 10,000 多个节点填充此数据,其中还有大量“杂项”节点。

  <BillData>
    <BillHeader>
      <Year>2010</Year>
      <misc></misc>
      <misc2></misc2>
      <misc3></misc3>
      <AcctNumber>0002566129</AcctNumber>
      <misc4></misc4>
      <PayAmounts>
         <CurrentAmountDue>133.06</CurrentAmountDue>
         <misc5></misc5>
      </PayAmounts>
      <misc6></misc6>
      <HasDelinquents>true</HasDelinquents>
      <WarrantInfo>
         <FormattedWarrantNumber>201115447</FormattedWarrantNumber>
      </WarrantInfo>
     </BillHeader>
   </BillData>

CurrentAmountDue 和 FormattedWarrantNumber 可能并不总是存在。这并不是说它们是空白的,而是 CurrentAmountDue 的整个条目可能会丢失,如下所示。

<PayAmounts>
   <misc5></misc5>
</PayAmounts>

我需要将此数据提取到逗号分隔的文本文件中。如果数据不存在,那么我只需要插入命令,所以当输出最终导入 Excel 时,可以注意到它是空白的。

我面临的挑战是进入不同的子节点并正确提取数据。我似乎无法正确选择不同的节点。

这些是我用作参考的一些链接,但似乎无法正常工作。

http://technet.microsoft.com/en-us/magazine/2007.02.heyscriptingguy.aspx 这似乎是前进的方向,但我收到一个错误“此处需要节点测试”:

  Set colNodes=xmlDoc.SelectNodes("/BillData/BillHeader/*" (Year | Account | CurrentAmountDue)")

我在 Stack 上发现了一篇帖子,它建议在下面使用这种技术,但是一旦我超过了两个值,它就对我不起作用,而我有更多。我猜这是因为 CurrentAmountDue 和 FormattedWarrantNumber 可以说是 XML 的更深层次。

  strQuery = "/BillData/BillHeader/ " & _
  "[name()='Year' or name()='AccountNumber' or name()='HasDelinquents' or name()='CurrentAmountDue' or name()='FormattedWarrantNumber']"

令我惊讶的是,我能够让它返回一些值,但不是全部在同一个循环中,所以我的输出是关闭的(第一行将只显示年份,最后一行丢失)并且只是一个逗号。

   strQuery = "/BillData/BillHeader/*"
   Set colNodes=xmlDoc.selectNodes(strQuery)
   For Each objNode in colNodes 

   ' some lame if then statements that get the values, but this can't be the correct approach!
   ' these three items (Year, Account and HasDelinquents are under each BillHeader as far as I can tell, but this doesn't seem to be the most effective method.
     if objNode.nodeName = "Year" then strYear = objNode.text  
     if objNode.nodeName = "Account" then strAccount = objNode.text 
     if objNode.nodeName = "HasDelinquents" then strHasDelq = objNode.text 

          for each CurrentAmt in objNode.SelectNodes("./CurrentAmountDue")
                strCurrAmt = CurrentAmt.text
                ' i finally got a value here when I use msgbox to view it.'
          next

          for each WarrantNum in objNode.SelectNodes("./FormattedWarrantNumber")
                strWarNum = WarrantNum.text   
                ' getting this value also when I use msgbox to view it.
          next
   next

所以你可以看到我的尝试是徒劳的。

我也尝试在下面插入这一行。我把它放在最后一个 NEXT 之前,但它没有按预期工作。我还尝试在写入文件之前插入一些 IF-Then 语句来检查 Year 和 Account 中的值,然后在写入文件后清除这些值。这几乎奏效了,但我的第一行和最后一行没有产生正确的数据。

     objFileToWrite.WriteLine(strYear & "," & strAccount & "," & strCurrAmt & "," & strHasDelq & "," & strWarNum)

好的,既然您已经对我在史前编写此代码的尝试感到傻笑,您能帮帮我吗? :) 让我知道是否需要其他任何东西。 感谢您投入的任何时间。我知道你们中的一些人可能会轻松解决这个问题。

【问题讨论】:

    标签: xml vbscript


    【解决方案1】:

    问题前半部分的低技术“设计模式” - 创建和写入 .CSV/.TXT 文件 - 是:

    Get an FSO
    Open traget file for writing
    WriteLine Header (optional)
    Loop over your data to export
        Create empty Array (elements ~ columns)
        Fill elements (if possible)
        WriteLine Join(Array, Delimiter) to traget file
    Close file
    

    在代码中:

      Option Explicit
      Dim oFS     : Set oFS = CreateObject("Scripting.FileSystemObject")
      Dim sFSpec  : sFSpec  = "..\data\step00.csv"
      Dim sDelim  : sDelim  = ";"
      Dim aFields : aFields = Split("Yr ANum Amnt Delq FWNum")
      Dim oTS     : Set oTS = oFS.CreateTextFile(sFSpec)
      Dim nRecs   : nRecs   = 10
      Dim nRec
      oTS.WriteLine Join(aFields, sDelim)
      For nRec = 1 To nRecs
          ReDim aData(UBound(aFields))
          aData(0) = nRec
          If nRec Mod 2 Then aData(1) = "odd"
    
          oTS.WriteLine Join(aData, sDelim)
      Next
      oTS.Close
    
      WScript.Echo oFS.OpenTextFile(sFSpec).ReadAll()
    

    输出:

    Yr;ANum;Amnt;Delq;FWNum
    1;odd;;;
    2;;;;
    3;odd;;;
    4;;;;
    5;odd;;;
    6;;;;
    7;odd;;;
    8;;;;
    9;odd;;;
    10;;;;
    

    请注明区别

    oTS.WriteLine Join(aData, sDelim)
    

    objFileToWrite.WriteLine(strYear & "," & strAccount & "," & strCurrAmt & "," & strHasDelq & "," & strWarNum)
    (spurious param list (), btw)
    

    第二部分的框架 - 循环结构化 XML - 应该 看起来像这样

    Get an msxml2.domdocument
    Configure
    Load .XML file
    If error
       deal with it
    Else
       use top level XPath to get your top level nodelist
       Loop nodelist
          handle sub-parts
    End If
    

    在代码中:

      Option Explicit
      Dim oFS     : Set oFS = CreateObject("Scripting.FileSystemObject")
      Dim sFSpec  : sFSpec  = oFS.GetAbsolutePathName("..\data\step01.xml")
      WScript.Echo oFS.OpenTextFile(sFSpec).ReadAll()
    
      Dim oXD : Set oXD = CreateObject("msxml2.domdocument")
      oXD.setProperty "SelectionLanguage", "XPath"
      oXD.async = False
      oXD.load sFSpec
      If oXD.parseError.errorCode Then
         WScript.Echo "fail", sFSpec
         WScript.Echo oXD.parseError.reason
      Else
         WScript.Echo "ok", sFSpec
         Dim ndlBills : Set ndlBills = oXD.selectNodes("/Bills/BillData/BillHeader")
         If ndlBills.length Then
            WScript.Echo ndlBills.length, "bill nodes"
            Dim ndBill
            For Each ndBill In ndlBills
                Dim ndSub
                Set ndSub = ndBill.selectSingleNode("Year")
                If ndSub Is Nothing Then
                   WScript.Echo "no Year"
                Else
                   WScript.Echo "Year", ndSub.text
                End If
                Set ndSub = ndBill.selectSingleNode("PayAmounts/CurrentAmountDue")
                If ndSub Is Nothing Then
                   WScript.Echo "no Amount"
                Else
                   WScript.Echo "Amount", ndSub.text
                End If
            Next
         End If
      End If
    

    输出:

    <?xml version="1.0" encoding="utf-8" ?>
    <Bills>
     <BillData>
      <BillHeader>
       <Year>2012</Year>
      </BillHeader>
     </BillData>
     <BillData>
      <BillHeader>
       <PayAmounts>
        <CurrentAmountDue>123.45</CurrentAmountDue>
       </PayAmounts>
      </BillHeader>
     </BillData>
    </Bills>
    
    ok E:\trials\SoTrials\answers\19571565\data\Step01.xml
    2 bill nodes
    Year 2012
    no Amount
    no Year
    Amount 123.45
    

    由于您想将每个 BillHeader 中的数据放入 .CSV 的一行中,并且 缺少元素,不要冒险使用 // 或其他类型的错误映射 松散的查询。只需获取所有“/Bills/BillData/BillHeader”的列表并向下钻取即可。

    两个脚本的合并:

      Option Explicit
      Dim oFS     : Set oFS = CreateObject("Scripting.FileSystemObject")
      Dim sXFSpec : sXFSpec = oFS.GetAbsolutePathName("..\data\step02.xml")
      WScript.Echo oFS.OpenTextFile(sXFSpec).ReadAll()
      Dim sCFSpec : sCFSpec = "..\data\step02.csv"
      Dim sDelim  : sDelim  = ","
      Dim aFields : aFields = Split("Yr ANum Amnt Delq FWNum")
      Dim oTS     : Set oTS = oFS.CreateTextFile(sCFSpec)
      oTS.WriteLine Join(aFields, sDelim)
    
      Dim oXD : Set oXD = CreateObject("msxml2.domdocument")
      oXD.setProperty "SelectionLanguage", "XPath"
      oXD.async = False
      oXD.load sXFSpec
      If oXD.parseError.errorCode Then
         WScript.Echo "fail", sXFSpec
         WScript.Echo oXD.parseError.reason
      Else
         WScript.Echo "ok", sXFSpec
         Dim ndlBills : Set ndlBills = oXD.selectNodes("/Bills/BillData/BillHeader")
         If ndlBills.length Then
            WScript.Echo ndlBills.length, "bill nodes"
            Dim ndBill
            For Each ndBill In ndlBills
                ReDim aData(UBound(aFields))
                Dim ndSub
                Set ndSub = ndBill.selectSingleNode("Year")
                If Not ndSub Is Nothing Then
                   aData(0) = ndSub.text
                End If
                Set ndSub = ndBill.selectSingleNode("PayAmounts/CurrentAmountDue")
                If Not ndSub Is Nothing Then
                   aData(2) = ndSub.text
                End If
                oTS.WriteLine Join(aData, sDelim)
            Next
         End If
      End If
      oTS.Close
    
      WScript.Echo oFS.OpenTextFile(sCFSpec).ReadAll()
    

    输出:

    <?xml version="1.0" encoding="utf-8" ?>
    <Bills>
     <BillData>
      <BillHeader>
       <Year>2012</Year>
      </BillHeader>
     </BillData>
    
      <BillHeader>
       <Year>0000</Year>
       <PayAmounts>
        <CurrentAmountDue>0.0</CurrentAmountDue>
       </PayAmounts>
       <junk/>
      </BillHeader>
    
     <BillData>
      <BillHeader>
       <PayAmounts>
        <CurrentAmountDue>123.45</CurrentAmountDue>
       </PayAmounts>
      </BillHeader>
     </BillData>
    
     <BillData>
      <BillHeader>
       <Year>2013</Year>
       <PayAmounts>
        <CurrentAmountDue>47.11</CurrentAmountDue>
       </PayAmounts>
      </BillHeader>
     </BillData>
    </Bills>
    
    ok E:\trials\SoTrials\answers\19571565\data\Step02.xml
    3 bill nodes
    Yr,ANum,Amnt,Delq,FWNum
    2012,,,,
    ,,123.45,,
    2013,,47.11,,
    

    为了解决您的实际问题,您可以编织更多的 IF 子句 喜欢

    Set ndSub = ndBill.selectSingleNode("XPath")
    If Not ndSub Is Nothing Then
       aData(N) = ndSub.text
    End If
    

    或者 - 从长远来看可能会更好

    定义一个查询数组(按字段顺序)

    Dim aQueries : aQueries = Array( _ “年” _ , "PayAmounts/CurrentAmountDue" _ )

    将最里面的循环减少到

    Dim ndBill
    For Each ndBill In ndlBills
        oTS.WriteLine Join(getData(ndBill, aQueries), sDelim)
    Next
    

    定义 getData()

    Function getData(ndBill, aQueries)
      Dim nUb : nUb = UBound(aQueries)
      ReDim aData(nUb)
      Dim q
      For q = 0 To nUb
          Dim ndSub
          Set ndSub = ndBill.selectSingleNode(aQueries(q))
          If Not ndSub Is Nothing Then
             aData(q) = ndSub.text
          End If
      Next
      getData = aData
    End Function
    

    【讨论】:

    • 哇,那是另一回事!感谢您为此付出的所有时间,我将阅读您列出的一些项目,例如“连接”、数组项、if not 语句等。这有点压倒性,但我会尝试一下并尝试关注它,以便我可以合并搜查令号。
    【解决方案2】:

    您只得到节点YearHasDelinquents,因为节点CurrentAmountDueFormattedWarrantNumber 不是/BillData/BillHeader 的直接子节点,并且没有名为AccountNumber 的节点(正确的节点名称将是AcctNumber)。要从 XML 树中的任何位置选择节点,请尝试如下表达式:

    //*[name()='Year' or name()='AcctNumber' or name()='HasDelinquents' or name()='CurrentAmountDue' or name()='FormattedWarrantNumber']
    

    【讨论】:

    • 我将通过我尝试这种方法的旧迭代之一,但看起来我没有在路径名中包含 *,我会将它减少到你有看看我是否可以根据需要收集数据。感谢您的评论!
    猜你喜欢
    • 1970-01-01
    • 2018-02-01
    • 1970-01-01
    • 2012-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多