使用C#按Alpha.Numeric对XML节点排序

时间:2012-01-05 20:41:17

标签: c# .net xml .net-4.0 xmldocument

我说我生成的XmlDocument InnerXml<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> ,如下所示:

<PID.18>

正如您所见,节点<PID.3>位于节点<MSH.9>之前。 (<MSH.6>也在{{1}}之前。)

重组我的一代会让我干净整洁的代码变得非常混乱。

有没有办法对节点进行排序,以便它排序alpha直到它到达最后一个句点然后排序数字(如果最后的值是数字)?

通过“数字排序”我的意思是它将查看整数而不是char的char。 (所以18> 3)。

5 个答案:

答案 0 :(得分:3)

显而易见的答案是肯定的。

如果这是您想要的结果:

<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>

然后这个班级会这样做:(我应该为此付出代价......)

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;
        }
    }
}

有人可能会写出这种更清洁和更清洁的东西,但这应该让你顺利。

答案 1 :(得分:3)

对你的问题感兴趣所以这是我的两分钱。

我已经实现了IComparer<T>来处理元素比较和两个处理递归的方法。代码可以清理一下但是我已经粘贴在我创建的控制台应用程序代码中,以向我展示我认为很好的解决方案。

编辑 为了让这更容易阅读,我已经将其分解为核心部分,但我已离开功能控制台应用

IComparer<T>实施:

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);
    }
}

处理递归的方法:

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));
}

功能控制台应用程序:

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);
        }
    }
}

答案 2 :(得分:2)

利用System.Xml.Linq,此代码可以为您提供帮助。

用法:

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);
    }
}

答案 3 :(得分:1)

又一次尝试,使用修改后的Dotnet.Commons。Xml。

获取XmlUtils类here

创建一个具有所有逻辑的自定义Comparer(这将帮助您)

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]);
        }
    }

然后修改XmlUtils SortElements函数,将其添加到顶部:

CustomComparer comparer = new CustomComparer();

并更改行:

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

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

答案 4 :(得分:0)

solution from Java2S.com会有用吗?