XML sortieren (c#)

TresPuntos

Cadet 4th Year
Registriert
Juni 2018
Beiträge
113
Hallo,

Wie ist es möglich bei XML-Dateien die Propertys nach Ihrem Namensattribut zu sortieren?
Code:
<Settings>
    <Value>
        <property name="B" />
        <property name="C" />
        <property name="A" />
        <property name="D" />
    </Value>
</Settings>

Dieser Code soll so aussehen:
Code:
<Settings>
    <Value>
        <property name="A" />
        <property name="B" />
        <property name="C" />
        <property name="D" />
    </Value>
</Settings>

Habe diesen Code gefunden, bekomme aber eine Fehlermeldung?
Code:
public void test(){
            XDocument xDoc = XDocument.Load(@"C:\Test\Hallo.xml");
            var bookstore = xDoc.Element("Value")
                .Elements("property")
                .OrderByDescending(s => (string)s.Attribute("name"));
            XDocument doc = new XDocument(new XElement("property", bookstore));
            doc.Save(@"C:\Test\export.xml");
}
 
Zuletzt bearbeitet:
Ja genau,
allerdings wird das Attribut e in diesem Beispiel als Fehler gemeldet, weil es nicht in der Klammer stehen kann/ darf
C#:
static void Main(string[] args)
        {
            string xml = @"<?xml version=""1.0"" encoding=""UTF-8\""?>
                    <websites>
                        <site language=""Spanish"" name=""Excélsior"" order=""2"">http://www.excelsior.com.mx/</site>
                        <site language=""Japanese"" name=""TOKYO Web"" order=""3"">http://www.tokyo-np.co.jp/</site>
                        <site language=""Italian"" name=""Corriere della Sera"" order=""1"">http://www.corriere.it/</site>
                        <site language=""Spanish"" name=""SpanishTest1"" order=""4"">www.spanishtest1.com</site>
                    </websites>";
 
            XDocument xdoc = XDocument.Parse(xml);
            XDocument xNewDoc = new XDocument();
 
            xNewDoc.Add(xdoc.Root);
 
            xNewDoc.Root.RemoveNodes();
            xNewDoc.Root.Add(xdoc.Root.Elements().OrderBy(e => e.Attribute("language").Value));
 
            Console.WriteLine("Before: \r\n{0}", xdoc.ToString());
            Console.WriteLine();
            Console.WriteLine("After: \r\n{0}", xNewDoc.ToString());
            Console.ReadLine();
        }
 
XML:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Value">
    <xsl:copy>
      <xsl:apply-templates select="property">
        <xsl:sort select="@name"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Geht auch mit XSLT, falls du dir das Programmieren sparen willst.
 
  • Gefällt mir
Reaktionen: TresPuntos
@umask007 Danke, dass klappt gut:
habe das dann so umgesetzt, aber ich bekomme in der Zieldatei (export.xml) die sortierte XML-Datei in eine Zeile geliefert:
1535022785704.png

Code:
        private void button7_Click(object sender, EventArgs e)
        {
            string myXmlFile = @"C:\Test\Hallo.xml";
            string myStyleSheet = @"C:\Test\sortStyle.xslt";
            XPathDocument myXPathDoc = new XPathDocument(myXmlFile);
            XslCompiledTransform myXslTrans = new XslCompiledTransform();
            myXslTrans.Load(myStyleSheet);
            XmlTextWriter myWriter = new XmlTextWriter(@"C:\Test\export.xml", null);
            myXslTrans.Transform(myXPathDoc, null, myWriter);
        }
Ist es eventuell auch möglich zu sagen: Sortier nur den Knoten XY und darin die property name Attribute?
Die eigentlichen XML-Dateien besitzen nämlich mehr Knoten und sind geschachtelter, weshalb die XLST da nicht funktioniert


@Madman1209 :
Fehlermeldung: (Parameter) XElement e
eine lokale Variable oder ein Parameter namens "e" kann in diesem Bereich nicht deklariert werden, da der Name in einem einschließenden lokalen Bereich zur Definition einer lokalen Variable oder eines Parameters verwendet wird.
1535023061764.png
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: TresPuntos
Danke für den Tipp mit myWriter.Formatting = Formatting.Indented; hat funktioniert.

umask007 schrieb:
Klar, einfach den XPATH Selektor (<xsl:template match="Value">)
entsprechend anpassen
Sry gar nicht dran gedacht, da hätte ich selber drauf kommen müssen.

Zum Thema mit weiter verschachtelt zu sein.
Die eigentliche XML-Datei ist riesig und sieht in der folgenden Form aus. Es sollte am besten als erstes nach Setting Key sortiert werden und dann spezifisch für Xtra in den properties nach name sortiert werden.
Bei den property ist es der Fall das die unterschiedlich lang geschachtelt werden können wie unten.
Hab da herumprobiert mit deinem Beispiel, aber da wurde nach Setting alles abgeschnitten.
Code:
<?xml version="1.0"?>
<Settings Version="0.0.0.0">
  <Setting Key="A">
    <Value>
      <Xtra version="1.0" application="View">
        <property name="A" />
        <property name="C">-1</property>
        <property name="B">
            <property name="TEST1">Hallo</property>
            <property name="TEST3">Hallo</property>
            <property name="Test2">
                <property name="BspA"></property>
                <property name="BspC"></property>
                <property name="BspB"></property>
            </property>
            <property name="TEST4">Hallo</property>
        </property>
      </Xtra>
    </Value>
  </Setting>
  <Setting Key="D">
    <Value>
      ...
    </Value>
  </Setting>
  <Setting Key="C">
    <Value>
      ...
    </Value>
  </Setting>
  <Setting Key="B">
    <Value>
      ...
    </Value>
  </Setting>
</Settings>
 
Zuletzt bearbeitet:
XML:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/Settings/Setting/Value//*[property]">
    
    <xsl:copy>
       <xsl:copy-of select="@*"/>
      <xsl:apply-templates select="property">
        <xsl:sort select="translate(@name, 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/> <!--Case insensitives sortieren-->
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="/Settings">
    <xsl:copy>
 
      <xsl:apply-templates select="Setting">
        <xsl:sort select="@Key"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
 
</xsl:stylesheet>


Sollte in etwa so funtionieren, getestet hab ich das nicht gründlich...

Code:
         myWriter.Formatting = Formatting.Indented;

myXslTrans.Transform(myXPathDoc, null, myWriter);
Dient dem Formattieren, also damit das Ergebnis nicht in einer Zeile ist, wie du in einem früheren Post geschrieben hast.
 
  • Gefällt mir
Reaktionen: TresPuntos
Dankeschön
klappt gut, nur wird <?xml version="1.0"?> gar nicht ausgegeben und bei <Settings Version="0.0.0.0"> feht die Version.
 
XML:
  <xsl:template match="/Settings">
    <xsl:copy>
       <xsl:copy-of select="@*"/>
      <xsl:apply-templates select="Setting">
        <xsl:sort select="@Key"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
Da fehlte noch was beim Settings Template. Die fehlende XML Version liegt meiner Meinung nach an den Einstellungen des XMLWriter

XmlWriterSettings settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
ConformanceLevel = ConformanceLevel.Document,
OmitXmlDeclaration = false,
CloseOutput = true,
Indent = true,
IndentChars = " ",
NewLineHandling = NewLineHandling.Replace
};
 
Ergänzung ()

umask007 schrieb:
Da fehlte noch was beim Settings Template. Die fehlende XML Version liegt meiner Meinung nach an den Einstellungen des XMLWriter

XmlWriterSettings settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
ConformanceLevel = ConformanceLevel.Document,
OmitXmlDeclaration = false,
CloseOutput = true,
Indent = true,
IndentChars = " ",
NewLineHandling = NewLineHandling.Replace
};


Unter Settings wurde nun das richtige eingefügt, aber <?xml version="1.0" encoding="utf-8"?> wird nicht ganz oben eingefügt. Habe ich das eventuell falsch eingefügt?
C#:
        private void button7_Click(object sender, EventArgs e)
        {
            XmlWriterSettings settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                ConformanceLevel = ConformanceLevel.Document,
                OmitXmlDeclaration = false,
                CloseOutput = true,
                Indent = true,
                IndentChars = " ",
                NewLineHandling = NewLineHandling.Replace
            };
            string myXmlFile = @"C:\Test\Hallo.xml";
            string myStyleSheet = @"C:\Test\sortStyle.xslt";
            XPathDocument myXPathDoc = new XPathDocument(myXmlFile);
            XslCompiledTransform myXslTrans = new XslCompiledTransform();
            myXslTrans.Load(myStyleSheet);
            XmlTextWriter myWriter = new XmlTextWriter(@"C:\Test\export.xml", null);
            myWriter.Formatting = Formatting.Indented;
            myXslTrans.Transform(myXPathDoc, null, myWriter);
        }
 
Zuletzt bearbeitet:
Sry hatte mich vertan, dass Problem existiert nicht
Habe nur noch oben beschriebenes Problem.
Sry falls ich deswegen extra Arbeit verursacht habe, habe es deswegen oben entfernt, damit andere da nicht in die irre geführt werden
TresPuntos schrieb:
Unter Settings wurde nun das richtige eingefügt, aber <?xml version="1.0" encoding="utf-8"?> wird nicht ganz oben eingefügt. Habe ich das eventuell falsch eingefügt?
C#:
private void button7_Click(object sender, EventArgs e)
{
XmlWriterSettings settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
ConformanceLevel = ConformanceLevel.Document,
OmitXmlDeclaration = false,
CloseOutput = true,
Indent = true,
IndentChars = " ",
NewLineHandling = NewLineHandling.Replace
};
string myXmlFile = @"C:\Test\Hallo.xml";
string myStyleSheet = @"C:\Test\sortStyle.xslt";
XPathDocument myXPathDoc = new XPathDocument(myXmlFile);
XslCompiledTransform myXslTrans = new XslCompiledTransform();
myXslTrans.Load(myStyleSheet);
XmlTextWriter myWriter = new XmlTextWriter(@"C:\Test\export.xml", null);
myWriter.Formatting = Formatting.Indented;
myXslTrans.Transform(myXPathDoc, null, myWriter);
}
 
Also laut deinem Code werden die Settings erzeugt, dem XMLWriter aber nicht übergeben.
 
umask007 schrieb:
Also laut deinem Code werden die Settings erzeugt, dem XMLWriter aber nicht übergeben.
Hi,

sry für das späte Melden, aber ich war bis vor kurzem im Urlaub. Leider bin ich dabei völlig überfragt und weiß nicht an welcher Stelle meine Settings dem XMLWriter übergeben werden können
 
Zuletzt bearbeitet:
Ja klappt jetzt super und habe ihn auf meinen Fall angewendet:
C#:
private void lstVergleich_DoubleClick(object sender, EventArgs e)
        {
            XmlWriterSettings settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                ConformanceLevel = ConformanceLevel.Document,
                OmitXmlDeclaration = false,
                CloseOutput = true,
                Indent = true,
                IndentChars = " ",
                NewLineHandling = NewLineHandling.Replace
            };
            string customXMLFile = Path.Combine(TxtPfadQuelle.Text, lstOrdner.SelectedItem.ToString(), lstVergleich.SelectedItem.ToString());
            string customStyleSheet = @"C:\Test\sortStyle.xslt";
            XPathDocument customXPathDoc = new XPathDocument(customXMLFile);
            XslCompiledTransform customXslTrans = new XslCompiledTransform();
            customXslTrans.Load(customStyleSheet);
            XmlWriter customWriter = null;
            customWriter = XmlWriter.Create(@"C:\Test\customDatei.xml", settings);
            customXslTrans.Transform(customXPathDoc, customWriter);

            string standardXmlFile = Path.Combine(TxtPfadQuelle.Text, lstOrdner.SelectedItem.ToString(), lstVergleich.SelectedItem.ToString());
            string standardStyleSheet = @"C:\Test\sortStyle.xslt";
            XPathDocument standardXPathDoc = new XPathDocument(standardXmlFile);
            XslCompiledTransform standardXslTrans = new XslCompiledTransform();
            standardXslTrans.Load(standardStyleSheet);
            XmlWriter standardWriter = null;
            standardXslTrans.Transform(standardXPathDoc, standardWriter);
            //startet WinMerge, um den Vergleich der Dateien auszugeben
            Process.Start("WinMergeU.exe", @"C:\Test\standardDatei.xml  C:\Test\customDatei.xml");
}

Ich habe dabei aber ein Problem und eine Fehlermeldung, die bereits vorher entstanden ist. Ich nutze das Ganze in einem DoubleKlickEvent beim ersten Starten passiert nichts, sobald ich die Methode nochmal starte, kommt eine Fehlermeldung, weil die Dateien bereits vom Dienst vorher genutzt werden:
1536562170396.png

Wie kann ich das Ganze beheben?
 
Warscheinlich sind die Dateien in WinMerge geöffnet. Einfachste Lösung: Verwende jedes Mal eine neue temporäre Datei
für die Ergebnisse
Code:
Path.GetTempFileName()
So kannst du verhindern, dass die Dateien bereits woanders geöffnet sind.

/edit
könnte auch am Code liegen. XmlWriter implementiert IDisposable ( using Direktive verwenden!)
 
Das Problem liegt nicht an Process.Start von WinMerge, da der Fehler auch ohne diesen Befehl auftritt.
Das liegt wirklich an XMLWriter, da die datei schon durch das erste Klicken geöffnet und bearbeitet wird und das zweite Zugreifen dadurch verhindert.

Was genau meinst du mit Direktive?

EDIT:
Habe die Lösung gefunden, beide Dateien müssen nach Process.Start mit
C#:
customWriter.Dispose();
standardWriter.Dispose();
wieder zur Verfügung gestellt werden
Ergänzung ()

Hätte da noch eine Frage:
Wie würde es gehen die Attribute bei 2 Tags zu entfernen, also speziell bei Setting die Attribute Version und LastChanged und bei Settings das Attribut Version.
Nach dem Motto:
C#:
XDocument doc1 = GetXDoc(@"C:\Test\customDatei.xml");
doc1.Descendants("Setting").Attributes("LastChanged").Remove();
doc1.Descendants("Settings").Attributes("Version").Remove();
doc1.Descendants("Setting").Attributes("Version").Remove();
Ich denke das sollte bei XSLT geschehen, aber wie kann man das einbinden?
Habe es jetzt so erstellt und es funktioniert, aber wäre wahrscheinlich eleganter zu lösen:
Code:
XmlWriterSettings settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                ConformanceLevel = ConformanceLevel.Document,
                OmitXmlDeclaration = false,
                CloseOutput = true,
                Indent = true,
                IndentChars = " ",
                NewLineHandling = NewLineHandling.Replace
            };
            try
            {
                XPathDocument XPathDoc = new XPathDocument(transformDatei);
                XslCompiledTransform XslTrans = new XslCompiledTransform();
                //transformiert die XML - Datei in XSLT, um diese zu sortieren anhand der xslt Datei
                XslTrans.Load(XSLtDatei);
                XmlWriter Writer = null;
                Writer = XmlWriter.Create(tempDatei, settings);
                //Formatiert die XSLT - Datei zurück in XML und ist somit sortiert
                //customWriter.Formatting = Formatting.Indented;
                XslTrans.Transform(XPathDoc, Writer);
                Writer.Dispose();
                XDocument doc1 = GetXDoc(tempDatei);
                doc1.Descendants("Setting").Attributes("LastChanged").Remove();
                doc1.Descendants("Settings").Attributes("Version").Remove();
                doc1.Descendants("Setting").Attributes("Version").Remove();
                doc1.Save(tempDatei);
            }
            catch (Exception f)
            {
                MessageBox.Show("" + f);
            }
 
Zuletzt bearbeitet:
Zurück
Oben