3

Visual Studio creates ".trx" files when it runs tests and I am trying to process the XML in these files. However I am getting unexpected results when trying to access parts of the XML with Xpath expressions.

The code below includes a cut down version of a ".trx" file. The files contain an xmlns="..." attribute that appears to prevent the Xpath accesses. The accesses work and find the expected nodes when the xmlns... is removed.

How do I change the name space manager (manager) or the Xpaths in the code (or something else) so that I can get the list of nodes matching various Xpaths in the unmodified XML.

I have tried adding manager.AddNamespace("ns", trxContent.NamespaceURI); and including ns: in the Xpaths, but without success.

using System;
using System.Xml;

namespace XpathXmlns
{
    class XpathXmlns
    {
        static string WithXmlns = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<TestRun id=""111"" name=""someName"" runUser=""someUser"" xmlns=""http://microsoft.com/schemas/VisualStudio/TeamTest/2010""> tr1
  <TestSettings name=""Local1"" id=""222"" idx=""333""> ts2
    <Description>Description 1 context.</Description> ts3
  </TestSettings> tr4
  <TestSettings name=""Local2"" id=""333"" idx=""444""> ts5
    <Description>Description 2 context.</Description> ts6
  </TestSettings> tr7
</TestRun>
";

        static void Main(string[] args)
        {
            string WithoutXmlns = WithXmlns.Replace(@" xmlns=""http://microsoft.com/schemas/VisualStudio/TeamTest/2010""", "");

            ProcessXml("WithXmnns", WithXmlns);
            ProcessXml("WithoutXmlns", WithoutXmlns);
        }

        static int counter = 0;
        static XmlNamespaceManager manager;

        static void ProcessXml(string label, string xml)
        {
            Console.WriteLine(label);
            Console.WriteLine(xml);
            XmlDocument trxContent = new XmlDocument();
            trxContent.LoadXml(xml);

            XmlNameTable xmlnt = trxContent.NameTable;
            manager = new XmlNamespaceManager(xmlnt);
            //manager.AddNamespace("ns", trxContent.NamespaceURI);

            XmlNode root = trxContent.DocumentElement;

            Extract(trxContent, "//TestRun");
            Extract(trxContent, "//TestRun/TestSettings");
            Extract(trxContent, "//TestRun/TestSettings/Description");
            Extract(trxContent, "//Description");
            Extract(trxContent, "//TestSettings/@id");
            //Extract(trxContent, "//@id"); // This works OK, it finds the nodes in both cases.
            //Extract(trxContent, "//@idx"); // This works OK, it finds the nodes in both cases.
            Extract(trxContent, "//ns:TestRun");
            Extract(trxContent, "//ns:TestRun/TestSettings");
            Extract(trxContent, "//ns:TestRun/TestSettings/Description");
            Extract(trxContent, "//ns:Description");
            Extract(trxContent, "//ns:TestSettings/@id");
        }

        static void Extract(XmlDocument doc, string xpath)
        {
            counter = 0;
            ExtractNodes("doc-node", doc, xpath);
            ExtractDocNodes("doc", doc, xpath);
            Console.WriteLine();
        }

        private static void ExtractNodes(string source, XmlNode root, string xpath)
        {
            counter++;
            XmlNodeList nodes = root.SelectNodes(xpath, manager);
            ListFoundNodes(source, xpath, nodes);
        }
        private static void ExtractDocNodes(string source, XmlDocument root, string xpath)
        {
            counter++;
            XmlNodeList nodes = root.SelectNodes(xpath, manager);
            ListFoundNodes(source, xpath, nodes);
        }

        private static void ListFoundNodes(string source, string xpath, XmlNodeList nodes)
        {
            Console.WriteLine("    {0,2}: Get {1} from {2} {3}", counter, nodes.Count, source, xpath);
            foreach (XmlNode node in nodes)
            {
                Console.WriteLine("             Nodes  name='{0}'", node.Name ?? "__None__");
            }
        }
    }
}
AdrianHHH
  • 13,492
  • 16
  • 50
  • 87
  • Is there a reason why you are using XmlDocument rather than the (easier, newer and LINQ-friendly) XDocument? – spender Nov 21 '16 at 11:23
  • Search for "XPath default namespace" and find another 1000 answers to this question. – Michael Kay Nov 21 '16 at 12:45
  • @spender Thank you for suggesting `XDocument`, I will look at that. I used `XmlDocument` because it was the method on the Microsoft pages I found. – AdrianHHH Nov 21 '16 at 12:48
  • @MichaelKay There are many Q&As about this topic. I have view several found by the search term you suggest. They seem dissimilar to my question. Perhaps they are the same but the way those Q&As are phrased did not match my understanding of the problem. – AdrianHHH Nov 21 '16 at 13:03
  • Here's one that pretty well identical: http://stackoverflow.com/questions/585812/using-xpath-with-default-namespace-in-c-sharp If you don't recognize it as identical, then that may be because you haven't mastered the terminology, in which case it would help to do more background reading. – Michael Kay Nov 21 '16 at 14:59

1 Answers1

4

Xmlns attribute defines xml namespace of element. So, your TestRun element (and all elements under it) belong to the namespace http://microsoft.com/schemas/VisualStudio/TeamTest/2010. So first you have to add that namespace to the manager:

XmlNameTable xmlnt = trxContent.NameTable;
manager = new XmlNamespaceManager(xmlnt);
manager.AddNamespace("ns", @"http://microsoft.com/schemas/VisualStudio/TeamTest/2010");

If you don't want to hardcode it, you can use namespace of the root element:

XmlNode root = trxContent.DocumentElement;
XmlNameTable xmlnt = trxContent.NameTable;
manager = new XmlNamespaceManager(xmlnt);
manager.AddNamespace("ns", root.NamespaceURI);

Then you have to use that namespace prefix you defined (ns) in your queries:

Extract(trxContent, "//ns:TestRun");
// note that all subelements (like TestSettings) are also prefixed
Extract(trxContent, "//ns:TestRun/ns:TestSettings");
Extract(trxContent, "//ns:TestRun/ns:TestSettings/ns:Description");
Extract(trxContent, "//ns:Description");
Extract(trxContent, "//ns:TestSettings/@id");
Evk
  • 98,527
  • 8
  • 141
  • 191
  • 1
    After lots of experimenting on my part I wrote the question and now you have provided a solution within just a few minutes. Thank you. – AdrianHHH Nov 21 '16 at 13:08