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 notwendig, redundante Logik in separate Templates auszulagern. Bei der Konvertierung einer XML-Input-Struktur in eine andere Zielstruktur gibt es dabei unterschiedliche Konzepte.

Konvertierung von XML nach XML

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:element>
  <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 - ggf. verarbeitet - 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 Numerierung 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>

Nehmen wir die Empfehlung, xsl:template match=""-Aufrufe vorrangig (ausschließlich?) bei schwächer strukturierten XML Input-Dokumenten (mixed content) zu verwenden, dann ist dieser Ansatz suboptimal.

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

Eine Alternative finden wir 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 ist nicht zu umgehen, daß die Datenfelder aus dem XML Input Dokument direkt in das benannte Template übernommen werden.


 <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>

Nehmen wir die Empfehlung, daß benannte Templates keinen direkten Bezug zum XML Input haben sollten, um einen späteres Maintenance zu erleichtern, dann 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, wir aber (um uns das Leben schwer zu machen - und nebenher noch etwas zu lernen) den Parameter "pAusgaben" als "xs:decimal" deklariert haben, ist ein entsprechender Typecast 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 / 8. Januar 2018



Fragen? Anmerkungen? Tips?

Bitte nehmen Sie Kontakt zu mir auf:

Vorname
Nachname
Mailadresse







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: info2018@wilfried-grupe.de

www.wilfried-grupe.de/XSL_XML2XML.html