12

I have some code that reads from xml files with a namespace using XmlDocument.My challenge is that i have the namespace of the file i'm reading hard coded for now and i pass that to the XmlNamespaceManager.I would like for my approach to be a little more flexible.To read from any kind of xml file.If it has a namespace,then use the namespace manager to read the elements without hard coding the namespace.If the file doesn't have a namespace,then go ahead and just parse it.Below is what I've done.

xmldoc = new XmlDocument ();
xmldoc.Load (fileLocation);


XmlNamespaceManager nameSpaceManager = new XmlNamespaceManager(xmldoc.NameTable);

nameSpaceManager.AddNamespace ("ns","http://schemas.sample.data.org/2005");

XmlNodeList nodeList = xmldoc.SelectNodes("/ns:Demo/ns:Items",  nameSpaceManager);
if (nodeList != null) 
{
    foreach (XmlNode childNode in nodeList) 
    {
        string first = childNode.SelectSingleNode ("ns:First", nameSpaceManager).InnerText;
        string second= childNode.SelectSingleNode ("ns:Second", nameSpaceManager).InnerText;
        string third = childNode.SelectSingleNode ("ns:Third", nameSpaceManager).InnerText;
    }
}

Here's the sample xml file i'm using

<Demo xmlns:i="http://www.justasample.com" xmlns="http://schemas.sample.data.org/2005">
 <Items>

  <First>first</First>
  <Second>second</Second>
  <Third>third</Third>

  </Items>

</Demo>
3
  • 1
    Any reason for not using XDocument? That makes everything, esp. namespaces, a lot easier. Commented May 16, 2015 at 18:50
  • 2
    OK, it is not really a duplicate. But be a little clearer about what does or doesn't work with your current approach. Commented May 16, 2015 at 18:56
  • @HenkHolterman ,it doesn't really have to be just XmlDocument.I'm open to any other suggestions.I think i put that in the question because it's what i had implemented.And to clarify my question,my current approach doesn't work that well because the name space is hard coded.I needed to get rid of that and enable it to work with any xml file. Commented May 17, 2015 at 2:03

4 Answers 4

20

You could consider the following options:

  1. determine whether document contains namespace and depending on it construct xpath query
  2. use namespace agnostic xpath such as local-name(), which will ignore the namespace

Option 1

var xmlDoc = new XmlDocument();
xmlDoc.Load(fileLocation);
//determine  whether document contains namespace
var namespaceName = "ns";
var namespacePrefix = string.Empty;
XmlNamespaceManager nameSpaceManager = null;
if (xmlDoc.FirstChild.Attributes != null)
{
    var xmlns = xmlDoc.FirstChild.Attributes["xmlns"];
    if (xmlns != null)
    {
          nameSpaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
          nameSpaceManager.AddNamespace(namespaceName, xmlns.Value);
          namespacePrefix = namespaceName + ":";
    }
}

XmlNodeList nodeList = xmlDoc.SelectNodes(string.Format("/{0}Demo/{0}Items",namespacePrefix), nameSpaceManager);
if (nodeList != null)
{
    foreach (XmlNode childNode in nodeList)
    {
       string first = childNode.SelectSingleNode(namespacePrefix + "First", nameSpaceManager).InnerText;
       string second = childNode.SelectSingleNode(namespacePrefix + "Second", nameSpaceManager).InnerText;
       string third = childNode.SelectSingleNode(namespacePrefix +  "Third", nameSpaceManager).InnerText;
     }
 }

Option 2

XmlNodeList nodeList = xmlDoc.SelectNodes("/*[local-name() = 'Demo']/*[local-name() = 'Items']");
if (nodeList != null)
{
    foreach (XmlNode childNode in nodeList)
    {    
        string first = childNode.SelectSingleNode("*[local-name() = 'First']").InnerText;
        string second = childNode.SelectSingleNode("*[local-name() = 'Second']").InnerText;
        string third = childNode.SelectSingleNode("*[local-name() = 'Third']").InnerText;
     }
}
Sign up to request clarification or add additional context in comments.

7 Comments

thank you very much for this answer.I'm implementing this right away and will let you know how it works.
I tried both options and they work just fine.Thank you so much.I accepted this as the answer because it's simple and well explained.One quick question,on the implementation option 2, is there a way to ignore that node "Demo" as we're doing with the namespace?Say the "Demo" is auto generated and isn't constant..and next time it will have another unique name.But the inner node will always be called "Items".Is it possible to ignore the 'Demo' in the implementation but still parse the file?
Sure, xpath //*[local-name() = 'Items'] will do the trick, for example: XmlNodeList nodeList = xmlDoc.SelectNodes("//*[local-name() = 'Items']");
I also implemented this last part and it works. This is what i now have: xmlDoc.SelectNodes("//*[local-name() = 'Items']");
Option 2 is good, that way, you can parse it with multiple namespaces and it works great.
|
1

Improving Vadim's Option1, but using XDocument (instead of XmlDocument) API and F# instead of C#:

let fileStream = File.Open(fileLocation, FileMode.Open)
let xDocument = XDocument.Load fileStream
let nsOpt =
    let nsString = xDocument.Root.Name.Namespace.ToString()
    if String.IsNullOrEmpty nsString then
        Console.Error.WriteLine "Warning: no namespace URL found in xmlns attrib"
        None
    else
        let nsManager = XmlNamespaceManager(NameTable())
        let nsPrefix = "x"
        nsManager.AddNamespace(nsPrefix, nsString)
        if nsString <> "http://schemas.sample.data.org/2005" then
            Console.Error.WriteLine "Warning: the namespace URL doesn't match expectations, query may result in no elements"
        Some(nsManager, sprintf "%s:" nsPrefix)

let query = "//{0}Demo/{0}Items"
let nodes =
    match nsOpt with
    | None ->
        let fixedQuery = String.Format(query, String.Empty)
        xDocument.XPathSelectElements fixedQuery
    | Some(nsManager, nsPrefix) ->
        let fixedQuery = String.Format(query, nsPrefix)
        xDocument.XPathSelectElements(fixedQuery, nsManager)
for node in nodes do
    ...

Comments

0

Using Linq-to-XML and my library extensions, specifically ToXName which is handled internally, you can do:

XElement root = XElement.Load(fileLocation);
var items = root.Descendants("Items")
                .Select(item => new
                {
                    First = item.Get("First", ""),
                    Second= item.Get("Second", ""),
                    Third = item.Get("Third", "")
                });

So every First, Second, Third element will use the namespace of the Items element, to determine its own namespace.

1 Comment

@Chuck Savage thanks for the response.I appreciate it.
0

You can give all namespaces in the xml file and add them dynamically.

var document = new XmlDocument();
document.LoadXml(rawData);

var nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("dpx", document.DocumentElement.NamespaceURI); //default

var attributeCollection = document.DocumentElement.Attributes;
for (int i = 0; i < attributeCollection.Count; i++)
{
    var isAttribute = nsmgr.LookupNamespace(attributeCollection[i].LocalName) == null;     
    if (isAttribute)
        nsmgr.AddNamespace(attributeCollection[i].LocalName, attributeCollection[i].Value);
}

XmlElement xmlElem = document.DocumentElement;
var node = xmlElem.SelectSingleNode(xpath, nsmgr);

If the nodes in XML do not have a prefix, you should give them with default prefix when giving path.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.