Eine XML-Hierarchie neu organisieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie müssen die Informationen in einem XML-Dokument neu organisieren, um einige implizite Informationen explizit und einige explizite Informationen implizit zu machen.

Lösung

XSLT 1.0

Schauen Sie sich auch hier das Dokument SalesBySalesPerson.xml aus Auswählen und Durchlaufen an:

<salesBySalesperson>
  <salesperson name="John Adams" seniority="1">
    <product sku="10000" totalSales="10000.00"/>
    <product sku="20000" totalSales="50000.00"/>
    <product sku="25000" totalSales="920000.00"/>
  </salesperson>
  <salesperson name="Wendy Long" seniority="5">
    <product sku="10000" totalSales="990000.00"/>
    <product sku="20000" totalSales="150000.00"/>
    <product sku="30000" totalSales="5500.00"/>
  </salesperson>
  <salesperson name="Willie B. Aggressive" seniority="10">
    <product sku="10000" totalSales="1110000.00"/>
    <product sku="20000" totalSales="150000.00"/>
    <product sku="25000" totalSales="2920000.00"/>
    <product sku="30000" totalSales="115500.00"/>
    <product sku="70000" totalSales="10000.00"/>
  </salesperson>
  <salesperson name="Arty Outtolunch" seniority="10"/>
</salesBySalesperson>

Explizit wird dargestellt, welche Waren von welchem Vertreter verkauft werden und wie viel jeder Vertreter für jedes verkaufte Produkt erhält. Das Gesamteinkommen, das mittels der einzelnen Produkte erzielt wird, ergibt sich implizit, ebenso die Namen aller Vertreter, die ein bestimmtes Produkt verkauft haben.

Um daher dieses Dokument neu zu organisieren, müssten Sie es in eine Sicht umwandeln, die Verkäufe pro Produkt anzeigt. Das folgende Stylesheet führt diese Transformation durch:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:key name="sales_key" match="salesperson" use="product/@sku"/>
  <xsl:variable name="products" select="//product"/>
  <xsl:variable name="unique-products" select="$products[not(@sku = preceding::product/@sku)]"/>
  <xsl:template match="/">
    <salesByProduct>
      <xsl:for-each select="$unique-products">
        <xsl:variable name="sku" select="@sku"/>
        <xsl:copy>
          <xsl:copy-of select="$sku"/>
          <xsl:attribute name="totalSales">
            <xsl:value-of select="sum($products[@sku=$sku]/@totalSales)"/>
          </xsl:attribute>
          <xsl:for-each select="key('sales_key',$sku)">
            <xsl:copy>
              <xsl:copy-of select="@*"/>
              <xsl:attribute name="sold">
                <xsl:value-of select="product[@sku=$sku]/@totalSales"/>
              </xsl:attribute>
            </xsl:copy>
          </xsl:for-each>
        </xsl:copy>
      </xsl:for-each>
    </salesByProduct>
  </xsl:template>
</xsl:stylesheet>

Die resultierende Ausgabe wird hier gezeigt:

<salesByProduct>
  <product sku="10000" totalSales="2110000">
    <salesperson name="John Adams" seniority="1" sold="10000.00"/>
    <salesperson name="Wendy Long" seniority="5" sold="990000.00"/>
    <salesperson name="Willie B. Aggressive" seniority="10" sold="1110000.00"/>
  </product>
  <product sku="20000" totalSales="350000">
    <salesperson name="John Adams" seniority="1" sold="50000.00"/>
    <salesperson name="Wendy Long" seniority="5" sold="150000.00"/>
    <salesperson name="Willie B. Aggressive" seniority="10" sold="150000.00"/>
  </product>
  <product sku="25000" totalSales="3840000">
    <salesperson name="John Adams" seniority="1" sold="920000.00"/>
    <salesperson name="Willie B. Aggressive" seniority="10" sold="2920000.00"/>
  </product>
  <product sku="30000" totalSales="121000">
    <salesperson name="Wendy Long" seniority="5" sold="5500.00"/>
    <salesperson name="Willie B. Aggressive" seniority="10" sold="115500.00"/>
  </product>
  <product sku="70000" totalSales="10000">
    <salesperson name="Willie B. Aggressive" seniority="10" sold="10000.00"/>
  </product>
</salesByProduct>

Eine alternative Lösung basiert auf der Muench-Methode, die nach Steve Muench benannt ist. Diese Methode verwendet einen xsl:key, um die Extraktion der eindeutigen Produkte durchzuführen. Der Ausdruck $products[count(.|key('product_key', @sku)[1]) = 1] wählt das erste Produkt in der speziellen Gruppe aus, wobei die Gruppierung nach sku erfolgt:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:variable name="doc" select="/"/>
  <xsl:key name="product_key" match="product" use="@sku"/>
  <xsl:key name="sales_key" match="salesperson" use="product/@sku"/>
  <xsl:variable name="products" select="//product"/>
  <xsl:template match="/">
    <salesByProduct>
      <xsl:for-each select="$products[count(.|key('product_key',@sku)[1]) = 1]">
        <xsl:variable name="sku" select="@sku"/>
        <xsl:copy>
          <xsl:copy-of select="$sku"/>
          <xsl:attribute name="totalSales">
            <xsl:value-of select="sum(key('product_key',$sku)/@totalSales)"/>
          </xsl:attribute>
          <xsl:for-each select="key('sales_key',$sku)">
            <xsl:copy>
              <xsl:copy-of select="@*"/>
              <xsl:attribute name="sold">
                <xsl:value-of select="product[@sku=$sku]/@totalSales"/>
              </xsl:attribute>
            </xsl:copy>
          </xsl:for-each>
        </xsl:copy>
      </xsl:for-each>
    </salesByProduct>
  </xsl:template>
</xsl:stylesheet>

XSLT 2.0

Dieses Problem lässt sich in XSLT 2.0 recht einfach lösen, da Sie xsl:for-each-group ausnutzen können:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <salesByProduct>
      <!-- Gruppieren der Produkte nach sku -->
      <xsl:for-each-group select="//product" group-by="@sku">
        <xsl:copy>
          <xsl:copy-of select="@sku"/>
          <!-- current-group( ) verwenden, um die Verkäufe zusammenzurechnen -->
          <xsl:attribute name="totalSales" select="format-number(sum(current-group( )/@totalSales),'#')"/>
          <!-- Kopieren von salesperson-Elementen, die ein Kind-Produkt mit dem sku der aktuellen Produktgruppe enthalten -->
          <xsl:for-each select="/*/salesperson">
            <xsl:if test="product[@sku eq current-grouping-key( )]">
              <xsl:copy>
                <xsl:copy-of select="@*"/>
              </xsl:copy>
            </xsl:if>
          </xsl:for-each>
        </xsl:copy>
      </xsl:for-each-group>
    </salesByProduct>
  </xsl:template>
</xsl:stylesheet>

Diskussion

XSLT 1.0

Die Lösung zeigt ein sehr anwendungsspezifisches Beispiel. Dieses Szenario lässt sich nicht ändern. Es ist schwierig, wenn nicht sogar unmöglich, ein generisches Neuorganisations-Stylesheet zu präsentieren, da die Arten der Neuorganisation sich je nach der Natur der im speziellen Fall transformierten Dokumente unterscheiden.

Dennoch tauchen in diesen Arten von Neuorganisation einige gebräuchliche Wendungen auf.

Erstens, da Sie den Dokumentbaum vollständig neu organisieren, ist es unwahrscheinlich, dass eine Lösung vorrangig auf dem Erfassen und Anwenden von Templates beruht. Diese Arten von Stylesheets verwenden mit höherer Wahrscheinlichkeit einen iterativen Stil. Mit anderen Worten, die Lösungen beruhen wahrscheinlich stark auf xsl:for-each.

Zweitens initialisieren Rezepte in dieser Klasse fast immer globale Variablen, in denen Elemente enthalten sind, die aus der Tiefe der XML-Struktur extrahiert wurden. Darüber hinaus müssen Sie wahrscheinlich eine eindeutige Teilmenge dieser Elemente ermitteln. Im Rezept Knoten nach dem Kontext auswählen finden Sie eine vollständige Beschreibung der Techniken, die für die Konstruktion eindeutiger Mengen von Elementen verfügbar sind.

Drittens umfasst die Neuorganisation oft das erneute Zusammenfassen von Daten mit Hilfe von Summen, Produkten oder noch komplexeren Operationen. Unter Zahlen und Berechnungen bespreche ich Techniken zum Berechnen dieser Zusammenfassungen.

XSLT 2.0

Die Leistungsfähigkeit von xsl:for-each-group sorgt auf jeden Fall dafür, dass Neuorganisationen von XML viel einfacher vonstatten gehen. Der Schlüssel liegt darin, genau zu verstehen, welches Kriterium der Gruppe zugrunde liegt, und dann die anderen Elemente des Dokuments anhand ihrer Beziehung zu der Gruppe neu zu organisieren.

Siehe auch

Das Rezept for-each-group der Muench-Methode zur Gruppierung vorziehen enthält weitere Beispiele für die Verwendung des XSLT-2.0-for-each-group.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de