Jahmic Jahmic - 3 months ago 50
C# Question

C# XML Select dictionary node by key value

I have an XML file (It's an iTunes file) with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<key>Major Version</key><integer>1</integer>
<key>Minor Version</key><integer>1</integer>
<key>Application Version</key><string></string>


<key>Music Folder</key><string>file://localhost/C:/Users/username/longpath/</string>

How can I get the value of the last string, where the
is 'Music Folder'?

It's a really big file, so I'd rather not iterate through all the nodes.

I've tried some of the following:

XmlNode location = xmlDocument.SelectSingleNode("descendant::dict[key='Music Folder']");
location = xmlDocument.SelectSingleNode("//text()[.='Music Folder']");
location = xmlDocument.SelectSingleNode("string[last()]");
location = xmlDocument.SelectSingleNode("dict::string[last()]");
location = xmlDocument.SelectSingleNode("descendant::dict[string[last()]]");

I'm not that familiar with XPath expressions and having trouble making the correct expression.

I'm open to LINQ solutions too.


The XPath expression for your document is evaluated in the context of the document root so you need to specify the plist node ideally, and I assume you want the string value so you will need to navigate to that node.

XmlNode location = xmlDocument.SelectSingleNode("plist/dict/key[.='Music Folder']/following-sibling::string[1]")

Note that the value is not nested inside the key element but is the following sibling. Given that this appears at the end you could probably get away with:

XmlNode location = xmlDocument.SelectSingleNode("plist/dict/string[last()]")

I would explicitly specify the plist node in the XPath as it keeps the possible matches to a minimum. Another point to note is that your XPath will likely end up with the underlying technology iterating each node anyway depending on how you structure your expression as some implementations may be able to apply optimisations.

You were close but I see you were mixing up operators and using :: or []where / would be more appropriate. The :: separates an axis from a node name so descendants::dict gets all the descendants of the context node (xmlDocument) named dict. The / operator is effectively shorthand for child::. The [] is your filter predicate that narrows a result set.