XSL-Übersicht / Konvertierung von XML nach XML

Konvertierung von XML nach XML

Konvertierung von XML nach XML

➪ Da viele Programmierer die mehrfache Implementierung identischer Codezeilen als No-Go ansehen, ist es sinnvoll, redundante Logik in separate Templates auszulagern. Bei der Konvertierung einer XML-Input-Struktur in eine andere Zielstruktur gibt es dabei unterschiedliche Konzepte.

Auf dieser Seite:

Die Zielstruktur wird durch ein XML-Schema vorgegeben:


<xs:schema
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  elementFormDefault="qualified">
  <xs:element name="Root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="A">
          <xs:annotation>
            <xs:documentation xml:lang="en">Group A 
            contains all persons who earn more 
            than they spend</xs:documentation>
          </xs:annotation>
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" 
                minOccurs="0" ref="Person"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="B">
          <xs:annotation>
            <xs:documentation>Group B contains 
              all persons who spend more than 
              they earn</xs:documentation>
          </xs:annotation>
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" 
                minOccurs="0" ref="Person"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Person" type="Person_type"/>
  <xs:simpleType name="mySimpleType">
    <xs:restriction base="xs:string">
      <xs:pattern value="[A-Z][a-z]{1,19}"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType name="Person_type">
    <xs:sequence>
      <xs:element name="Nr" type="xs:integer"/>
      <xs:element name="FN" type="mySimpleType"/>
      <xs:element name="LN" type="mySimpleType"/>
      <xs:element name="Town" type="mySimpleType"/>
      <xs:element name="Surplus" type="xs:decimal"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

pic/xsd6.png

Nun geht es darum, eine Logik zu entwickeln, um die Inhalte der Quellstruktur in die Zielstruktur zu konvertieren. Der konzeptionelle Ansatz besteht darin, mittels gezielt eingesetzten XPath-Statements jene Datenfelder (Elemente/Attribute) anzusprechen, die im Output benötigt werden, und diese Inhalte in die Zielfelder zu mappen.

Implementierung mit xsl:template match

Ein möglicher Ansatz besteht in der Arbeit mit xsl:template match und xsl:apply-templates. Die Nodes werden präzise über XPath (z.B. /Orte/Ort/Person[Gehalt &gt;= sum(Kauf/Gesamt)]") adressiert und an das Template Person weitergeleitet.


 <xsl:template match="/">
  <Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:noNamespaceSchemaLocation="../xsd/ZweiGruppen.xsd">
   <A>
    <xsl:apply-templates 
      select="/Orte/Ort/Person[Gehalt &gt;= sum(Kauf/Gesamt)]"/>
   </A>
   <B>
    <xsl:apply-templates 
      select="/Orte/Ort/Person[Gehalt &lt; sum(Kauf/Gesamt)]"/>
   </B>
  </Root>
 </xsl:template>

Hier folgt das template match="Person", das die Ausgabe der Nummerierung sowie der relativ adressierten XPath-Felder übernimmt.


 <xsl:template match="Person">
  <Person>
   <Nr>
    <xsl:value-of select="position()"/>
   </Nr>
   <NN>
    <xsl:value-of select="name"/>
   </NN>
   <VN>
    <xsl:value-of select="vorname"/>
   </VN>
   <WO>
    <xsl:value-of select="../name"/>
   </WO>
   <EK>
    <xsl:value-of select="Gehalt"/>
   </EK>
   <AG>
    <xsl:value-of select="sum(Kauf/Gesamt)"/>
   </AG>
  </Person>
 </xsl:template>

Ich empfehle, xsl:template match=""-Aufrufe vorrangig bei schwächer strukturierten XML-Input (mixed content) zu verwenden. Da es sich hier um ein stark strukturiertes XML-Dokument handelt, ist dieser Ansatz suboptimal.

Implementierung mit xsl:for-each und direktem xsl:call-template

Eine Alternative finden Sie in der Arbeit mit benannten Templates via xsl:for-each und xsl:call-template.


 <xsl:template match="/">
  <Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xsi:noNamespaceSchemaLocation="../xsd/ZweiGruppen.xsd">
   <A>
    <xsl:for-each 
      select="/Orte/Ort/Person[Gehalt &gt;= sum(Kauf/Gesamt)]">
      <xsl:call-template name="shPerson"/>
    </xsl:for-each>
   </A>
   <B>
    <xsl:for-each 
      select="/Orte/Ort/Person[Gehalt &lt; sum(Kauf/Gesamt)]">
      <xsl:call-template name="shPerson"/>
    </xsl:for-each>
   </B>
  </Root>
 </xsl:template>

Das Template shPerson wird bei jedem vorhergehenden XML-Node Person aufgerufen. Hier werden die Datenfelder aus dem XML-Input-Dokument direkt in das benannte Template übernommen.


 <xsl:template name="shPerson">
  <Person>
   <Nr><xsl:value-of select="position()"/></Nr>
   <NN><xsl:value-of select="name"/></NN>
   <VN><xsl:value-of select="vorname"/></VN>
   <WO><xsl:value-of select="../name"/></WO>
   <EK><xsl:value-of select="Gehalt"/></EK>
   <AG><xsl:value-of select="sum(Kauf/Gesamt)"/></AG>
  </Person>
 </xsl:template>

Ich empfehle ebenso, dass benannte Templates keinen direkten Bezug zum XML-Input haben sollten, um ein späteres Maintenance zu erleichtern. Insofern ist auch dieser Ansatz suboptimal.

Implementierung mit xsl:for-each und parametrisiertem xsl:call-template

Eine dritte Alternative liegt darin, wie vor mit xsl:for-each und xsl:call-template zu arbeiten, dem benannten Template jedoch eine eindeutige Parameterliste zu übergeben. Damit wäre vermieden, benannten Templates einen direkten Bezug zum XML-Input zu gewähren.


 <xsl:template match="/">
  <Root 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:noNamespaceSchemaLocation="../xsd/ZweiGruppen.xsd">
   <A>
    <xsl:for-each 
       select="/Orte/Ort/Person[Gehalt &gt;= sum(Kauf/Gesamt)]">
     <xsl:call-template name="shPerson">
      <xsl:with-param name="ppos" select="position()"/>
      <xsl:with-param name="pnachname" select="name"/>
      <xsl:with-param name="pvorname" select="vorname"/>
      <xsl:with-param name="pwohnort" select="../name"/>
      <xsl:with-param name="pEinkommen" select="Gehalt"/>
      <xsl:with-param name="pAusgaben" 
         select="xs:decimal(sum(Kauf/xs:decimal(Gesamt)))"/>
     </xsl:call-template>
    </xsl:for-each>
   </A>
   <B>
    <xsl:for-each 
       select="/Orte/Ort/Person[Gehalt &lt; sum(Kauf/Gesamt)]">
     <xsl:call-template name="shPerson">
      <xsl:with-param name="ppos" select="position()"/>
      <xsl:with-param name="pnachname" select="name"/>
      <xsl:with-param name="pvorname" select="vorname"/>
      <xsl:with-param name="pwohnort" select="../name"/>
      <xsl:with-param name="pEinkommen" select="Gehalt"/>
      <xsl:with-param name="pAusgaben" 
         select="xs:decimal(sum(Kauf/xs:decimal(Gesamt)))"/>
     </xsl:call-template>
    </xsl:for-each>
   </B>
  </Root>
 </xsl:template>

Da die sum-Funktion im obigen Aufruf einen xs:double-Typen zurückgibt, ich den Parameter pAusgaben jedoch als xs:decimal deklariert habe, ist ein entsprechender Type-Cast erforderlich.


 <xsl:template name="shPerson">
  <xsl:param name="ppos" as="xs:integer"/>
  <xsl:param name="pnachname" as="xs:string"/>
  <xsl:param name="pvorname" as="xs:string"/>
  <xsl:param name="pwohnort" as="xs:string"/>
  <xsl:param name="pEinkommen" as="xs:decimal"/>
  <xsl:param name="pAusgaben" as="xs:decimal"/>
  <Person>
   <Nr>
    <xsl:value-of select="$ppos"/>
   </Nr>
   <NN>
    <xsl:value-of select="$pnachname"/>
   </NN>
   <VN>
    <xsl:value-of select="$pvorname"/>
   </VN>
   <WO>
    <xsl:value-of select="$pwohnort"/>
   </WO>
   <EK>
    <xsl:value-of select="$pEinkommen"/>
   </EK>
   <AG>
    <xsl:value-of select="$pAusgaben"/>
   </AG>
  </Person>
 </xsl:template>

wg / 5. April 2018



Fragen? Anmerkungen? Tipps?

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/XSL_XML2XML.html