Say I have an XmlDocument
that I generate 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 before node <PID.3>
. ( <MSH.9>
is also before <MSH.6>
.)
Restructuring my generation would cause my nice clean code to become very messy.
Is there a way to sort the nodes so that it will sort alpha until it hits the last period then sort numeric (if the last values are numbers)?
By "numeric sorting" I mean it will look at the whole number rather than char by char. (So 18 > 3).
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 it: (I should get paid for this...)
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's 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 may be able to write this cleaner and meaner, but this should get you going.
Was interested in your question so here is my two cents.
I've implemented IComparer<T>
to handle the element comparisons and two methods which handle recursion. The code could be cleaned up a bit but I've pasted in the console application code I created to show you my solution which I think worked out well.
Edit: To make this easier to read I've broken this down into the core parts though I've left the functional console app
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);
}
}
Methods for handling the recursion:
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);
}
}
}
utilizing System.Xml.Linq
, this code may help you.
Usage:
string xmlString=
@"
....your string.....
";
XDocument xDoc = XDocument.Load(new StringReader(xmlString));
XDocument newXDoc = SortXml(xDoc);
Console.WriteLine(newXDoc);
SortXml
function:
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);
}
}
Yet another attempt, using a modified Dotnet.Commons .Xml.
Get the XmlUtils class here .
Create a custom Comparer that has all your logic (this will get you going)
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 modify the XmlUtils SortElements function, add this to 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)
这个来自Java2S.com的解决方案会有用吗?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.