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

发布于 2024-12-25 06:57:13 字数 881 浏览 3 评论 0原文

假设我生成的 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>

如您所见,节点 位于之前节点。 ( 也在 之前。)

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

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

烛影斜 2025-01-01 06:57:13

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

如果这是你想要的结果:

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

有人可能能够写出这个更干净、更刻薄的代码,但这应该让你继续下去。

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.

痕至 2025-01-01 06:57:13

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

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

编辑: 为了使本文更易于阅读,我将其分解为核心部分,尽管我已经离开了功能控制台应用程序

IComparer实现:

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

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);
        }
    }
}
千纸鹤 2025-01-01 06:57:13

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

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);
    }
}
漆黑的白昼 2025-01-01 06:57:13

另一种尝试,使用修改后的 Dotnet.Commons.Xml。

此处获取 XmlUtils 类。

创建一个包含所有逻辑的自定义比较器(这将帮助您继续)

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)

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)
陌伤ぢ 2025-01-01 06:57:13

这个来自 Java2S.com 的解决方案有什么用吗?

Would this solution from Java2S.com be of any use?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文