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