1. 程式人生 > 其它 >PowerShell筆記 - 14.xml基本操作

PowerShell筆記 - 14.xml基本操作

本系列是一個重新學習PowerShell的筆記,內容引用自PowerShell中文部落格

XML是”可擴充套件標記語言“的縮寫,是一種對於任意結構化的資訊的可描述性語言。過去處理XML還是相當麻煩的,但是現在PowerShell中,對XML有了非常優秀的支援。通過它的幫助,你既可以非常容易的在XML中包裝資料,也可以非常舒服的訪問已有的XML檔案。

XML 結構

XML使用標籤來唯一標識資訊片段,一個標籤像網站中的HTML文件使用的一樣,是一對尖括號。通常,一條資訊被一個起始和結束標記所分割。結束標記前面使用“/”,其結果謂之結點,在下面的例子就應當叫做Name結點。

<Name>Tobias Weltner</Name>

另外,結點擁有它自身相關資訊的屬性(attributes)。這些資訊位於開始標籤。

<staff branch="Hanover" Type="sales">...</staff>

如果一個結點為空,它的開始結點和結束結點可以摺疊起來。結束符號“/”指向標籤結束。例如,在Hanover的子公司沒有任何員工從事銷售工作,那這個標籤可以像這樣描述。

<staff branch="Hanover" Type="sales"/>

通常,如果一個標籤不空,包含更多資訊,這些資訊應當包含在標籤中。這就允許按照你喜歡的深度產生資訊結構。下面XML結構描述Hanover子公司銷售部門工作的兩位員工。

<staff branch="Hanover" Type="sales">
    <employee>
        <Name>Tobias Weltner</Name>
        <function>management</function>
        <age>39</age>
    </employee>
    <employee>
        <Name>Cofi Heidecke</Name>
        <function>security</function>
        <age>4</age>
    </employee>
</staff>

為了讓XML檔案被識別,通常在它們的開始有一個非常簡單類似下面例子中的一個頭宣告。

<?xml version="1.0" ?>

頭宣告聲明瞭緊跟其後的XML符合1.0 版本規範。被稱為”schema”的東西也可以在這裡被指定。具體來說,Schema有個XSD檔案的形式,它用來描述XML檔案結構應當遵循確定的規範。在上一個例子中,Schema可能會指定必須包含“staff”結點作為員工的資訊,進而指定多個命名為“staff”的子結點為必須的。Schema也可以指定相關的資訊,例如每個員工的名稱和定義他們具體的職能。

因為XML檔案有純文字構成,你可以使用編輯器建立他們,也可以直接使用PowerShell。讓我們將前面定義的員工資訊儲存為一個XML檔案:


PS C:\PowerShell> $xml = @'
>> <?xml version="1.0" standalone="yes"?>
>> <staff branch="Hanover" Type="sales">
>> <employee>
>> <Name>Tobias Weltner</Name>
>> <function>management</function>
>> <age>39</age>
>> </employee>
>> <employee>
>> <Name>Cofi Heidecke</Name>
>> <function>security</function>
>> <age>4</age>
>> </employee>
>> </staff>
>> '@ | Out-File employee.xml                                                                                           PS C:\PowerShell> ls


    Directory: C:\PowerShell


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2021/9/22     11:16            556 employee.xml

注意:XML檔案大小寫敏感的!

載入和處理XML檔案

訪問Xml

如果你想將XML檔案按照實際的XML來處理,而不是純文字。檔案的內容必須轉換成XML型別。型別轉換在第六章已經提到,只須一行。

PS C:\PowerShell> $xmldata = [xml](Get-Content employee.xml)                                                            PS C:\PowerShell> $xmldata                                                                                              
xml                            staff
---                            -----
version="1.0" standalone="yes" staff

然而,轉換隻會在指定的xml文字合法並不包含解析錯誤時有效。當你嘗試轉換的xml檔案結構不合法,會報錯。
用來描述XML的結構和資訊現在被存放在變數$xmldata。從現在開始,獲取一小段資訊將會變得非常容易,因為XML物件將每個結點轉換成了屬性。你可以像這樣獲取員工資訊:

PS C:\PowerShell> $xmldata.staff.employee                                                                               
Name           function   age
----           --------   ---
Tobias Weltner management 39
Cofi Heidecke  security   4

訪問和更新單個結點

如果一個結點在xml中是唯一的,你可以像前面的例子一樣輸入一個句號來訪問它。然而,多數情況下XML文件包含了許多類似的結點(被稱為兄弟結點),像前面最後一個例子中一樣,包含了多個獨立的員工。比如,你可以使用管道獲得特定的員工,然後來更新它的資料。

PS C:\PowerShell> $xmldata.staff.employee |
>> Where-Object { $_.Name -match "Tobias Weltner" }                                                                     
Name           function   age
----           --------   ---
Tobias Weltner management 39

PS C:\PowerShell> $employee = $xmldata.staff.employee |
>> Where-Object { $_.Name -match "Tobias Weltner" }                                                                     PS C:\PowerShell> $employee.function = "vacation"                                                                       PS C:\PowerShell> $xmldata.staff.employee | ft -autosize                                                                
Name           function age
----           -------- ---
Tobias Weltner vacation 39
Cofi Heidecke  security 4

使用SelectNodes()來選擇Nodes

SelectNodes()方法是Xpath查詢語言支援的方法,也允許你選擇結點。XPath指的是一個結點‘路徑名稱’:

PS C:\PowerShell> $xmldata = [xml](Get-Content employee.xml)                                                            PS C:\PowerShell> $xmldata.SelectNodes("staff/employee")                                                                
Name           function   age
----           --------   ---
Tobias Weltner management 39
Cofi Heidecke  security   4

結果看起來像前面直接通過屬性訪問一樣,但是XPath支援在方括號中使用萬用字元訪問。
下面的語句只會返回第一個員工結點。

PS C:\PowerShell> $xmldata.SelectNodes("staff/employee[1]")                                                             
Name           function   age
----           --------   ---
Tobias Weltner management 39

如果你想,你還可以獲取一個年齡小於18歲的員工列表:


PS C:\PowerShell> $xmldata.SelectNodes("staff/employee[age<18]")                                                        
Name          function age
----          -------- ---
Cofi Heidecke security 4

類似的方式,查詢語言也支援獲取列表中的最後一位員工資訊,所以也可以指定位置:

PS C:\PowerShell> $xmldata.SelectNodes("staff/employee[last()]")                                                        
Name          function age
----          -------- ---
Cofi Heidecke security 4


PS C:\PowerShell> $xmldata.SelectNodes("staff/employee[position()>1]")                                                  
Name          function age
----          -------- ---
Cofi Heidecke security 4

或者,你可以使用所謂的XpathNavigator,從中獲取許多從XML文字的型別轉換。

# 建立一個 XML定位:
PS C:\PowerShell> $xpath = [System.XML.XPath.XPathDocument]`
>> [System.IO.TextReader][System.IO.StringReader]`
>> (Get-Content employee.xml | out-string)                                                                              PS C:\PowerShell> $navigator = $xpath.CreateNavigator()

# 輸出Hanover子公司的最後一位員工
PS C:\PowerShell> $query = "/staff[@branch='Hanover']/employee[last()]/Name"                                            PS C:\PowerShell> $navigator.Select($query) | Format-Table Value                                                        
Value
-----
Cofi Heidecke

# 輸出Hanover子公司的除了Tobias Weltner之外的所有員工,
PS C:\PowerShell> $query = "/staff[@branch='Hanover']/employee[Name!='Tobias Weltner']"                                                                                                           PS C:\PowerShell> $navigator.Select($query) | Format-Table Value                                                        
Value
-----
Tobias Weltnermanagement39
Cofi Heideckesecurity4

基於Namespace來SelectNode

上面使用PowerShell處理xml文件時,用到了一個selectNode。但是有時候按照上面程式碼執行就是看不到期望的結果。原因可能是因為你的XML文件中多了個名稱空間(Namespace)。沒有名稱空間時,Name就是Name,有了名稱空間後,Name=Namespace+Name。

看下面的例子,有一個XML文件,要求輸出“Obj”的結點資訊。

PS C:\PowerShell> @"
>> <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
>>   <Obj RefId="0">
>>     <TN RefId="0">
>>       <T>Selected.System.IO.FileInfo</T>
>>       <T>System.Management.Automation.PSCustomObject</T>
>>       <T>System.Object</T>
>>     </TN>
>>     <MS>
>>       <S N="Name">test.txt</S>
>>       <I64 N="Length">143</I64>
>>       <DT N="CreationTime">2013-12-01T20:05:22.5568032+08:00</DT>
>>     </MS>
>>   </Obj>
>> </Objs>
>> "@ | Out-File namespacexml.xml                                                                                       PS C:\PowerShell> ls                                                                                                    

    Directory: C:\PowerShell


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2021/9/22     11:50            412 employee.xml
-a----        2021/9/16     13:55            812 Error.txt
-a----        2021/9/22     11:59            840 namespacexml.xml

如果直接使用SelectNodes方法就會列印下面的資訊

PS C:\PowerShell> $xmldata = [xml](Get-Content namespacexml.xml)
PS C:\PowerShell> $xml.DocumentElement.SelectNodes("/Objs/Obj")
At line:1 char:45
+ $xml.DocumentElement.SelectNodes("/Objs/Obj")
+                                             ~
Missing ')' in method call.
At line:1 char:45
+ $xml.DocumentElement.SelectNodes("/Objs/Obj")
+                                             ~
Unexpected token ')' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall

此時就需要加入名命空間引用

PS C:\PowerShell> $ns= new-object System.Xml.XmlNamespaceManager $xmldata.NameTable                                     PS C:\PowerShell> $ns.AddNamespace("myNS", $xmldata.DocumentElement.NamespaceURI)                                       PS C:\PowerShell> $xmldata.DocumentElement.SelectNodes("//myNS:Objs//myNS:Obj",$ns)                                     
RefId TN MS
----- -- --
0     TN MS

但是我一般不用XML中的SelectNodes,PowerShell中有更方便的方法,請看:

PS C:\PowerShell> $xmldata.Objs.Obj                                                                                     
RefId TN MS
----- -- --
0     TN MS

訪問屬性

屬性是定義在一個XML標籤中的資訊,如果你想檢視結點的屬性,可以使用get_Attributes()方法:

PS C:\PowerShell> $xmldata.staff.get_Attributes()                                                                       
#text
-----
Hanover
sales

使用GetAttribute()方法來查詢一個特定的屬性:

PS C:\PowerShell> $xmldata.staff.GetAttribute("branch")                                                                 Hanover

使用SetAttribute()方法來指定新的屬性,或者更新(重寫)已有的屬性。

PS C:\PowerShell> $xmldata.staff.SetAttribute("branch", "New York")                                                     PS C:\PowerShell> $xmldata.staff.GetAttribute("branch")                                                                 New York

新增新結點

如果你想在員工結點列表中新增新的員工。首先,使用CreateElement()建立一個員工元素,然後定製員工的內部結構。最後,就可以在XML結構中你期望的位置插入這個元素。

PS C:\PowerShell> # 載入XML文字檔案:                                                                                   
PS C:\PowerShell> $xmldata = [xml](Get-Content employee.xml)                                                            PS C:\PowerShell> # 建立新的結點:                                                                                      
PS C:\PowerShell> $newemployee = $xmldata.CreateElement("employee")                                                     PS C:\PowerShell> $newemployee.set_InnerXML("<Name>Bernd Seiler</Name><function>expert</function>")                                                              PS C:\PowerShell> # 插入新結點:                                                                                        
PS C:\PowerShell> $xmldata.staff.AppendChild($newemployee)                                                              
Name         function
----         --------
Bernd Seiler expert


PS C:\PowerShell> # 驗證結果:                                                                                           PS C:\PowerShell> $xmldata.staff.employee                                                                               
Name           function   age
----           --------   ---
Tobias Weltner management 39
Cofi Heidecke  security   4
Bernd Seiler   expert

PS C:\PowerShell> # 輸出為純文字:                                                                                      
PS C:\PowerShell> $xmldata.get_InnerXml()                                                                               <?xml version="1.0" standalone="yes"?>
<staff branch="Hanover" Type="sales">
<employee><Name>Tobias Weltner</Name><function>management</function><age>39</age></employee><employee><Name>Cofi Heidecke</Name><function>security</function><age>4</age></employee><employee><Name>Bernd Seiler</Name><function>expert</function></employee>
</staff>

儲存XML檔案

PS C:\PowerShell>  &{(Get-Location).Path + "\employee.xml"}                                                             C:\PowerShell\employee.xml
PS C:\PowerShell> $xmldata.Save( ((Get-Location).Path + "\employee.xml"))                                               PS C:\PowerShell> ls                                                                                                    

    Directory: C:\PowerShell


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2021/9/22     11:50            412 employee.xml