在XDocument中对所有元素进行排序

16

我有一个XDocument,我想按字母顺序对所有元素进行排序。这是结构的简化版本:

<Config>
 <Server>
    <Id>svr1</Id>
    <Routing>
        <RoutingNodeName>route1</RoutingNodeName>
        <Subscription>
            <Id>1</Id>
        </Subscription>
        <RoutingParameters id="Routing1">
            <Timeout>7200</Timeout>
        </RoutingParameters>
    </Routing>
    <Storage>
            <Physical>HD1</Physical>
    </Storage>
 </Server>
 <Applications>
    <Services>
        <Local></Local>
    </Services>
 </Applications>
</Config>

我想要对这个文档的所有层级进行排序,目前我能够这样排序:

private static XDocument Sort(XDocument file)
{
    return new XDocument(
        new XElement(file.Root.Name,
            from el in file.Root.Elements()
            orderby el.Name.ToString()
            select el));
}

这将生成:

<Config>
<Applications>
  <Services>
    <Local></Local>
  </Services>
</Applications>
<Server>
  <Id>svr1</Id>
  <Routing>
    <RoutingNodeName>route1</RoutingNodeName>
    <Subscription>
      <Id>1</Id>
    </Subscription>
    <RoutingParameters id="Routing1">
      <Timeout>7200</Timeout>
    </RoutingParameters>
  </Routing>
  <Storage>
    <Physical>HD1</Physical>
  </Storage>
</Server>
</Config>

我想能够通过递归函数以相同的方式对所有子元素进行排序。你有什么方法可以用LINQ实现吗?

谢谢任何帮助。

4个回答

25

您已经有一个用于排序元素的方法。只需将其递归应用即可:

private static XElement Sort(XElement element)
{
    return new XElement(element.Name,
            from child in element.Elements()
            orderby child.Name.ToString()
            select Sort(child));
}

private static XDocument Sort(XDocument file)
{
    return new XDocument(Sort(file.Root));
}

请注意,这将从您的文档中删除所有非元素节点(属性、文本、注释等)。


如果您想保留非元素节点,则需要复制它们:

private static XElement Sort(XElement element)
{
    return new XElement(element.Name,
            element.Attributes(),
            from child in element.Nodes()
            where child.NodeType != XmlNodeType.Element
            select child,
            from child in element.Elements()
            orderby child.Name.ToString()
            select Sort(child));
}

private static XDocument Sort(XDocument file)
{
    return new XDocument(
            file.Declaration,
            from child in file.Nodes()
            where child.NodeType != XmlNodeType.Element
            select child,
            Sort(file.Root));
}

谢谢,我能够使用这个来获取我的预期结果与属性和文本。干杯。 - Zero Cool
这是展示如何对节点进行排序的好方法,但是去除所有值和属性并不是回答OP问题的答案。@ZeroCool如果您编辑此答案并向其他人展示您如何获得所需的正确答案,那将是非常棒的。 - Arvo Bowen
@ArvoBowen 对于没有提供最终解决方案的事情我向您道歉,因为我不再拥有那个代码库,所以无法查看我最终采取的方法,但我认为它与您和 dtb 添加的内容相似。感谢您添加评论和答案。很高兴您能够解决它。 - Zero Cool

18

这种方法可以创建真实的文档扩展名并保留属性和文本值。

我基于这里那里的几篇文章和代码提出了这个方法……感谢所有做出贡献的人!

在相同的命名空间(而不是同一类)中添加以下内容……

public static void Sort(this XElement source, bool bSortAttributes = true)
{
    //Make sure there is a valid source
    if (source == null) throw new ArgumentNullException("source");

    //Sort attributes if needed
    if (bSortAttributes)
    {
        List<XAttribute> sortedAttributes = source.Attributes().OrderBy(a => a.ToString()).ToList();
        sortedAttributes.ForEach(a => a.Remove());
        sortedAttributes.ForEach(a => source.Add(a));
    }

    //Sort the children IF any exist
    List<XElement> sortedChildren = source.Elements().OrderBy(e => e.Name.ToString()).ToList();
    if (source.HasElements)
    {
        source.RemoveNodes();
        sortedChildren.ForEach(c => c.Sort(bSortAttributes));
        sortedChildren.ForEach(c => source.Add(c));
    }
}

使用文档扩展...

//Load the xDoc
XDocument xDoc = XDocument.Load("c:\test.xml");

//Sort the root element
xDoc.Root.Sort();

将以下一行代码更改为:List<XElement> sortedChildren = source.Elements().OrderBy(elem => elem.Attributes("Name").Any() ? elem.Attributes("Name").First().Value.ToString() : string.Empty).ToList();这样可以很好地对 edmx 文件进行排序。感谢 Arvo。 - Steven T. Cramer

4

这里是一个更新的示例,将在执行排序时包括所有属性。

private static XElement Sort(XElement element)
{
    XElement newElement = new XElement(element.Name,
        from child in element.Elements()
        orderby child.Name.ToString()
        select Sort(child));
    if (element.HasAttributes)
    {
        foreach (XAttribute attrib in element.Attributes())
        {
            newElement.SetAttributeValue(attrib.Name, attrib.Value);
        }
    }
    return newElement;
}

private static XDocument Sort(XDocument file)
{
    return new XDocument(Sort(file.Root));
}

这篇文章对我帮助很大,因为我不想使用XSLT来进行XML排序,因为我不想重新格式化XML。我在各个地方搜索了一个简单的解决方案来使用C#和ASP.NET进行XML排序,当我找到这个线程时,我感到非常高兴。感谢所有人,这正好满足了我的需求。


这样做不会达到原帖作者想要的效果。这会删除所有的值! - Arvo Bowen

-1

我认为这些扩展方法效果最佳。

public static class XmlLinq
{
  public static void Sort(this XElement source, bool sortAttributes = true)
  {
     if (source == null)
        throw new ArgumentNullException("source");

     if (sortAttributes)
        source.SortAttributes();

     List<XElement> sortedChildren = source.Elements().OrderBy(e => e.Name.ToString()).ToList();
     source.RemoveNodes();

     sortedChildren.ForEach(c => source.Add(c));
     sortedChildren.ForEach(c => c.Sort());
  }

  public static void SortAttributes(this XElement source)
  {
     if (source == null)
        throw new ArgumentNullException("source");

     List<XAttribute> sortedAttributes = source.Attributes().OrderBy(a => a.ToString()).ToList();
     sortedAttributes.ForEach(a => a.Remove());
     sortedAttributes.ForEach(a => source.Add(a));
  }
}

这是我跟随的一个很好的基础,但它并没有保留所有的文本值或属性。因此,我不得不加入一些修改后实现了自己的答案。 ;) - Arvo Bowen

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