PowerShell で XML を扱う時に名前空間がついている事に気づかずはまった

2018年6月5日

XML を読み込んで XPath を使って特定の要素を取得する、至って普通のことをやっているつもりだったけど、何故か取得できずハマった。言い訳をいうときっと夜中に作業をしてたからだと思うw

ネームスペースなし

PS> $xml = @"
<?xml version="1.0" encoding="utf-8"?>
<root id="0000001" >
    <items>
        <item name="Name1">ほげ1</item>
        <item name="Name2">ほげ2</item>
        <item name="Name3">ほげ3</item>
    </items>
</root>
"@

PS> $docXml = [xml]$xml
PS> $docXml.SelectNodes('//item[@name="Name1"]')
name  #text
----  -----
Name1 ほげ1  

こうなる予定でした

ネームスペースあり

PS> $xml = @"
<?xml version="1.0" encoding="utf-8"?>
<root id="0000001" xmlns="http://tempuri.org/hoge.xsd">
    <items>
        <item name="Name1">ほげ1</item>
        <item name="Name2">ほげ2</item>
        <item name="Name3">ほげ3</item>
    </items>
</root>
"@

PS> $docXml = [xml]$xml
PS> $docXml.SelectNodes('//item[@name="Name1"]')

なにもない…

ちなみに Node を辿ることは可能

PS> $docXml.root.items

Name            : items
LocalName       : items
NamespaceURI    : http://tempuri.org/hoge.xsd
Prefix          : 
NodeType        : Element
ParentNode      : root
OwnerDocument   : #document
IsEmpty         : False
Attributes      : {}
HasAttributes   : False
SchemaInfo      : System.Xml.XmlName
InnerXml        : <item name="Name1" xmlns="http://tempuri.org/hoge.xsd">ほげ1</item><item name="Name2" xmlns="http://tempuri.org/hoge.xsd">ほげ2</item><item nam
                  e="Name3" xmlns="http://tempuri.org/hoge.xsd">ほげ3</item>
InnerText       : ほげ1ほげ2ほげ3
NextSibling     : 
PreviousSibling : 
Value           : 
ChildNodes      : {Name1, Name2, Name3}
FirstChild      : item
LastChild       : item
HasChildNodes   : True
IsReadOnly      : False
OuterXml        : <items xmlns="http://tempuri.org/hoge.xsd"><item name="Name1">ほげ1</item><item name="Name2">ほげ2</item><item name="Name3">ほげ3</item></items>
BaseURI         : 
PreviousText    : 

解決方法は XmlNamespaceManager を使う

PS> $NsMgr  = New-Object -TypeName System.Xml.XmlNamespaceManager -ArgumentList $docXml.NameTable
PS> $NsMgr.AddNamespace('d',  $docXml.DocumentElement.GetAttribute('xmlns'))
PS> $docXml.SelectNodes('//d:item[@name="Name1"]', $NsMgr)
name  #text
----  -----
Name1 ほげ1