Sort XML nodes by Alpha.Numeric using C #

Let's say I have an XmlDocument that I create that has InnerXml that looks like this:

 <ORM_O01> <MSH> <MSH.9> <MSG.2>O01</MSG.2> </MSH.9> <MSH.6> <HD.1>13702</HD.1> </MSH.6> </MSH> <ORM_O01.PATIENT> <PID> <PID.18> <CX.1>SecondTestFin</CX.1> </PID.18> <PID.3> <CX.1>108</CX.1> </PID.3> </PID> </ORM_O01.PATIENT> </ORM_O01> 

As you can see, node <PID.18> is in front of node <PID.3> . ( <MSH.9> also up to <MSH.6> .)

Restructuring my generation will cause my good clean code to become very dirty.

Is there a way to sort the nodes so that it sorts the alpha until it reaches the last period and then sorts the numerical values ​​(if the last values ​​are numbers)?

By "numerical sorting" I mean that he will look at the whole number, and not at char on char. (So ​​18> 3).

+6
source share
5 answers

The obvious answer is yes.

If this is the result you want:

 <ORM_O01> <MSH> <MSH.6> <HD.1>13702</HD.1> </MSH.6> <MSH.9> <MSG.2>O01</MSG.2> </MSH.9> </MSH> <ORM_O01.PATIENT> <PID> <PID.3> <CX.1>108</CX.1> </PID.3> <PID.18> <CX.1>SecondTestFin</CX.1> </PID.18> </PID> </ORM_O01.PATIENT> </ORM_O01> 

Then this class will do this: (I need to pay for it ...)

 using System; using System.IO; using System.Linq; using System.Xml.Linq; namespace Test { public class SortXmlFile { XElement rootNode; public SortXmlFile(FileInfo file) { if (file.Exists) rootNode = XElement.Load(file.FullName); else throw new FileNotFoundException(file.FullName); } public XElement SortFile() { SortElements(rootNode); return rootNode; } public void SortElements(XElement root) { bool sortWithNumeric = false; XElement[] children = root.Elements().ToArray(); foreach (XElement child in children) { string name; int value; // does any child need to be sorted by numeric? if (!sortWithNumeric && Sortable(child, out name, out value)) sortWithNumeric = true; child.Remove(); // we'll re-add it in the sort portion // sorting child children SortElements(child); } // re-add children after sorting // sort by name portion, which is either the full name, // or name that proceeds period that has a numeric value after the period. IOrderedEnumerable<XElement> childrenSortedByName = children .OrderBy(child => { string name; int value; Sortable(child, out name, out value); return name; }); XElement[] sortedChildren; // if needed to sort numerically if (sortWithNumeric) { sortedChildren = childrenSortedByName .ThenBy(child => { string name; int value; Sortable(child, out name, out value); return value; }) .ToArray(); } else sortedChildren = childrenSortedByName.ToArray(); // re-add the sorted children foreach (XElement child in sortedChildren) root.Add(child); } public bool Sortable(XElement node, out string name, out int value) { var dot = new char[] { '.' }; name = node.Name.ToString(); if (name.Contains(".")) { string[] parts = name.Split(dot); if (Int32.TryParse(parts[1], out value)) { name = parts[0]; return true; } } value = -1; return false; } } } 

Someone can write it cleaner and mean, but it should make you go.

+3
source

Your question was interesting, so here are my two cents.

I implemented IComparer<T> to handle comparing elements and two methods that handle recursion. The code could have been cleaned up a bit, but I put it into the console application code that I created to show you my solution, which I think turned out well.

Edit: To make reading easier, I broke it down into the main parts, although I left the functional console application

IComparer<T> Implementation:

 public class SplitComparer : IComparer<string> { public int Compare(string x, string y) { var partsOfX = x.Split('.'); int firstNumber; if (partsOfX.Length > 1 && int.TryParse(partsOfX[1], out firstNumber)) { var secondNumber = Convert.ToInt32(y.Split('.')[1]); return firstNumber.CompareTo(secondNumber); } return x.CompareTo(y); } } 

Recursion processing methods:

 private static XElement Sort(XElement element) { var xe = new XElement(element.Name, element.Elements().OrderBy(x => x.Name.ToString(), new SplitComparer()).Select(x => Sort(x))); if (!xe.HasElements) { xe.Value = element.Value; } return xe; } private static XDocument Sort(XDocument file) { return new XDocument(Sort(file.Root)); } 

Functional console application:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.IO; using System.Xml.Linq; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { var xml = @"<ORM_O01> <ORM_O01.PATIENT> <PID> <PID.18> <CX.1>SecondTestFin</CX.1> </PID.18> <PID.3> <CX.1>108</CX.1> </PID.3> </PID> </ORM_O01.PATIENT> <MSH> <MSH.9> <MSG.2>O01</MSG.2> </MSH.9> <MSH.6> <HD.1>13702</HD.1> </MSH.6> </MSH> </ORM_O01>"; var xDoc = XDocument.Parse(xml); var result = Sort(xDoc); Console.WriteLine(result.ToString()); Console.Read(); } private static XElement Sort(XElement element) { var xe = new XElement(element.Name, element.Elements().OrderBy(x => x.Name.ToString(), new SplitComparer()).Select(x => Sort(x))); if (!xe.HasElements) { xe.Value = element.Value; } return xe; } private static XDocument Sort(XDocument file) { return new XDocument(Sort(file.Root)); } } public class SplitComparer : IComparer<string> { public int Compare(string x, string y) { var partsOfX = x.Split('.'); int firstNumber; if (partsOfX.Length > 1 && int.TryParse(partsOfX[1], out firstNumber)) { var secondNumber = Convert.ToInt32(y.Split('.')[1]); return firstNumber.CompareTo(secondNumber); } return x.CompareTo(y); } } } 
+3
source

using System.Xml.Linq , this code can help you.

Using:

 string xmlString= @" ....your string..... "; XDocument xDoc = XDocument.Load(new StringReader(xmlString)); XDocument newXDoc = SortXml(xDoc); Console.WriteLine(newXDoc); 

SortXml :

 XDocument SortXml(XDocument xDoc) { Func<XElement, string> keyBuilder = s => s.Name.ToString().Split('.') .Aggregate("",(sum, str) => sum += str.PadLeft(32,' ')); XElement root = new XElement(xDoc.Root.Name); SortXml(root, xDoc.Elements(), keyBuilder); return new XDocument(root); } void SortXml(XElement newXDoc, IEnumerable<XElement> elems, Func<XElement, string> keyBuilder) { foreach (var newElem in elems.OrderBy(e => keyBuilder(e))) { XElement t = new XElement(newElem); t.RemoveNodes(); newXDoc.Add(t); SortXml(t, newElem.Elements(), keyBuilder); } } 
+2
source

Another attempt using the modified Dotnet.Commons .Xml.

Get the XmlUtils class here .

Create a custom Comparer that has all your logic (this will allow you to move)

 public class CustomComparer : IComparer { public int Compare(object x, object y) { string o1 = x as string; string o2 = y as string; string[] parts1 = o1.Split('.'); string[] parts2 = o2.Split('.'); // Assuming first part is alpha, last part is numeric and both of them has second part. Otherwise compare original ones. if (parts1.Length < 2 || parts2.Length < 2) return o1.CompareTo(o2); if (parts1[0].Equals(parts2[0])) { // Do a numeric compare return int.Parse(parts1[parts1.Length - 1]).CompareTo(int.Parse(parts2[parts2.Length - 1])); } else { // Just compare the first part return parts1[0].CompareTo(parts2[0]); } } 

Then change the XmlUtils SortElements function, add this at the top:

CustomComparer comparer = new CustomComparer ();

and change the line:

 if (String.Compare(node.ChildNodes[i].Name, node.ChildNodes[i-1].Name, true) < 0) 

to

 if (comparer.Compare(node.ChildNodes[i].Name, node.ChildNodes[i - 1].Name) < 0) 
+1
source
0
source

Source: https://habr.com/ru/post/905244/


All Articles