XSL-Übersicht / XML auswerten mit XPath, C#.NET und Java

XML auswerten mit XPath, C#.NET und Java

XML auswerten mit XPath, C#.NET und Java

➪ Diese Seite beschreibt anhand eines einfachen Beispiels verschiedene Möglichkeiten zur Auswertung von XML-Dokumenten in XPath/XSLT, C#.NET und Java.

Viele Java- und C#.NET-Programmierer sind gewohnt, mit zustandsabhängigen Variablen zu arbeiten. Da im XSL-Umfeld eine etwas andere Bedeutung haben und die XPath-Syntax nicht jedem Developer näher vertraut ist, mag die Auswertung von XML-Dokumenten im Einzelfall etwas mühsam erscheinen.

Gibt's da keine Alternativen in Java oder C#.NET? Ja, die gibt es, und ich möchte anhand eines einfachen Beispiels einige Möglichkeiten aufzeigen und sie den XPath-Lösungen gegenüber stellen. Das Beispiel beinhaltet Elemente, deren Werte zu prüfen sind: Davon abhängig sollen andere Daten ausgegeben werden. Sehen Sie sich diese Input-Datei an:


<root>
  <Nachweis>irgendwo</Nachweis>
  <Name name="Hugo Holzflos">
    <Bemerkung>LQ</Bemerkung>
    <Aktenzeichen>1234</Aktenzeichen>
  </Name>
  <Name name="Tanja Tadellos">
    <Bemerkung>OK</Bemerkung>
    <Aktenzeichen>2345</Aktenzeichen>
  </Name>
  <Name name="Achim Achtlos">
    <Bemerkung>LQ</Bemerkung>
    <Aktenzeichen>3456</Aktenzeichen>
  </Name>
  <Name name="Nikki Nixlos">
    <Bemerkung>LQ</Bemerkung>
    <Aktenzeichen>4567</Aktenzeichen>
  </Name>
  <Name name="Sabine Sinnlos">
    <Aktenzeichen>5678</Aktenzeichen>
  </Name>
  <Name name="Wilma Wunschlos">
    <Aktenzeichen>6789</Aktenzeichen>
  </Name>
</root>

Gesucht sind alle Elemente Name, die nicht den optionalen Childnode Bemerkung mit dem Inhalt "LQ" aufweisen. Von diesen Name-Elementen ist das Attribut @name sowie der Childnode Aktenzeichen erforderlich, ebenso eine aufsteigende Nummerierung.

Auswertung mit XQuery

In XQuery ist das eine sehr überschaubare Angelegenheit. Mit dem -Statement /root/Name[string(Bemerkung)!='LQ'] sprechen Sie gezielt die jeweiligen Nodes an.


<ergebnis>
{
  for $m at $p in /root/Name[string(Bemerkung)!='LQ'] 
  return
  <Mensch 
    name="{$m/@name}" 
    az="{$m/Aktenzeichen}" 
    nr="{$p}"/>
}
</ergebnis>

Das Resultat lautet:


<ergebnis>
   <Mensch name="Tanja Tadellos"  az="2345" nr="1"/>
   <Mensch name="Sabine Sinnlos"  az="5678" nr="2"/>
   <Mensch name="Wilma Wunschlos" az="6789" nr="3"/>
</ergebnis>

Auswertung mit XSLT

In XPath / XSLT ist das ebenso einfach. Dasselbe XPath-Statement spricht dieselben Nodes an, der Rest besteht in deren Auswertung.


<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  version="1.0">
  <xsl:output method="xml" indent="yes"/>    
  <xsl:template match="/">
    <ergebnis>            
      <xsl:for-each 
           select="/root/Name[string(Bemerkung)!='LQ']">
         <Mensch name="{@name}" 
                 az="{Aktenzeichen}" 
                 nr="{position()}"/>
            </xsl:for-each>
        </ergebnis>
    </xsl:template>    
</xsl:stylesheet>

Sie arbeiten gelegentlich mit , Ihre XSL-Stylesheets sind dennoch etwas -lastig? Auch kein Problem:


<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/root">
        <ergebnis>            
            <xsl:for-each 
                 select="Name[string(Bemerkung)!='LQ']">
                <xsl:call-template name="tuwas"/>
            </xsl:for-each>
        </ergebnis>
    </xsl:template>
    <xsl:template name="tuwas">
        <Mensch   name="{@name}" 
                  az="{Aktenzeichen}" 
                  nr="{position()}"/>       
    </xsl:template>
</xsl:stylesheet>

Sie sind kein Fan von xsl:for-each und xsl:call-template, arbeiten lieber mit ? Bitte sehr, auch das geht klar:


<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0">
    <xsl:output method="xml" indent="yes"/>  
    <xsl:template match="/root">
        <ergebnis>
            <xsl:apply-templates 
                 select="Name[string(Bemerkung)!='LQ']"/>
        </ergebnis>
    </xsl:template>
    <xsl:template match="Name">
        <Mensch   name="{@name}" 
                  az="{Aktenzeichen}" 
                  nr="{position()}"/>    
    </xsl:template>
</xsl:stylesheet>

Auswertung mit C#.NET via System.Xml.XmlDocument und XPath

In C#.NET gibt es verschiedene Möglichkeiten, XML-Dokumente auszuwerten. Bei der Arbeit mit System.Xml.XmlDocument kommt das bewährte XPath-Statement zur Anwendung, mit dem Sie die gesuchten Nodes gezielt ansprechen können. Eine Vergleichbarkeit auf XML-Basis kann hergestellt werden über XMLWriter.


int position = 0;
System.Xml.XmlDocument d = new System.Xml.XmlDocument();
d.Load(@"(inputdokument)");
string xpath = "/rootEntity/Name[not(Bemerkung='LQ')]";
using (System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(
      Console.Out, 
      new System.Xml.XmlWriterSettings() { Indent = true }))
{
  xw.WriteStartDocument();
  xw.WriteStartElement("ergebnis");
  foreach (System.Xml.XmlElement eName in d.SelectNodes(xpath))
  {
    string ganzername = eName.Attributes["name"].InnerText;
    string aktenzeichen = eName.SelectSingleNode("Aktenzeichen").InnerText;
    position++;
    xw.WriteStartElement("Mensch");
    xw.WriteAttributeString("name", ganzername);
    xw.WriteAttributeString("az", aktenzeichen);
    xw.WriteAttributeString("nr",position.ToString());
    xw.WriteEndElement();
  }
  xw.WriteEndElement();
  xw.WriteEndDocument();
}

Soweit einige XPath-basierte Lösungen. Wenn Sie jedoch aus irgend einem Grund bei der XML-Auswertung auf XPath verzichten wollen, stehen durchaus Alternativen zur Verfügung.

Auswertung mit C#.NET via LINQ

Eine erste Alternative, die tatsächlich ohne XPath auskommt, finden Sie in . Ob der Umgang mit Lambda-Ausdrücken die Sache soviel leichter macht, betrachte ich als eine Frage der Übung.


System.Xml.Linq.XElement myroot;
myroot = System.Xml.Linq.XElement.Load(@"(inputdokument)");
int position = 0;
var menge    = myroot.Descendants("Name")
               .Where(e => (string)e.Element("Bemerkung") != "LQ");
var output   = new System.Xml.Linq.XElement("ergebnis",
    from o in menge select (new System.Xml.Linq.XElement("Mensch",
    new System.Xml.Linq.XAttribute("name", o.Attribute("name").Value),
    new System.Xml.Linq.XAttribute("az", o.Element("Aktenzeichen").Value),
    new System.Xml.Linq.XAttribute("nr", (++position))
    )));
Console.WriteLine(output);

Auswertung mit C#.NET via System.Xml.XmlReader

Sie bevorzugen eine rein sequenzielle Auswertung des XML-Dokuments? Das geht auch: Hier wird aber deutlich, dass XML-Dokumente nicht ebenso mühelos auswertbar sind wie vergleichsweise -Dateien oder Flatfiles.

Mit System.Xml.XmlReader lesen Sie das XML-Dokument sequenziell: Hier brauchen Sie (wunschgemäß) eine Reihe von Variablen, um zu prüfen, dass der XmlReader auch die gesuchten Elemente bzw. Wertinhalte liest. Ich bin nicht restlos überzeugt, dass dieser Ansatz die Wartbarkeit des Codes nachhaltig vereinfacht. Käme zusätzlich die Anforderung hinzu, die Ergebnisse zu sortieren (in XSLT problemlos via realisierbar), wäre der Aufwand hier nochmals höher.


System.Xml.XmlReader r = System.Xml.XmlReader.Create(@"(inputdokument)");
string ganzername      = "";
string aktenzeichen    = "";
bool iscorrect         = false;
bool isremark          = false;
bool isaz              = false;
int position           = 0;
using (System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(
      Console.Out, 
      new System.Xml.XmlWriterSettings() { Indent = true }))
{
xw.WriteStartDocument();
xw.WriteStartElement("ergebnis");
while (r.Read())
{
  if(r.IsStartElement() && r.Name== "Name")
  {
    iscorrect  = true;
    isaz       = true;
    ganzername = r.GetAttribute("name");
  }
  if (r.IsStartElement() && r.Name == "Bemerkung")
  {
    isremark = true;
  }
  if (isremark && r.Value == "LQ")
  {
    iscorrect = false;
  }
  if (r.IsStartElement() && r.Name == "Aktenzeichen")
  {
    isaz = true;
  }
  if(isaz && r.Value!="") aktenzeichen = r.Value;
  if (!r.IsStartElement() && r.Name == "Aktenzeichen")
  {
    isaz = false;
  }
  else if (!(r.IsStartElement()) && r.Name == "Name")
  {
    isremark = false;
    isaz = false;
  }
  if(iscorrect && !(r.IsStartElement()) && r.Name == "Name")
  {
    position++;
    xw.WriteStartElement("Mensch");
    xw.WriteAttributeString("name", ganzername);
    xw.WriteAttributeString("az", aktenzeichen);
    xw.WriteAttributeString("nr",position.ToString());
    xw.WriteEndElement();
  }
}
r.Close();
xw.WriteEndElement();
xw.WriteEndDocument();

Auswertung mit Java via org.xml.sax.XMLReader

Wenn Sie auch in Java auf XPath verzichten möchten, bleibt Ihnen beispielsweise die Alternative mit dem org.xml.sax.XMLReader, der einen separaten ContentHandler benötigt. In diesem ContentHandler, speziell in dessen Methoden startElement, endElement und characters finden Sie wesentliche Teile der Verarbeitungslogik.


import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
class SAX_ContentHandler implements ContentHandler  {
  private String ganzername      = "";
  private String aktenzeichen    = "";
  private String aktuellerstring = "";
  private boolean iscorrect      = false;
  private boolean isremark       = false;
  private int position           = 0;
  private XMLStreamWriter xstreamwriter=null;
  public SAX_ContentHandler(XMLStreamWriter pxstreamwriter) {
    this.xstreamwriter = pxstreamwriter;
  }
  public void setDocumentLocator(Locator locator) {}  
  public void startDocument() throws SAXException {}  
  public void endDocument() throws SAXException {}  
  public void startPrefixMapping(String prefix, 
                                 String uri) 
                                 throws SAXException {}  
  public void endPrefixMapping(String prefix) throws SAXException {}
  public void startElement(String uri, 
                           String localName, 
                           String qName, 
                           Attributes atts) 
                           throws SAXException {
    if(localName=="Name") {
      iscorrect=true;
      isremark=true;
      ganzername=atts.getValue("name");        
    }
    if(localName=="Bemerkung") {
      isremark=true;  
    }  
    if(localName=="Aktenzeichen") {
      isremark=false;  
    }
  }
  public void endElement(String uri, 
                         String localName, 
                         String qName) 
                         throws SAXException {
    if(localName=="Name") {
      isremark=false;  
      if(iscorrect) {
        position++;        
        try {
          xstreamwriter.writeStartElement("Mensch");
          xstreamwriter.writeAttribute("name", ganzername);
          xstreamwriter.writeAttribute("az", aktenzeichen);
          xstreamwriter.writeAttribute("nr", Integer.toString(position));
          xstreamwriter.writeEndElement();
        } catch (XMLStreamException e) {
          e.printStackTrace();
        }
      }
    }
    if(localName=="Aktenzeichen") {      
      if(iscorrect) {
        aktenzeichen = aktuellerstring;        
      }      
    }  
  }
  public void characters(char[] ch, 
                         int start, 
                         int length) throws SAXException {
    aktuellerstring = new String(ch).substring(start, start + length);    
    if(isremark && aktuellerstring.equals("LQ")) {
      iscorrect=false;
    }
  }
  public void ignorableWhitespace(char[] ch, 
                                  int start, 
                                  int length) 
                                  throws SAXException {}  
  public void processingInstruction(String target, 
                                    String data) 
                                    throws SAXException {}  
  public void skippedEntity(String name) throws SAXException {}
}

Um die Ansätze vergleichen zu können, arbeite ich bei der Ausgabe mit javax.xml.stream.XmlStreamWriter. Dessen Instanz wird in der folgenden Logik erzeugt und dem SAX_ContentHandler via Konstruktor übergeben. Damit kann das XML-Dokument ausgewertet werden:


public static void readXML_SAX()
{
  XMLOutputFactory xoutputfactory =  XMLOutputFactory.newInstance();
  SAX_ContentHandler ch=null;
  try {
      XMLStreamWriter xstreamwriter = null;
      xstreamwriter = xoutputfactory.createXMLStreamWriter(System.out);
      ch = new SAX_ContentHandler(xstreamwriter);
      XMLReader r = XMLReaderFactory.createXMLReader();
      r.setContentHandler(ch);
      xstreamwriter.writeStartDocument("utf-8","1.0");
      xstreamwriter.writeStartElement("ergebnis");
      r.parse("(inputdokument)");
      xstreamwriter.writeEndElement();
      xstreamwriter.writeEndDocument();
      xstreamwriter.flush();
      xstreamwriter.close();
  } catch (XMLStreamException e) {
    e.printStackTrace();
  } catch (SAXException e) {
    e.printStackTrace();
  } catch (IOException e) {
    e.printStackTrace();
  }
}

wg / 23. Juni 2018



Fragen? Anmerkungen? Tips?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/xpath_cs_java_Auswertung.html