19

I'm trying to parse an XML document. The document in question is an AppxManifest file.

An example document looks like this:

<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest" xmlns:build="http://schemas.microsoft.com/developer/appx/2012/build" IgnorableNamespaces="build">
  <Identity Name="uytury" Publisher="hygj" Version="1.0.0.12" ProcessorArchitecture="neutral" />
  <Properties>
    <DisplayName>jhjj</DisplayName>
    <PublisherDisplayName>bhhjb</PublisherDisplayName>
    <Logo>Assets\StoreLogo.png</Logo>
  </Properties>
  <Prerequisites>
    <OSMinVersion>6.2.1</OSMinVersion>
    <OSMaxVersionTested>6.2.1</OSMaxVersionTested>
  </Prerequisites>
  <Resources>
    <Resource Language="EN" />
  </Resources>
  <Applications>
    <Application Id="App" Executable="gfg.exe" EntryPoint="gfg.App">
      <VisualElements DisplayName="fdsf" Logo="Assets\Logo.png" SmallLogo="Assets\SmallLogo.png" Description="gfdsg" ForegroundText="light" BackgroundColor="#2672EC">
        <DefaultTile ShowName="allLogos" WideLogo="Assets\WideLogo.png" ShortName="gfdsg" />
        <SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#2672EC" />
        <InitialRotationPreference>
          <Rotation Preference="portrait" />
          <Rotation Preference="landscape" />
          <Rotation Preference="portraitFlipped" />
          <Rotation Preference="landscapeFlipped" />
        </InitialRotationPreference>
      </VisualElements>
      <Extensions>
        <Extension Category="windows.search" />
        <Extension Category="windows.shareTarget">
          <ShareTarget>
            <DataFormat>Text</DataFormat>
          </ShareTarget>
        </Extension>
      </Extensions>
    </Application>
  </Applications>
  <build:Metadata>
    <build:Item Name="TargetFrameworkMoniker" Value=".NETCore,Version=v4.5" />
    <build:Item Name="VisualStudio" Version="11.0" />
    <build:Item Name="OperatingSystem" Version="6.2.9200.16384 (win8_rtm.120725-1247)" />
    <build:Item Name="Microsoft.Build.AppxPackage.dll" Version="11.0.50727.1" />
    <build:Item Name="Microsoft.Windows.UI.Xaml.Build.Tasks.dll" Version="11.0.50727.1" />
  </build:Metadata>
</Package>

I try to parse it like so:

var xml=new XmlDocument();
xml.Load(myfile);
var mgr=new XmlNamespaceManager(xml.NameTable);
mgr.AddNamespace("", "http://schemas.microsoft.com/appx/2010/manifest");
var nodes=xml.SelectNodes("Applications");

However, after I execute this, nodes will never contain anything. The xml document is loaded and such though. using SelectNodes("//*") returns every node as expected. What is my problem here?

I've also tried many variations on that XPath query such as

  • /Package/Applications/Application
  • Applications/Application
  • Applications/*

Nothing appears to retrieve the single node though. Ideally, I'd like for nodes to contain all of the Application nodes

Earlz
  • 62,085
  • 98
  • 303
  • 499
  • 2
    Your title talks about XDocument, but your code uses XmlDocument. If you were *really* using LINQ to XML, I'd just suggest using Descendants... is LINQ to XML an option? – Jon Skeet Sep 26 '12 at 18:20
  • @JonSkeet sadly we can't use Linq to XML because we target .Net 2.0. However I meant to put XmlDocument. I always get the two confused – Earlz Sep 26 '12 at 18:22

4 Answers4

47

You have to use xml namespace specifically to select them. consider

"//*[local-name()='Applications']/*[local-name()='Application']"    

in your case this code may also work well:

var doc = new XmlDocument();
doc.LoadXml(xml);
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("a", "http://schemas.microsoft.com/appx/2010/manifest");
var nodes = doc.SelectNodes("//a:Applications/a:Application",nsmgr);
M. Schena
  • 2,039
  • 1
  • 21
  • 29
aiodintsov
  • 2,545
  • 15
  • 17
  • For the first one I get "expression must evaluate to a node-set" – Earlz Sep 26 '12 at 18:20
  • 1
    Ah, that works now. By chance is there a cleaner way of doing this? I've never dealt with namespaces with XPath so I'm use to something simple and clean like `/Applications/Application` just working – Earlz Sep 26 '12 at 18:27
  • I had to use it couple months ago and was not able to find a cleaner syntax, but the XPath 2.0 "//*:name" which is not supported by .net framework system libs. you can find a 3rd party XPath 2.0 and XSTL 2.0 products though. – aiodintsov Sep 26 '12 at 18:30
  • Oh, you CAN specify namespaces you need via this overload http://msdn.microsoft.com/en-us/library/4bektfx9.aspx however in my case I did to deal with generic requests those could have different namespaces/namespace versions declared so resolving namespace was not an option for me – aiodintsov Sep 26 '12 at 18:32
  • Although you went through a lot of revisions, I think you provided the most complete answer. Other people mentioned naming the namespace, but you also included the overload to make it actually work, and for my XPath query to be nice and clean :) Thanks! – Earlz Sep 26 '12 at 18:41
11

You need to specify prefixes for namespaces in NamespaceManager and XPaths. Note that prefixes does not need to match anything except between your XPath and your namespace manager*.

var xml=new XmlDocument();
xml.Load(myfile);
var mgr=new XmlNamespaceManager(xml.NameTable);
mgr.AddNamespace("a", "http://schemas.microsoft.com/appx/2010/manifest");
mgr.AddNamespace("bar", "http://schemas.microsoft.com/developer/appx/2012/build");
var nodes=xml.SelectNodes("//a:Applications", mgr);

And as pointed out by other answers XPath that accepts any namespace is another option.

*) I.e. in your particular sample there are 2 namespaces "default" (note that default prefix is not the same as empty namespace) and "build". So when you define your namespace manager you need to specify a prefix for each of the namespace (if you need to target nodes in both), but prefixes can be arbitrary strings (valid for prefixes but not empty). I.e. use "a" for "default" namespace and "bar" for namespace that mapped to "build" in the XML.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • Heh, that's odd. With that code I get "Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function" which doesn't make any sense to me as I have a namespace manager? – Earlz Sep 26 '12 at 18:35
  • 1
    I think either `//a:Applications` or `a:Package/a:Applications`, because that node is not directly under the root. – MiMo Sep 26 '12 at 18:37
  • 1
    The namespace manager should be passed to `SelectNodes`: `SelectNodes("//a:Applications", mgr);` – MiMo Sep 26 '12 at 18:38
  • @Earlz, sample fixed (I hope) - forget to pass namespace manger. And inlined MiMo suggestion too. – Alexei Levenkov Sep 26 '12 at 18:40
6

Not in this particular case, but in general, if the namespace URN in the actual XML is not exactly the same as one used to add a namespace to a namespace manager (example: missing a trailing slash), and a prefix is specified in XPath, the query may return null.

If namespace URN in the XML is not reliable, syntax

"//*[local-name()='tag']" 

will work.

ajeh
  • 2,652
  • 2
  • 34
  • 65
4

You might need to read this

Here's your code:

var xml = new XmlDocument();
xml.Load("myXMLFile1.xml");
var mgr = new XmlNamespaceManager(xml.NameTable);
mgr.AddNamespace("", "http://schemas.microsoft.com/appx/2010/manifest");
XmlNode root = xml.DocumentElement;
var nodes = root.SelectNodes("//*[local-name()='Applications']/*[local-name()='Application']");
Andreas Rehm
  • 2,222
  • 17
  • 20
  • I did read that :) I just didn't realize it'd change my XPath query so radically compared to an XML file without a namespace – Earlz Sep 26 '12 at 18:31