Sort with XPath - Not XSL - c#

Is there any way in XPath DOM Programming with using System.Xml to run selectNodes (XPATH) with a sort parameter?
For example, with the following XML and program writes values in the same order as the document (descending). Is there a way using XPath to get the values in ascending order?
NOTE. Of course, you could pre-sort in XSL, however I need to update the values as I'm looping through them. Since XSL gives me a sorted copy of the elements, not the actual elements themselves, I can't use XSL.
Here's some XML, a program out output
public static void Main() {
XmlDocument xml = new XmlDocument();
xml.Load( "t.xml" );
// SelectNodes gets in document order, but I want in
// ascending order based on #value
foreach( XmlNode ndNode in xml.SelectNodes( "/xml/ele" ) ) {
Console.WriteLine( ndNode.Attributes["value"].Value );
}
}
Here's the XML
<xml>
<ele value='3' test='Third'/>
<ele value='2' test='Second'/>
<ele value='1' test='First'/>
</xml>
Finally the output in document (descending) order. I'd like an XPath that returns the nodes in ascending order.
3
2
1
PS, I'm using System.Xml in Visual Studio 2008 .NET 3.5

XPath doesn't supprort ordering, however you may look at AddSort method.

No, there is no way to do sorting in XPath alone.
But you can easily use LINQ to sort your collection of nodes by whatever values you want.
XmlDocument xml = new XmlDocument ();
xml.LoadXml("<xml> <ele value='3' test='Third'/> <ele value='2' test='Second'/> <ele value='1' test='First'/> </xml>");
var nodes = xml.SelectNodes( "/xml/ele" ).Cast<XmlNode>().OrderBy(ndNode => ndNode.Attributes["value"].Value);

For anyone stumbling upon this question later, XPath 3.1 has a sort function.

Related

Get text from all instances of XML element using C#

I need to parse XML files where I can't predict the structure. I need to fill a string array with the inner text from every instance of the below tag no matter where they occur in the tree.
<SpecialCode>ABCD1234</SpecialCode>
Is there a simple way to accomplish this using c#?
Solution
If your XML is a string:
XDocument doc = XDocument.Parse("<SpecialCode>ABCD1234</SpecialCode>");
string[] specialCodes = doc.Descendants("SpecialCode").Select(n => n.Value).ToArray();
If your XML is a file:
XDocument doc = XDocument.Load("specialCodes.xml");
string[] specialCodes = doc.Descendants("SpecialCode").Select(n => n.Value).ToArray();
Explanation
XDocument is a handy class that allows for easy XML parsing. You'll need to add a reference to the System.Xml.Linq assembly to your project in order to use it.
The Descendents method will get all children of the XML document, which takes care of your unknown XML structure.
The Select method is a LINQ method and allows us to select a property from each node--in this case, Value.
ToArray converts the IEnumerable result from Select() to your desired result.
XmlDocument doc = new XmlDocument();
doc.Load(FILENAME);
// IN CASE OF STRING, USE FOLLOWING
//doc.LoadXml(XAML_STRING);
XmlNodeList list = doc.DocumentElement.SelectNodes("//SpecialCode");
// prefic will fetch all the SpecialCode tags from xml.

Iterate through XDocument when you dont know the structure

Is there any way to iterate through a XDocument when you dont know what the XML structure is (using c#)?
There is plenty of examples when you know the structure, like the answer to this question : C# - Select XML Descendants with Linq and C# Foreach XML Node
I've tried Descendants("A") where A is the example below - which in my foreach returns me one element with the name as the root and the value as 'all of the values concatinated into one string'
The reason I'm doing this is to anonymize certain nodes which I know the names.
The XDocument's I'm loading can be of any shape - so i've decided to just create a list which users can add to which contains these sensitive elements.
A solution I want to avoid is users creating XPath's for sensitive fields.
The XML is also sensitive so I cant share online literally but one example (out of 5) would look.
<A>
<B>
<C>
<D>
<dee>value1</dee>
<doo>value2</doo>
<date>value3</date>
<time>value4</time>
</D>
</C>
</B>
<E>
...ommited..this doc is 5000 lines long with 500~ unique node names
</E>
............
</A>
So is there a way to iterate without using Descendants?
Use .Descendants() to iterate every element.
xmlDoc.Root.Descendants()
.ToList()
.ForEach(e => Console.WriteLine(e.Name));
This is the way I went about it.
Descendants means you know the structure of the nodes before hand. Even with an empty method call to descendants (which should get everything from the root) wasn't giving me what I was expecting.
The below code should work for any XML document, without knowing the structure.
XmlDocument doc = new XmlDocument();
doc.Load(file);
using (XmlReader reader = new XmlNodeReader(doc))
{
while (reader.Read())
{
currentNodeName = reader.Name;

Get specific data from XML document

I have xml document like this:
<level1>
<level2>
<level3>
<attribute1>...</attribute1>
<attribute2>false</attribute2>
<attribute3>...</attribute3>
</level3>
<level3>
<attribute1>...</attribute1>
<attribute2>true</attribute2>
<attribute3>...</attribute3>
</level3>
</level2>
<level2>
<level3>
<attribute1>...</attribute1>
<attribute2>false</attribute2>
...
...
...
I'm using c#, and I want to go thru all "level3", and for every "level3", i want to read attribute2, and if it says "true", i want to print the corresponding attribute3 (can be "level3" without these attributes).
I keep the xml in XmlDocument.
Then I keep all the "level3" nodes like this:
XmlNodeList xnList = document.SelectNodes(String.Format("/level1/level2/level3"));
(document is the XmlDocument).
But from now on, I don't know exactly how to continue. I tried going thru xnList with for..each, but nothing works fine for me..
How can I do it?
Thanks a lot
Well I'd use LINQ to XML:
var results = from level3 in doc.Descendants("level3")
where (bool) level3.Element("attribute2")
select level3.Element("attribute3").Value;
foreach (string result in results)
{
Console.WriteLine(result);
}
LINQ to XML makes all kinds of things much simpler than the XmlDocument API. Of course, the downside is that it requires .NET 3.5...
(By the way, naming elements attributeN is a bit confusing... one would expect attribute to refer to an actual XML attribute...)
You can use LINQ to XML and reading this is a good start.
You can use an XPath query. This will give you a XmlNodeList that contains all <attribute3> elements that match your requirement:
var list = document.SelectNodes("//level3[attribute2 = 'true']/attribute3");
foreach(XmlNode node in list)
{
Console.WriteLine(node.InnerText);
}
You can split the above xpath query in three parts:
"//level3" queries for all descendant elements named <level3>.
"[attribute2 = 'true']" filters the result from (1) and only keeps the elements where the child element <attribute2> contains the text true.
"/attribute3" takes the <attribute3> childnode of each element in the result of (2).

How to get the immediate child elements of the root element using C# and XML?

<Document>
<Heading1>
<text>Heading Title</text>
<para>para1</para>
<para>para2</para>
<para>para3</para>
</Heading1>
<Heading1>
<text>2nd Heading Title</text>
<para>para4</para>
<para>para5</para>
<para>para6</para>
<Heading2>
<text>3rd Heading Title</text>
<para>para4</para>
<para>para5</para>
</Heading2>
</Heading1>
</Document>
This is XML Document. Now, i want to parse this XML file using C# (4.0). Here, I want to get all the Heading1 elements without using that element name in my program. For example, don't use document.GetElementsByTagName("Heading1");. How i get it. Guide me get out of this issue.
Thanks & Regards.
Using LINQ to XML, you can do:
var headings = yourXDocument.Root.Elements();
Using Nodes() instead of Elements() will also return text nodes and comments, which is apparently not what you want.
You can access the child elements of the document or element through the Elements() method if using LINQ to XML.
XDocument doc = ...;
var query = doc.Root.Elements();
If you're using XmlDocument, this works:
var elements = doc.SelectNodes("/*/*");
That finds all child elements of the top-level element irrespective of any of their names. It's usually safer to specify the names if you know them, so that elements with unexpected names don't get returned in your list - use /Document/Heading1 to do this.

Setting attributes in an XML document

I'm writing one of my first C# programs. Here's what I'm trying to do:
Open an XML document
Navigate to a part of the XML tree and select all child elements of type <myType>
For each <myType> element, change an attribute (so <myType id="oldValue"> would become <myType id="newValue">
Write this modified XML document to a file.
I found the XmlDocument.SelectNodes method, which takes an XPath expression as its argument. However, it returns an XmlNodeList. I read a little bit about the difference between an XML node and an XML element, and this seems to explain why there is no XmlNode.SetAttribute method. But is there a way I can use my XPath expression to retrieve a list of XmlElement objects, so that I can loop through this list and set the id attributes for each?
(If there's some other easier way, please do let me know.)
Simply - it doesn't know if you are reading an element or attribute. Quite possibly, all you need is a cast here:
foreach(XmlElement el in doc.SelectNodes(...)) {
el.SetAttribute(...);
}
The SelectNodes returns an XmlNodeList, but the above treats each as an XmlElement.
I am a big fan of System.Xml.Linq.XDocument and the features it provides.
XDocument xDoc = XDocument.Load("FILENAME.xml");
// assuming you types is the parent and mytype is a bunch of nodes underneath
IEnumerable<XElement> elements = xdoc.Element("types").Elements("myType");
foreach (XElement type in elements)
{
// option 1
type.Attribute("id").Value = NEWVALUE;
// option 2
type.SetAttributeValue("id", NEWVALUE);
}
Option 1 or 2 works but I prefer 2 because if the attribute doesn't exist this'll create it.
I'm sitting at my Mac so no .NET for me...
However, I think that you can cast an XmlNode to an XmlElement via an explicit cast.
You should be able to cast the XmlElement to an XmlNode then and get it's children Nodes using something like XmlNode.ChildNodes.

Categories

Resources