XSL-Übersicht / 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.
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>
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.
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 >= 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 >= sum(Kauf/Gesamt)]"/>
</A>
<B>
<xsl:apply-templates
select="/Orte/Ort/Person[Gehalt < 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.
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 >= sum(Kauf/Gesamt)]">
<xsl:call-template name="shPerson"/>
</xsl:for-each>
</A>
<B>
<xsl:for-each
select="/Orte/Ort/Person[Gehalt < 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.
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 >= 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 < 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:
V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn
☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de