Reputation: 41
I'm struggling with this sorting and need little bit help.
I will sort child nodes along the rank and save it. e.g. to order sub nodes inside the main element I will pass the ID for the specific section.
This example works only for the first Descendants and now I stuck.
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);
I need like that.
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>
Target
<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>
Upvotes: 4
Views: 155
Reputation: 2814
Even if this is not a direct answer to your problem, i wanna suggest you a way to avoid to deal directly with the Xml via XDocument and related classes.
When dealing with xml (if .xsd files are not already provided) I tend to avoid manual parsing by creating its corresponding .xsd and from that I generate the classes needed to work with the xml.
open visual studio prompt:
-> xsd "yourxml.xml" (a .xsd file will be generated)
-> xsd /c "yourxml.xsd" (a .cs file will be generated)
(Note that you may have to adjust the generated xsd manually to better fit your needs and apply additional contraints)
The class needed to hold information about the xml is generated.
Now you can read the whole xml in a strongly typed object, by using the class generated in the .cs
file. You just need to import the .cs
file in your project and deserialize the original xml:
string fileContent = File.ReadAllText( fileLocation );
var xmlObj = StringXmlSerializer.XmlDeserialize<YourXsdGeneratedType>( fileContent );
You can edit the xml in memory and serialize back to xml like this:
string xmlContext = StringXmlSerializer.XmlSerialize( xmlObj );
File.WriteAllText( filePath, xmlObj );
StringXmlSerializer
is a helper i class i wrote that fit my needs to serialize in memory on a string (but you can serialize directly on a file). I post the code to get you started on that:
/// <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 );
}
}
Hope it helps someone
Upvotes: 0
Reputation: 9143
I think you have a typos there. However, take a look at this solution:
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)));
At this point x
contains following 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>
If att
is there instead of 'opt` and should be included, following will work:
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)));
If you need to sort single element by name, use following (if no rank attribute exists or is empty, put on back):
//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;
}));
}
Upvotes: 1