How to use XElement.XPathSelectElement with namespace [duplicate] - c#

Why is this Xpath not working using XDocument.XPathSelectElement?
Xpath:
//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]
XML
<Plugin xmlns="http://www.MyNamespace.ca/MyPath">
<UI>
<PluginPageCategory>
<Page>
<Group>
<CommandRef>
<Images>
</Images>
</CommandRef>
<CommandRef>
<Images>
</Images>
</CommandRef>
</Group>
</Page>
</PluginPageCategory>
</UI>
</Plugin>
C# Code:
myXDocument.XPathSelectElement("//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]", myXDocument.Root.CreateNavigator());

When namespaces are used, these must be used in the XPath query also. Your XPath query would only work against elements with no namespace (as can be verified by removing the namespace from your XML).
Here's an example showing how you create and pass a namespace manager:
var xml = ... XML from your post ...;
var xmlReader = XmlReader.Create( new StringReader(xml) ); // Or whatever your source is, of course.
var myXDocument = XDocument.Load( xmlReader );
var namespaceManager = new XmlNamespaceManager( xmlReader.NameTable ); // We now have a namespace manager that knows of the namespaces used in your document.
namespaceManager.AddNamespace( "prefix", "http://www.MyNamespace.ca/MyPath" ); // We add an explicit prefix mapping for our query.
var result = myXDocument.XPathSelectElement(
"//prefix:Plugin/prefix:UI[1]/prefix:PluginPageCategory[1]/prefix:Page[1]/prefix:Group[1]/prefix:CommandRef[2]",
namespaceManager
); // We use that prefix against the elements in the query.
Console.WriteLine(result); // <CommandRef ...> element is printed.
Hope this helps.

This should probably be a comment on #Cumbayah's post, but I can't seem to leave comments on anything.
You are probably better off using something like this instead of using XmlReader to get the nametable.
var xml = ... XML from your post ...;
var myXDocument = XDocument.Parse(xml);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("prefix", "http://www.MyNamespace.ca/MyPath");
var result = ...;

The easiest way in your case is to use XPath axes and node test for node name and position to select the element.
Your XPath selection:
myXDocument.XPathSelectElement("//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]", myXDocument.Root.CreateNavigator());
Can be easily translate to:
myXDocument.XPathSelectElement("/child::node()[local-name()='Plugin']/child::node()[local-name()='UI'][position()=1]/child::node()[local-name()='PluginPageCategory'][position()=1]/child::node()[local-name()='Page'][position()=1]/child::node()[local-name()='Group'][position()=1]/child::node()[local-name()='CommandRef'][position()=2]");
There is no need to create and pass XmlNamespaceManager as parameter.

There is a way to do it without any change to the xpath. The solution I've found is to remove the namespace when parsing the XML into the XDocument.
Here is an exemple:
var regex = #"(xmlns:?[^=]*=[""][^""]*[""])";
var myXDocument = XDocument.Parse(Regex.Replace("MyXmlContent", regex, "", RegexOptions.IgnoreCase | RegexOptions.Multiline))
Now that the namespace is gone, it is easyer to manipulate.

Related

XDocument not returning expected elements

I've done my research and tried many trials, but this has me stumped. I'm probably being thick, but given the follwing XML document, how can I most easly find the UnitTestResult elements?
<?xml version="1.0" encoding="UTF-8"?>
<TestReport>
<TestRun id="5cbd568d-02e4-4003-96c7-3d82cc0c2060" name="neil#HP6550BTS2 2014-09-08 15:23:16" runUser="AS\neil" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2006">
<Results>
<UnitTestResult executionId="c3194ea8-adec-4e50-a4b6-45e244c50963" testId="8e220518-d603-0136-5937-819380dd4738" testName="NewRowMatrixResourceBookingTest" computerName="HP6550BTS2" duration="00:00:00.0204886" startTime="2014-09-08T15:23:32.3595341+01:00" endTime="2014-09-08T15:23:33.8396821+01:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="2fc4d891-506d-45de-8c61-cf6aa44e8eb2">
<Output>
</Output>
</UnitTestResult>
<UnitTestResult executionId="c20b289e-4af5-4827-a35d-39360dec984a" testId="d48055d1-0a28-af07-4490-2bfca70dbbcf" testName="MatrixScreen_Checkout_NewScreenVersionLockedReason1" computerName="HP6550BTS2" duration="00:00:07.1635460" startTime="2014-09-08T15:23:33.8466828+01:00" endTime="2014-09-08T15:23:41.0143995+01:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="2fc4d891-506d-45de-8c61-cf6aa44e8eb2">
<Output>
</Output>
</UnitTestResult>
</Results>
</TestRun>
</TestReport>
I've tried
xdoc.Descendants("UnitTestResult")
and
xdoc.Elements("TestReport").Elements("TestRun").Elements("Results").Elements("UnitTestResult")
and also
var ns = "http://microsoft.com/schemas/VisualStudio/TeamTest/2006";
var x = from result in testReport.Elements("TestReport").Elements(XName.Get(ns, "TestRun")).Elements("Results").Elements("UnitTestResult")
and various other variations, but they all yield an empty enumeration.
What am I doing wrong?
How about just
XNamespace ns = "http://microsoft.com/schemas/VisualStudio/TeamTest/2006";
yourXmlDocument.Descendants(ns + "UnitTestResult");
Notice the explicit definition of XNamespace, otherwise the compiler will infer the type as string not XNamespace.
Unless explicitly declared otherwise, descendants of the node where default namespace declared inherits the same namespace. So you need to use the same namespace prefix for <Results> and <UnitTestResult> :
XNamespace ns = "http://microsoft.com/schemas/VisualStudio/TeamTest/2006";
var x = from result in xdoc.Elements("TestReport")
.Elements(ns+"TestRun")
.Elements(ns+"Results")
.Elements(ns+"UnitTestResult");
All other answers include namespace declarations, which might be right solutions. You might want to ignore namespaces with namespace-agnostic solution:
xdoc.Descendants()
.Where(node => node.Name.LocalName == "UnitTestResult")
which would yield results regardless of defined namespaces
Although using the XmlDocument class rather than the XDocument class, I had similar problems a while ago when trying to manipulate a .testsettings file to ensure that Code Coverage was enabled. The code I used to get this working is below, which you could try using as a basis to get yourself up and running. I think the key part is using the ms namespace suffix as well as declaring the namespace.
XmlDocument testSettings = new XmlDocument();
testSettings.Load(fullSettingsPath);
// Load the XML Namespace manager we need for reading the .testsettings file
XmlNamespaceManager xmlnsm = new XmlNamespaceManager(testSettings.NameTable);
xmlnsm.AddNamespace("ms", "http://microsoft.com/schemas/VisualStudio/TeamTest/2010");
XmlNodeList coverageNodes = testSettings.SelectNodes(
#"ms:TestSettings/ms:Execution/ms:AgentRule/ms:DataCollectors/ms:DataCollector[#uri='datacollector://microsoft/CodeCoverage/1.0']/ms:Configuration/CodeCoverage",
xmlnsm);
private void AtribesAndInnerTexts(string pathToFile)
{
var doc = new System.Xml.XmlDocument();
doc.LoadXml(File.ReadAllText(pathToFile));
// desired node in this case UnitTestResult
var UnitTestResults = doc.GetElementsByTagName("UnitTestResult");
foreach (var unitTestResult in UnitTestResults)
{
Console.WriteLine(((XmlElement) unitTestResult).GetAttribute("executionId"));
Console.WriteLine(((XmlElement) unitTestResult).InnerText);
}
}

C#: Read XML Attribute

Using C#2.0 and VIsualStudio2005
I'm trying to access the "DisplayName" inside "MonitorResponseRecord"
from an XML file like the one Below:
<Magellan xsi:schemaLocation="http://tempuri.org/XMLSchema.xsd ..\Schema\Configuration.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/XMLSchema.xsd">
<SchemaVersion>1.0</SchemaVersion>
<MonitorScope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="CleanStationChemicalManifoldFeed5" xmlns="http://tempuri.org/XMLSchema.xsd">
<PersonalSafety>
<MonitorResponseRecord Enabled="true" DisplayName="ChemicalManifoldFeed5ControllerFault">
<ExpressionMonitor>
<Expression>(ChemicalManifold.Feed5.DispenseValve = Open) and ((ChemicalManifold.Feed5.ViolatedRegion = HighProcess) or (ChemicalManifold.Feed5.ViolatedRegion = LowProcess) or (ChemicalManifold.Feed5.ViolatedRegion = Minimum))</Expression>
<TestInterval>0.1</TestInterval>
<MinimumTimeBetweenResponses>5</MinimumTimeBetweenResponses>
</ExpressionMonitor>
<Response>
<PostAlarm>
<AlarmName>ChemicalManifold_Feed5_ControllerFault</AlarmName>
<Parameter1 />
<Parameter2>Alarm Region = {ChemicalManifold.Feed5.ViolatedRegion}</Parameter2>
<Parameter3>{RecipeName}-{StepName}</Parameter3>
<Parameter4>FlowSetpoint = {ChemicalManifold.Feed5.Setpoint}-LPM, ActualFlow = {ChemicalManifold.Feed5.FlowMeter}-LPM</Parameter4>
</PostAlarm>
<ResponseEvent>
<TargetResource />
<Event>PA</Event>
<Reason>ChemicalSupply</Reason>
</ResponseEvent>
</Response>
</MonitorResponseRecord>
</PersonalSafety>
<PersonalSafety>
<MonitorResponseRecord Enabled="true" DisplayName = "PressureValveFailure">
...
...
...and soon
My current C# attempt is as follows, BUT it always hangs up when I try to XmlDocument.Load("");
XmlDocument doc = new XmlDocument();
doc.Load("../UMC0009.Configuration.Root.xml");
string attrVal = doc.SelectSingleNode("MonitorResponseRecord/#DisplayName").Value;
BUUUUT won't work :/ any help out there?
UPDATE:
the exception I recieve is as follows, and occures at the doc.Load("...") line:
{"Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function."} System.Exception {System.Xml.XPath.XPathException}
Your XPath query will be relative to the document root:
doc.SelectSingleNode("MonitorResponseRecord/#DisplayName")
To make it search anywhere in the doc prefix it with double slash:
doc.SelectSingleNode("//MonitorResponseRecord/#DisplayName")
If that still doesn't work I would try the above example after stripping out all those namespace declarations on the two root nodes.
Otherwise, with the namespace declarations you may find you need to define XML namespace mappings and use prefixes in your XPath like:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("x", "http://tempuri.org/XMLSchema.xsd");
doc.SelectSingleNode("//x:MonitorResponseRecord/#DisplayName")
What about:
XmlDocument doc = new XmlDocument();
doc.Load("UMC0009.Configuration.Root.xml");
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ns", "http://tempuri.org/XMLSchema.xsd");
string attrVal = doc.SelectSingleNode("//ns:MonitorResponseRecord/#DisplayName", nsmgr).Value;
Using a namespace manager, specify your namespace URI and use it in your XPath.
It works for me.

XPathSelectElement always returns null

Why is this Xpath not working using XDocument.XPathSelectElement?
Xpath:
//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]
XML
<Plugin xmlns="http://www.MyNamespace.ca/MyPath">
<UI>
<PluginPageCategory>
<Page>
<Group>
<CommandRef>
<Images>
</Images>
</CommandRef>
<CommandRef>
<Images>
</Images>
</CommandRef>
</Group>
</Page>
</PluginPageCategory>
</UI>
</Plugin>
C# Code:
myXDocument.XPathSelectElement("//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]", myXDocument.Root.CreateNavigator());
When namespaces are used, these must be used in the XPath query also. Your XPath query would only work against elements with no namespace (as can be verified by removing the namespace from your XML).
Here's an example showing how you create and pass a namespace manager:
var xml = ... XML from your post ...;
var xmlReader = XmlReader.Create( new StringReader(xml) ); // Or whatever your source is, of course.
var myXDocument = XDocument.Load( xmlReader );
var namespaceManager = new XmlNamespaceManager( xmlReader.NameTable ); // We now have a namespace manager that knows of the namespaces used in your document.
namespaceManager.AddNamespace( "prefix", "http://www.MyNamespace.ca/MyPath" ); // We add an explicit prefix mapping for our query.
var result = myXDocument.XPathSelectElement(
"//prefix:Plugin/prefix:UI[1]/prefix:PluginPageCategory[1]/prefix:Page[1]/prefix:Group[1]/prefix:CommandRef[2]",
namespaceManager
); // We use that prefix against the elements in the query.
Console.WriteLine(result); // <CommandRef ...> element is printed.
Hope this helps.
This should probably be a comment on #Cumbayah's post, but I can't seem to leave comments on anything.
You are probably better off using something like this instead of using XmlReader to get the nametable.
var xml = ... XML from your post ...;
var myXDocument = XDocument.Parse(xml);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("prefix", "http://www.MyNamespace.ca/MyPath");
var result = ...;
The easiest way in your case is to use XPath axes and node test for node name and position to select the element.
Your XPath selection:
myXDocument.XPathSelectElement("//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]", myXDocument.Root.CreateNavigator());
Can be easily translate to:
myXDocument.XPathSelectElement("/child::node()[local-name()='Plugin']/child::node()[local-name()='UI'][position()=1]/child::node()[local-name()='PluginPageCategory'][position()=1]/child::node()[local-name()='Page'][position()=1]/child::node()[local-name()='Group'][position()=1]/child::node()[local-name()='CommandRef'][position()=2]");
There is no need to create and pass XmlNamespaceManager as parameter.
There is a way to do it without any change to the xpath. The solution I've found is to remove the namespace when parsing the XML into the XDocument.
Here is an exemple:
var regex = #"(xmlns:?[^=]*=[""][^""]*[""])";
var myXDocument = XDocument.Parse(Regex.Replace("MyXmlContent", regex, "", RegexOptions.IgnoreCase | RegexOptions.Multiline))
Now that the namespace is gone, it is easyer to manipulate.

SelectSingleNode return null - even with namespace

I know this question has been asked in a similar fashion before, but I can't seem to get this working.
I have some xml:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<Research xmlns="http://www.rixml.org/2005/3/RIXML" xmlns:xalan="http://xml.apache.org/xalan" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" createDateTime="2011-03-29T15:41:48Z" language="eng" researchID="MusiJvs3008">
<Product productID="MusiJvs3008">
<StatusInfo currentStatusIndicator="Yes" statusDateTime="2011-03-29T15:41:48Z" statusType="Published" />
<Source>
<Organization type="SellSideFirm" primaryIndicator="Yes">
<OrganizationID idType="Reuters">9999</OrganizationID>
And I'm trying to read values using xpath:
XPathDocument xmldoc = new XPathDocument(xmlFile);
XPathNavigator nav = xmldoc.CreateNavigator();
XmlNamespaceManager nsMgr = new XmlNamespaceManager(nav.NameTable);
nsMgr.AddNamespace(string.Empty, "http://www.rixml.org/2005/3/RIXML");
XPathNavigator result = nav.SelectSingleNode("/Research", nsMgr); // <-- Returns null!
But even a simple select of the root node returns null! I am sure I have something wrong with my namespace. Can someone please help?
Ideally I want simple lines that will let me select values from the xml file, i.e.
String a = xmlDoc.SelectSingleNode(#"/Research/Product/Content/Title").Value;
BTW, I have no (direct) control over the XML file content.
I don't believe you can use an empty namespace alias and have it used automatically by the XPath expression. As soon as you use an actual alias, it should work though. This test is fine, for example:
using System;
using System.Xml;
using System.Xml.XPath;
class Test
{
static void Main()
{
string xmlFile = "test.xml";
XPathDocument xmldoc = new XPathDocument(xmlFile);
XPathNavigator nav = xmldoc.CreateNavigator();
XmlNamespaceManager nsMgr = new XmlNamespaceManager(nav.NameTable);
nsMgr.AddNamespace("x", "http://www.rixml.org/2005/3/RIXML");
XPathNavigator result = nav.SelectSingleNode("/x:Research", nsMgr);
Console.WriteLine(result);
}
}
Do you have to use XPath and XPathDocument, by the way? I tend to find that LINQ to XML is a much more pleasant API, particularly when it comes to namespaces. If you're using .NET 3.5 and you have no particular requirement to use XPath, I'd suggest you check it out.
Make the following changes
nsMgr.AddNamespace("x", "http://www.rixml.org/2005/3/RIXML");
XPathNavigator result = nav.SelectSingleNode("/x:Research", nsMgr);
i could post, and answer my own question, for the native equivalent of this code. Instead i'll just add it as an answer to the end.
When using the native IXMLDOMDocument (version 6) object:
//Give the default namespace as alias of "x"
document.setProperty("SelectionNamespaces","xmlns:x='http://www.rixml.org/2005/3/RIXML'");
//Query for the nodes we want
document.selectSingleNode("/x:Research");
Bonus Question: Why, oh why, does no Xml Document object model query the default namespace when no namespace is specified... sigh

Can i create a XmlNamespaceManager object from the xml file i am about to read?

I have some c# code running on sharepoint that i use to check inside the xml of an infopath document to see if i should checking the document or discard the document.
The code is working fine for a couple of different form templates i have created but is failing on my latested one.
I have discovered that the XmlNamespaceManager i am creating contains the wrong diffinition for the "MY" namepsace.
I'll try to explain
I have this code to decalre my XmlNamespaceManager
NameTable nt = new NameTable();
NamespaceManager = new XmlNamespaceManager(nt);
// Add prefix/namespace pairs to the XmlNamespaceManager.
NamespaceManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
NamespaceManager.AddNamespace("xhtml", "http://www.w3.org/1999/xhtml");
NamespaceManager.AddNamespace("dfs", "http://schemas.microsoft.com/office/infopath/2003/dataFormSolution");
NamespaceManager.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-07-14T13:45:59");
NamespaceManager.AddNamespace("xd", "http://schemas.microsoft.com/office/infopath/2003");`
I can then use the following line of code to search for the xml i am after
XPathNavigator nav = xml.CreateNavigator().SelectSingleNode("//my:HasSaved", NamespaceManager);
nav.Value then gets be the data i want.
This all works fine on a couple of my form templates. I ahve a new form template and have discovered that i need to use thi line instead
NamespaceManager.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-11-30T17:39:37");
The date is different.
My problem is that i cannot add this twice as only 1 set of forms will work.
So my question is. Is there a way i can generate the NamespaceManager object from the XML file as this is all contained in the header?
I have not been able to find a simple way round this.
I found a way of doing this. Instead of adding the "my" namespace it can be pulled from the XmlDocument object. This might just be a bit of luck that it works this way but i'm happy with it.
NamespaceManager.AddNamespace("my", formXml.DocumentElement.NamespaceURI
formXML is an XmlDocument created from the infopath XML
One option would be to try to load the xml node using the first namespace, if that doesn't give any results, call PushScope(), override the first namespace definition, select, etc...
var doc = new XmlDocument();
doc.LoadXml(#"<?xml version=""1.0""?>
<root xmlns:my=""http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-11-30T17:39:37"" value=""1"">
<my:item>test</my:item>
</root>");
var nameTable = new NameTable();
var namespaceManager = new XmlNamespaceManager(nameTable);
namespaceManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
namespaceManager.AddNamespace("xhtml", "http://www.w3.org/1999/xhtml");
namespaceManager.AddNamespace("dfs", "http://schemas.microsoft.com/office/infopath/2003/dataFormSolution");
namespaceManager.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-07-14T13:45:59");
namespaceManager.AddNamespace("xd", "http://schemas.microsoft.com/office/infopath/2003");
// n will be null since the namespace url doesn't match
var n = doc.SelectSingleNode("descendant::my:item", namespaceManager);
namespaceManager.PushScope();
namespaceManager.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-11-30T17:39:37");
// will work
n = doc.SelectSingleNode("descendant::my:item", namespaceManager);
namespaceManager.PopScope();
Another option is to parse the attributes in the header and look for any contained namespaces
foreach(XmlAttribute attribute in doc.DocumentElement.Attributes)
{
var url = namespaceManager.LookupNamespace(attribute.LocalName);
if(url != null && url != attribute.Value)
{
namespaceManager.RemoveNamespace(attribute.LocalName, url);
namespaceManager.AddNamespace(attribute.LocalName, attribute.Value);
}
}
You don't need to do anything like this. Just use "my2" or something for the second namespace. You then need the new namespace prefix for any nodes that use the new namespace, and use the old "my" namespace prefix for all the nodes that still use the old namespace.

Categories

Resources