使用LINQ对XML进行排序

3
我想用LINQ对XML文件进行排序。以下是一个示例XML,实际上它要大得多且更为复杂。XML应按标题升序排序。不是一次性整个XML,而是每个parentNode单独排序。叶子总在底部。文件夹或非叶子在顶部。以下XML已经很好地结构化,但是标题顺序错误。排序算法还应替换错误位置的非叶节点。 我已经有了一些代码可以完成工作,但我想知道是否有更优雅或更短的方法。目前,我必须调用递归函数来进行遍历。也许可以用另一种方式完成这个任务。 谢谢。
Rene 以下是我的XML:
<Node title="text99" leaf="no">
<Node title="text98" leaf="no">
    <Node title="text97" leaf="no">
        <Node title="text96" leaf="yes"/>
        <Node title="text95" leaf="yes"/>
    </Node>
    <Node title="text94" leaf="no">
        <Node title="text93" leaf="yes"/>
        <Node title="text92" leaf="yes"/>
    </Node>
    <Node title="text91" leaf="yes"/>
    <Node title="text90" leaf="yes"/>
</Node>
<Node title="text89" leaf="no">
    <Node title="text88" leaf="no">
        <Node title="text87" leaf="yes"/>
        <Node title="text86" leaf="yes"/>
    </Node>
    <Node title="text85" leaf="no">
        <Node title="text84" leaf="yes"/>
        <Node title="text83" leaf="yes"/>
    </Node>
    <Node title="text82" leaf="yes"/>
    <Node title="text81" leaf="yes"/>
</Node>
<Node title="text80" leaf="no">
    <Node title="text79" leaf="no">
        <Node title="text78" leaf="no">
            <Node title="text78" leaf="yes"/>
            <Node title="text77" leaf="yes"/>
        </Node>
        <Node title="text76" leaf="no">
            <Node title="text75" leaf="yes"/>
            <Node title="text74" leaf="yes"/>
        </Node>
        <Node title="text73" leaf="yes"/>
        <Node title="text72" leaf="yes"/>
    </Node>
    <Node title="text71" leaf="no">
        <Node title="text70" leaf="no">
            <Node title="text69" leaf="yes"/>
            <Node title="text68" leaf="yes"/>
        </Node>
        <Node title="text67" leaf="no">
            <Node title="text66" leaf="yes"/>
        </Node>
        <Node title="text65" leaf="yes"/>
        <Node title="text64" leaf="yes"/>
    </Node>
    <Node title="text63" leaf="yes"/>
    <Node title="text62" leaf="yes"/>
</Node>
<Node title="text61" leaf="yes"/>
<Node title="text60" leaf="yes"/>

这是我的代码:

using (XmlReader reader = XmlReader.Create(XmlStream))
{
    XDocument xDoc = XDocument.Load(reader);                        
    Action<XElement> sortXml = null;
    sortXml = xElement =>
    {
    bool sortParentNode = false;
    foreach (var xElem in xElement.Elements())
    {                                    
        if (xElem.HasElements)
        {
            // go into deep
                sortXml(xElem);
        }
        else
            {
        // break loop and sort parentNode
        sortParentNode = true;
        break;
        }                                    
    }
    if (sortParentNode)
    {
        xElement.ReplaceNodes(from node in xElement.Elements()
                orderby node.Attribute("title").Value
                group node by node.HasElements into folderGroup
                orderby folderGroup.Key descending
                select folderGroup);
    }
};
sortXml(xDoc.Root);                        
}

LINQ在递归方面表现不佳,你的方法看起来还可以(但为什么要使用Action而不是一个合适的方法呢?) - H H
1个回答

9

XML应按标题升序排序。不是一次性对整个XML进行排序,而是每个parentNode单独进行排序。叶子节点始终位于底部。文件夹或非叶子节点位于顶部。

这个解决方案似乎符合您的要求:

public static void SortXml(XElement node)
{
    node.ReplaceNodes(node.Elements("Node")
        .OrderBy(x => (string)x.Attribute("leaf"))
        .ThenBy(x => (string)x.Attribute("title")));

    foreach (var childNode in node.Elements("Node"))
        SortXml(childNode);
}

...

XDocument doc = XDocument.Load("test.xml");
SortXml(doc.Root);

首先按照叶子属性的值进行排序,因为字母表中“no”在“yes”之前,所以“no”的值小于“yes”的值。其次,按照标题进行二次排序。所有一级子节点都按照这种方式排序,然后使用每个子节点作为输入递归重复执行。

输出:

<Node title="text99" leaf="no">
  <Node title="text80" leaf="no">
    <Node title="text71" leaf="no">
      <Node title="text67" leaf="no">
        <Node title="text66" leaf="yes" />
      </Node>
      <Node title="text70" leaf="no">
        <Node title="text68" leaf="yes" />
        <Node title="text69" leaf="yes" />
      </Node>
      <Node title="text64" leaf="yes" />
      <Node title="text65" leaf="yes" />
    </Node>
    <Node title="text79" leaf="no">
      <Node title="text76" leaf="no">
        <Node title="text74" leaf="yes" />
        <Node title="text75" leaf="yes" />
      </Node>
      <Node title="text78" leaf="no">
        <Node title="text77" leaf="yes" />
        <Node title="text78" leaf="yes" />
      </Node>
      <Node title="text72" leaf="yes" />
      <Node title="text73" leaf="yes" />
    </Node>
    <Node title="text62" leaf="yes" />
    <Node title="text63" leaf="yes" />
  </Node>
  <Node title="text89" leaf="no">
    <Node title="text85" leaf="no">
      <Node title="text83" leaf="yes" />
      <Node title="text84" leaf="yes" />
    </Node>
    <Node title="text88" leaf="no">
      <Node title="text86" leaf="yes" />
      <Node title="text87" leaf="yes" />
    </Node>
    <Node title="text81" leaf="yes" />
    <Node title="text82" leaf="yes" />
  </Node>
  <Node title="text98" leaf="no">
    <Node title="text94" leaf="no">
      <Node title="text92" leaf="yes" />
      <Node title="text93" leaf="yes" />
    </Node>
    <Node title="text97" leaf="no">
      <Node title="text95" leaf="yes" />
      <Node title="text96" leaf="yes" />
    </Node>
    <Node title="text90" leaf="yes" />
    <Node title="text91" leaf="yes" />
  </Node>
  <Node title="text60" leaf="yes" />
  <Node title="text61" leaf="yes" />
</Node>

是的,你懂了!那正是我在寻找的。谢谢! 由于我在示例中简化了XML,所以我不得不更改orderBy行。实际的XML没有leaf属性,而是一个type属性,它可以取多个值,而不仅仅是“yes”和“no”。重要的类型是“folder”,它必须在排序顺序中排在第一位(就像leaf =“no”一样)。所以这是我的orderBy行: .OrderBy(x => (string)x.Attribute("type").Value == "folder" ? 1 : 2) 你觉得怎么样? - Rene
修正后的代码行:.OrderBy(x => (string)x.Attribute("type") == "folder" ? 1 : 2) - Rene
我尝试了很多类似的方法两天,但显然如果不调用“ReplaceNodes()”方法,元素不会改变位置。这就是我错过的...感谢您发布这个解决方案。 - Carlos H

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