使用LINQ进行排序

4

我在排序方面遇到了困难,需要一点帮助。

我将按照等级对子节点进行排序并保存。 例如,为了对主元素内的子节点进行排序,我将传递特定部分的ID。

这个示例仅适用于第一个子代,现在我卡住了。

XElement x = XElement.Load(xmlString1);
x.Descendants("opt").First().ReplaceNodes(x.Descendants("opt").First()
 .Descendants("sel").OrderBy(o => int.Parse(o.Attribute("rank").Value)));
4x.Save(xmlString2);

我需要这样。
x.Descendants("sub").Where(b => b.Attribute("id").Value == "DFG")
 .ReplaceNodes(x.Descendants("opt").First()
 .Descendants("sel").OrderBy(o => int.Parse(o.Attribute("rank").Value))

Original

  <main id="AFB" rank="1" name="ROOT">
<sub id="DFG" rank="2" name="SUB1">
 <att >
    <sel id="JIK" rank="4" name="444" />
    <sel id="OKI" rank="2" name="222" />
  </att>
  <opt>
    <sel id="JIK" rank="2" name="122" />
    <sel id="OKI" rank="1" name="111" />
  </opt>
</sub>  
 <sub id="EGG" rank="1" name="SUB2" >
  <opt>
    <sel id="DJI" rank="1" name="111" />
    <sel id="LOW" rank="3" name="333" />
    <sel id="QWE" rank="2" name="222" />
  </opt>
</sub>
<main>

目标

 <main id="AFB" rank="1" name="ROOT">   
 <sub id="EGG" rank="1" name="SUB2" >
  <opt>
    <sel id="DJI" rank="1" name="111" />        
    <sel id="QWE" rank="2" name="222" />
    <sel id="LOW" rank="3" name="333" />
  </opt>
</sub>
<sub id="DFG" rank="2" name="SUB1">
  <att >
    <sel id="OKI" rank="2" name="222" />
    <sel id="JIK" rank="4" name="444" />        
  </att>
  <opt>
    <sel id="OKI" rank="1" name="111" />
    <sel id="JIK" rank="2" name="122" />        
  </opt>
</sub>
<main>

1
我编辑了你的帖子以正确格式化代码并使其可读。但是 4x.Save(xmlString2); 在此之前就已经存在,而我在编辑时没有更改代码。这是一个打字错误吗? - René Vogt
1
你尝试过递归吗? - slawekwin
2个回答

1
我认为您在打字时有个错别字。不过,请看一下这个解决方案:
var text = @"
<main id='AFB' rank='1' name='ROOT'>
    <sub id='DFG' rank='2' name='SUB1'>
        <opt>
            <sel id='JIK' rank='4' name='444' />
            <sel id='OKI' rank='2' name='222' />
        </opt>
        <opt>
            <sel id='JIK' rank='2' name='122' />
            <sel id='OKI' rank='1' name='111' />
        </opt>
    </sub>  
    <sub id='EGG' rank='1' name='SUB2' >
        <opt>
            <sel id='DJI' rank='1' name='111' />
            <sel id='LOW' rank='3' name='333' />
            <sel id='QWE' rank='2' name='222' />
        </opt>
    </sub>
</main>";

var x = XDocument.Parse(text);
x.Root.ReplaceNodes(x.Descendants("sub").OrderBy(a => int.Parse(a.Attribute("rank").Value)));
foreach (var opt in x.Descendants("opt"))
    opt.ReplaceNodes(opt.Descendants("sel").OrderBy(a => int.Parse(a.Attribute("rank").Value)));

此时,x 包含以下XML内容:
<main id="AFB" rank="1" name="ROOT">
  <sub id="EGG" rank="1" name="SUB2">
    <opt>
      <sel id="DJI" rank="1" name="111" />
      <sel id="QWE" rank="2" name="222" />
      <sel id="LOW" rank="3" name="333" />
    </opt>
  </sub>
  <sub id="DFG" rank="2" name="SUB1">
    <opt>
      <sel id="OKI" rank="2" name="222" />
      <sel id="JIK" rank="4" name="444" />
    </opt>
    <opt>
      <sel id="OKI" rank="1" name="111" />
      <sel id="JIK" rank="2" name="122" />
    </opt>
  </sub>
</main>

如果应该包含`att`而不是`opt`,以下代码将起作用:

att如果存在,则应包含在内:

var x = XDocument.Parse(text);
x.Root.ReplaceNodes(x.Descendants("sub").OrderBy(a => int.Parse(a.Attribute("rank").Value)));
foreach (var opt in x.Descendants("sub").Elements())
    opt.ReplaceNodes(opt.Descendants("sel").OrderBy(a => int.Parse(a.Attribute("rank").Value)));

如果您需要按名称对单个元素进行排序,请使用以下方法(如果没有排名属性存在或为空,则放在后面):

//sub with id=EGG
var sub2 = x.Descendants("sub").FirstOrDefault(a => a.Attribute("id").Value == "EGG");
if (sub2 != null)
{
    foreach (var node in sub2.Elements())
        node.ReplaceNodes(node.Elements().OrderBy(a =>
        {
            int rank;
            if (a.Attribute("rank") == null || !int.TryParse(a.Attribute("rank").Value, out rank))
                rank = int.MaxValue;
            return rank;
        }));
}

非常感谢您的快速回复。我会尝试一下。 - andy
添加了另一个例子。 - Paweł Dyl
什么是空值条件运算符(https://msdn.microsoft.com/en-us/library/dn986595.aspx),用于防止“NullReferenceException”异常。这个错误是什么? - Paweł Dyl
转换为旧语法。 - Paweł Dyl
Pawel,你知道为什么这个排序只适用于子元素吗?现在我需要对主元素进行排序。但是这并不起作用,请参见顶部示例。我将对主元素进行排序。在这种情况下,我会传递 ID YYY 来对主元素进行排序。 - andy
<xxx id="YYY"> <main id="AFB" rank="2" name="ROOT1"> <sub id="DFG" rank="2" name="SUB1"> <opt> <sel id="JIK" rank="4" name="444" /> </opt> </sub>
<sub id="EGG" rank="1" name="SUB2" > <opt> <sel id="DJI" rank="1" name="111" /> </opt> </sub> </main> <main id="ABC" rank="1" name="ROOT2"> <sub id="EGG" rank="1" name="SUB2" > <opt> <sel id="DJI" rank="1" name="111" /> </opt> </sub> </main> <xxx>
- andy

0
即使这不是直接回答您的问题,我想建议您一种避免直接通过XDocument和相关类处理Xml的方法。
在处理xml时(如果没有提供.xsd文件),我倾向于通过创建相应的.xsd并从中生成需要使用xml的类来避免手动解析。
打开Visual Studio提示:
   -> xsd "yourxml.xml" (a .xsd file will be generated)
   -> xsd /c "yourxml.xsd" (a .cs file will be generated)

(请注意,您可能需要手动调整生成的 XSD 以更好地满足您的需求并应用其他约束)

需要保存有关 XML 的信息的类已生成。

现在,您可以通过使用在.cs文件中生成的类,将整个 XML 读入强类型对象中。您只需要在项目中导入.cs文件并对原始 XML 进行反序列化即可:

string fileContent = File.ReadAllText( fileLocation );
var xmlObj = StringXmlSerializer.XmlDeserialize<YourXsdGeneratedType>( fileContent );

您可以在内存中编辑XML,然后像这样序列化回XML:

string xmlContext = StringXmlSerializer.XmlSerialize( xmlObj );
File.WriteAllText( filePath, xmlObj );

StringXmlSerializer 是我编写的一个辅助类,适用于我的需求,在内存中将对象序列化为字符串(但您也可以直接将其序列化到文件中)。我在此发布代码,以便您开始使用:

/// <summary>
/// Serialize object in xml format on a string
/// </summary>
public static class StringXmlSerializer
{
    public static string XmlSerialize( object objectInstance )
    {
        XmlWriterSettings ws = new XmlWriterSettings();
        ws.NewLineHandling = NewLineHandling.Entitize;

        var serializer = new XmlSerializer( objectInstance.GetType() );
        var sb = new StringBuilder();

       using( XmlWriter xmlWriter = XmlWriter.Create( sb, ws ) )
           serializer.Serialize( xmlWriter, objectInstance );

        return sb.ToString();
    }

    public static T XmlDeserialize<T>( string objectData )
    {
        return (T)XmlDeserialize( objectData, typeof( T ) );
    }

    public static object XmlDeserialize( string objectData, Type type )
    {
        var serializer = new XmlSerializer( type );

        using( TextReader reader = new StringReader( objectData ) )
            return serializer.Deserialize( reader );
    }
}

希望对某人有所帮助


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