使用VBscript从节点和子节点中提取XML数据

时间:2013-10-24 16:31:44

标签: xml vbscript

第一次在这里发布海报,对VBscript有些新手。我真的可以使用那些知道这个第二天性的人的帮助。 我试图包含一些相关的信息,希望不要太多。

我一直试图让这个工作起来,并且经过几天的尝试和十几次代码迭代后我终于伸出援手了。我没有找到从XML文档中的多个级别(noes和chidlren)提取数据的示例。

我的任务是使用VBScript从XML文件中提取数据。 具体项目是:年份,帐号,当前应付金额,欠款? (真/假)和格式化的权证编号。

XML文件的格式如下所示,其中1000到10,000多个节点填充了这些数据,同时还有大量的'misc'节点。

  <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>

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

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

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

http://technet.microsoft.com/en-us/magazine/2007.02.heyscriptingguy.aspx 这似乎是进入的方向,但我得到一个错误“Node Test Expected Here”:

  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)
好了,现在你已经对我的史前尝试进行了一次傻笑,你可以帮我一把吗? :) 让我知道是否还需要其他任何东西。 感谢任何时间投入。我知道有些人可以轻松地解决这个问题。

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

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

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