XmlNode.SelectSingleNode returns element outside current? - c#

my problem is like this. Let's say i have xml like this
<root>
<child Name = "child1">
<element1>Value1</element1>
<element2>Value2</element2>
</child>
<child Name = "child2">
<element1>Value1</element1>
<element2>Value2</element2>
<element3>Value3</element3>
</child>
</root>
I have a method that gets as parameter XmlNode "node". Lets say "node" has value "child1" Then i try like this:
node.SelectSingleNode( "//element3" );
The problem is this code returns element3 from "child2". What i want is if there is no child "element3" of "node" to return null so i add it by hand.
Best Regards,
Iordand

The XPath expression you have isn't what you want.
Replace it with this:
node.SelectSingleNode( "element3" );
And you'll get the result you're looking for.

The following work perfect when i want to run xpath on the specified node.
XmlNodeList nodes = xmlDoc.SelectNodes(".//Child");

The "//" is a global look up.
What you'll need to do is get a list of all children
XmlNodeList nodes = xmlDoc.SelectNodes("//Child");
loop through that list and do a
XmlNode node = nodes.SelectSingleNode("element3");
This will return null if it's not there, and will step through every child looking.

the problem here is the XPath expression you are using, try it without the '//'. Like that:
node.SelectSingleNode( "element3" );
Read more here .

Related

XPath for attribute value that ends with a string?

I'm having trouble selecting an element with an attribute that ends with a certain value.
XML looks like
<root>
<object name="1_2"><attribute name="show" value="example"></object>
<object name="1_1"><attribute name="show" value="example"></object>
<object name="2_1"><attribute name="show" value="example"></object>
</root>
So I need to extract all values from attributes in objects ends with _1, how can I do that?
I did this code
XmlNodeList childnodes = xRoot.SelectNodes("//Object[#Name='1_1']");
foreach (XmlNode n in childnodes)
Console.WriteLine(n.SelectSingleNode("Attribute[#Name='show']").OuterXml);
but I can't find how to search for the part of attributes name and how to get the exact value of target parameter.
First note that XML and XPath are case sensitive, so Object is different than object, and Name is different than name.
XPath 2.0
This XPath 2.0 expression,
//object[ends-with(#name,'_1')]
will select all object elements whose name attribute value ends with _1.
XPath 1.0
XPath 1.0 lacks the ends-with() function but can achieve the same result with a bit more work:
ends-with($s, $e) ≡ (substring($s, string-length($s) - string-length($e) +1) = $e)
Applied to your case where $s is #name and $e is '_1', the above simplifies to this expression:
//object[substring(#name, string-length(#name) - 1) = '_1']
If C# supports XPath 2.0 you should be able to use:
XmlNodeList childnodes = xRoot.SelectNodes("//object[ends-with(#name, '_1')]");
if not then a slightly longer version should work:
XmlNodeList childnodes = xRoot.SelectNodes("//object[substring(#name, string-length(#name) - 1) = '_1']");
Also your xml is not valid as you need to close the attribute elements:
<root>
<object name="1_2"><attribute name="show" value="example"/></object>
<object name="1_1"><attribute name="show" value="example"/></object>
<object name="2_1"><attribute name="show" value="example"/></object>
</root>

Select an XML node with a given child value and update a different child element

I have the following xml file:
<LabelImageCreator xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PrintFieldList>
<PrintFieldDefinition>
<FieldName>Facility</FieldName>
<DataParameterName>Address</DataParameterName>
<FieldFont>
<FontName>Arial</FontName>
<FontSize>10</FontSize>
<FontStyle>Regular</FontStyle>
</FieldFont>
<CurrentDataValue/>
</PrintFieldDefinition>
<PrintFieldDefinition>
<FieldName>Country</FieldName>
<DataParameterName>CountryofOrigin</DataParameterName>
<WrapField>false</WrapField>
<FieldFont>
<FontName>Arial</FontName>
<FontSize>8</FontSize>
<FontStyle>Regular</FontStyle>
</FieldFont>
<CurrentDataValue/>
<TextPrefix>Produce of </TextPrefix>
</PrintFieldDefinition>
<PrintFieldList>
<LabelImageCreator>
I have to select the attribute with field name Facilityand add the address(eg: No 2546, Gorrge street, California, US) to <CurrentDataValue/> field and save it.
I tried with the below code,
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(path);
var node = xmlDocument.DocumentElement.SelectSingleNode(
"./PrintFieldList/PrintFieldDefinition[#FieldName='Facility']");
Above code while debuging it is not working. Can any one guide me how to select and update the xml attribute.
A couple of minor issues:
You need to start from the root element LabelImageCreator
FieldName is an element, not an attribute, so hence FieldName and not #FieldName
The closing tags on the Xml Document don't match up.
If you want to select the child element CurrentDataValue of parent PrintFieldDefinition with the child FieldName with value Facility:
var node = xmlDocument.DocumentElement.SelectSingleNode(
"/LabelImageCreator/PrintFieldList/PrintFieldDefinition[FieldName='Facility']/CurrentDataValue");
Changing the value is then simply:
node.InnerText = "No 2546, Gorrge street, California, US";
I would use XDocument instead of XmlDocument (it allows you to use linq which in my opinion, is easier than using xpath).
You can find your node like this and I believe you can update them too (first search and get the value, then search again and update on the other node).
Example:
var nodesMatching = from node in myXDocument.Descendants()
where node.Name.LocalName.Equals("mySearchNode")
select node;
var node = nodesMatching.FirstOrDefault();

xpath to select all leaf nodes with given ancestors attribute

I have a batch of xmls where each tag has an attribute isOptional. An example of such xml is
<node1 isOptional="False">
<node2 isOptional="True">
<node22 isOptional="False">
<node3 isOptional="False">text4</node3>
</node22>
</node2>
<node4 isOptional="False">
<node5 isOptional="False">
<node6 isOptional="True">text3</node6>
<node7 isOptional="False">
<node8 isOptional="True">text2</node8>
<node9 isOptional="False">text1</node9>
</node7>
</node5>
</node4>
</node1>
I need to write xpath to select all leaf nodes which do not have ancessors with #isOptional="True". For this example the result should be:
<node9 isOptional="False">text1</node9>
Actually I need something like this:
//*[not(*) and #isOptional="False" and count(ancestor::*[#isOptional = "True"] = 0)]
Could you please help me to get the correct xpath?
An XPath like this one should work:
//*[not(child::*) and not(ancestor-or-self::*[#isOptional = 'True'])]
I think you forgot to mention one additional requirement. What you said:
I need to write xpath to select all leaf nodes which do not have ancessors with #isOptional="True"
would map to the following XPath expression:
//*[not(*) and not(ancestor::*[#isOptional = 'True'])]
^^^ "all"
^^^^^^^ "leaf nodes"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "no ancestor where
#isOptional equals 'True'"
which would yield (individual results separated by ------):
<node6 isOptional="True">text3</node6>
-----------------------
<node8 isOptional="True">text2</node8>
-----------------------
<node9 isOptional="False">text1</node9>
But I assume you meant to add: Additionally, the isOptional attribute of the target nodes must not be set to True either. This results in the following path expression, already correctly suggested by potame:
//*[not(*) and not(ancestor-or-self::*[#isOptional = 'True'])]
and the only result is
<node9 isOptional="False">text1</node9>
because now the XPath expression contains the ancestor-or-self:: axis, which includes the context node itself.

Unable to remove root node from an xml document using linq to xml c#

Actullay, I need to get all elements except root node from first xml document and so that I could insert them as child nodes to an element(that has same name as a previous doc's root name) in a new document.
So I have tried various ways to achieve it, one of them is removing the root node of first and then trying to add elements to a new one's as given below:
I have tried the following but could not achieve it.
XDocument testDoc = XDocument.Parse(Mydocument);
testDoc.Descendants().Where(e => e.Name.LocalName == "rootName").Select(m=>m).Single().Remove();
var resultDoc = testDoc;
The above code is giving me an empty "{}" result.
my xml document looks something like the below one's:
<rootName xsi:schemaLocation="" xmlns:xsi="" xmlns="">
<main>
<child>
</child>
<anotherchild>
</anotherchild>
</main>
</rootName>
And another way is getting all the elements of first document as the following:
var resultDoc = testDoc.Descendants(ns + "rootName").Elements();
the above statement is giving me the list of elements in the "testDoc" which
I need to do something like below, I am clueless:
<AnotherDocument xsi:schemaLocation="" xmlns:xsi="" xmlns="">
<firstNode>
<rootName>
<main>
<child>
</child>
<anotherchild>
</anotherchild>
</main>
</rootName>
</firstNode>
Please let me know how to insert those elements in a new document as above if I am correct else let me know the way to resolve this issue.
Thanks in advance.
You can replace content of rootName element in another document with elements from first document root:
var xDoc = XDocument.Parse(Mydocument);
var anotherXDoc = XDocument.Load("anotherdata.xml");
XNamespace ns = "http://..."; // your xml namespance
var rootName = anotherXDoc.Descendants(ns + "rootName").First();
rootName.ReplaceNodes(xDoc.Root.Elements());
By this page_nodes gets all nodes now you can used all node by for each loop
var page_nodes = from p in xdoc.Descendants.Where(e => e.Name.LocalName == "rootName").Select(m=>m).Single().Remove() select p;
foreach (var page_node in page_nodes)
{
//Do stuff
}
Wouldn't removing a root node, remove all its child nodes as well? The result you are getting is to be expected I think. You should probably get all the children of the root and copy them to your new document.

Inserting XML Fragment after last specific node/element

I want to add an XML fragment to the last element to an XML document and I having problems i.e. the error I get is:
"The reference node is not a child of
this node".
So my existing XML document looks like this:
<MAP>
<LAYER name ="My first Layer">
<DATASET name="foo dataset" />
<SYMBOLOGY>
<SYMBOL colour="red" />
</SYMBOLOGY>
</LAYER>
<LAYER name="My second Layer">
<DATASET name="bar dataset" />
<SYMBOLOGY>
<SYMBOL colour="blue" />
</SYMBOLOGY>
</LAYER>
</MAP>
The XML fragment I want to insert after the last LAYER element is:
<LAYER name="My third Layer">
<DATASET name="whatever dataset" />
<SYMBOLOGY>
<SYMBOL colour="yellow" />
</SYMBOLOGY>
</LAYER>
The code I am using is:
XmlDocumentFragment xmlDocFrag = xmlDocument.CreateDocumentFragment();
xmlDocFrag.InnerXml = inputXML; //which is basically the third layer example - see above.
XmlElement rootElement = xmlDocument.DocumentElement;
XmlNode lastLayerNode = rootElement.SelectSingleNode(#"//LAYER[last()]");
rootElement.InsertAfter(xmlDocFrag, lastLayerNode); //error raised here.
Any ideas on what I'm doing wrong here would be much appreciated. My XPath query seems find and it seems to select the correct last layer it just won't insert after it for some bizarre reason.
UPDATE/SOLUTION - How to do this with XPATH
Finally figured it out in XPath - see the code below, I think it was down to basically not selecting the correct parent node in the first place, it's incorrect to select the last LAYER then try and InsertAfter() on this node. Better to select the level above i.e. MAP then AppendChild(). See below:
XmlDocumentFragment xmlDocFrag = xmlDocument.CreateDocumentFragment();
xmlDocFrag.InnerXml = inputXML;
XmlElement mapElement = (XmlElement)xmlDocument.SelectSingleNode(#"//MAP[last()]");
mapElement.AppendChild(xmlDocFrag);
Thanks to all the replies and help too :)
Taking into consideration that you need this to work with Framework 2.0, here's another solution:
string xml = "<map><layer>1</layer><layer>2</layer></map>";
string addMe = "<layer>3</layer>";
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
XmlDocumentFragment xmlDocFrag = xmlDocument.CreateDocumentFragment();
xmlDocFrag.InnerXml = addMe;
XmlElement rootElement = xmlDocument.DocumentElement;
rootElement.AppendChild(xmlDocFrag);
This results in:
<map><layer>1</layer><layer>2</layer><layer>3</layer></map>
Things look pretty good, but I would first try to avoid the xpath selection for the last node, and instead just use this:
rootElement.InsertAfter(xmlDocFrag, rootElement.LastChild);
I had similar issue, I used the ImportNode method to solve it
Here is a small example how you can use it to add node from different xml (stored in string) to your example at desired node in xml tree
string xmlstring =#"<tag>.....</tag>"; // holds xml tree to be appended
XmlDocument xml2 = new XmlDocument();
xml2.Load(#"path_of_main_xml");
XmlDocument xml1 = new XmlDocument();
xml1.Load(new StringReader(xmlString));
// get the node you want to import which in this icase is string
XmlNode elem = xml1.DocumentElement;
// use importNode to import it
XmlNode impnode = xml2.ImportNode(elem,true);
// get the node list of all node of particular tag name
XmlNodeList eNode = xml2.GetElementsByTagName("tag_name_of_parent");
eNode[0].AppendChild(impnode); // append new node
// write back the updates to same file
XmlWriter writer = XmlWriter.Create(#"path_of_main_xml");
xml2.Save(writer);

Categories

Resources