使用C#按字母数字顺序对XML节点进行排序

6

假设我有一个生成的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>之前。)

重新构造我的生成会导致我漂亮干净的代码变得非常混乱。

有没有一种方法可以对节点进行排序,使其按字母顺序排序,直到找到最后一个句点,然后按数字排序(如果最后的值是数字)?

通过“数字排序”,我指的是它将查看整个数字而不是逐个字符地查看。(所以18 > 3)。

5个回答

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

也许有人能够更简洁明了地写出这段代码,但这应该可以帮助你入门。


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

1

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

1

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

在此处获取XmlUtils类here

创建一个自定义比较器,其中包含您所有的逻辑(这将帮助您入门)。

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)

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接