2013-10-24 121 views
1

第一次海報在這裏和VBscript有點新手。我真的可以使用一些知道這種第二性質的人的幫助。 我試圖包含一些相關的信息,希望不要太多。使用VBscript從節點和子節點提取XML數據

我一直試圖讓這個工作,並在幾天的嘗試和十幾次代碼迭代後終於伸出援助之手。我還沒有找到從XML文檔中的多個級別(noes和chidlren)中提取數據的示例。

我的任務是使用VBScript從XML文件中提取數據。 具體項目爲:年,賬戶號碼,到期金額,有拖欠嗎? (真/假)和格式化的認股權證號碼。

XML文件的格式如下,其中有1000到10000多個節點的任何地方都充滿了這些數據,同時還有很多'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 這似乎是要走的方向,但我得到一個錯誤「節點測試預計這裏的」:

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

我發現棧後下方採用這種技術,它的建議,但它一旦我獲得了兩個價值觀,我就不適合我,而我有更多的價值。我猜這是由於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) 

好吧,現在你已經對我的史前編碼這件事進行了傻笑,你能幫我一把嗎? :) 讓我知道是否需要其他任何東西。 感謝您隨時投入。我知道你們中的一些人可能會輕鬆地踢出這一點。

回答

0

你只能得到節點YearHasDelinquents,因爲節點CurrentAmountDueFormattedWarrantNumber不是的/BillData/BillHeader直接子節點,並且沒有命名AccountNumber節點(正確的節點名稱將是AcctNumber)。爲了從XML樹中的任意位置選擇節點嘗試這樣的表達式:

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

我會去通過一個在我嘗試使用這種方法的舊版迭代中,似乎我沒有在路徑名中包含*,並將其減少到如何根據需要收集數據。欣賞評論! – Nick

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

輸出:

​​

請註明

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和 要素之一線丟失,通過使用//或其他類型的不危險錯誤映射 鬆散的查詢。只需獲取所有「/ Bill/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,, 

爲您解決現實世界的問題,你可以在織更多,如果條款 像

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

或 - 長期來看可能會更好

定義查詢的陣列(在字段順序)

昏暗aQueries:aQueries =陣列(_ 「年」 _ 「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 
+0

哇,那是別的!非常感謝你把所有的時間放在這裏,我會讀一讀你列出的一些項目,比如'連接',數組項目,如果沒有聲明等。這有點令人難以置信,但我會嘗試它並嘗試關注它,以便我可以包含權證號碼搜索。 – Nick