Knoten anhand der Position verarbeiten

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie wollen Knoten in einer Sequenz verarbeiten, die eine Funktion ihrer Position in einem Dokument oder einer Knotenmenge ist.

Lösung

Benutzen Sie xsl:sort, wobei die Auswahl auf die Funktionen position( ) oder last( ) gesetzt ist. In der trivialsten Anwendung dieses Beispiels werden Knoten in umgekehrter Dokumentenreihenfolge verarbeitet:

<xsl:apply-templates>
  <xsl:sort select="position( )" order="descending" data-type="number"/>
</xsl:apply-templates>

oder:

<xsl:for-each select="*">
  <xsl:sort select="position( )" order="descending" data-type="number"/>
  <!-- ... -->
</xsl:for-each>

Eine andere gebräuchliche Version dieses Beispiels durchläuft eine Knotenmenge so, als würde es sich um eine Matrix mit einer bestimmten Anzahl von Spalten handeln. Hier verarbeiten Sie alle Knoten in der ersten Spalte, dann in der zweiten und dann in der dritten:

<xsl:for-each select="*">
  <xsl:sort select="(position( ) - 1) mod 3" />
  <!-- ... -->
</xsl:for-each>

Oder vielleicht sauberer mit:

<xsl:for-each select="*[position( ) mod 3 = 1]">
  <xsl:apply-templates select=". | following-sibling::*[position( ) &lt; 3]" />
</xsl:for-each>

Manchmal müssen Sie position( ) verwenden, um den ersten Knoten in einer Knotenmenge von den restlichen Knoten zu trennen. Auf diese Weise können Sie mittels Rekursion auf einem Dokument komplexe Vereinigungsoperationen durchführen. Ich nenne dieses Beispiel recursive-aggregation. Die abstrakte Form des Beispiels folgt:

<xsl:template name="aggregation">
  <xsl:param name="node-set"/>
  <xsl:choose>
    <xsl:when test="$node-set">
      <!--Wir berechnen eine Funktion des ersten Elements, die einen Wert erzeugt, den wir vereinigen wollen. Die Funktion kann vom Typ des Elements abhängen (d.h. sie kann polymorph sein) -->
      <xsl:variable name="first">
        <xsl:apply-templates select="$node-set[1]" mode="calc"/>
      </xsl:variable>
      <!--Wir verarbeiten die verbleibenden Knoten rekursiv mit Hilfe von position( ) -->
      <xsl:variable name="rest">
        <xsl:call-template name="aggregation">
          <xsl:with-param name="node-set" select="$node-set[position( )!=1]"/>
        </xsl:call-template>
      </xsl:variable>
      <!-- Wir führen eine Vereinigungsoperation durch. Diese muss keinen Aufruf an ein Template erfordern. Beispielsweise könnte es sein: $first + $rest oder $first * $rest oder concat($first,$rest) usw. -->
      <xsl:call-template name="aggregate-func">
        <xsl:with-param name="a" select="$first"/>
        <xsl:with-param name="b" select="$rest"/>
      </xsl:call-template>
    </xsl:when>
    <!-- Hier sollte IDENTITY-VALUE durch den Gleichheitswert unter aggregate-func ersetzt werden. Zum Beispiel ist 0 der Gleichheitswert für die Addition, 1 ist der Gleichheitswert für die Subtraktion, "" ist der Gleichheitswert für die Verkettung usw. -->
    <xsl:otherwise>IDENTITY-VALUE</xsl:otherwise>
</xsl:template>

Diskussion

XSLT hat die natürliche Neigung, Knoten in der Dokumentenreihenfolge zu verarbeiten. Dies ist äquivalent dazu zu sagen, dass die Knoten in der Reihenfolge ihrer Position verarbeitet werden. Daher sind die folgenden beiden XSLT-Fragmente äquivalent (das sort ist redundant):

<xsl:for-each select="*">
  <xsl:sort select="position( )"/>
  <!-- ... -->
</xsl:for-each>

<xsl:for-each select="*">
  <!-- ... -->
</xsl:for-each>

Sie können das Organigramm Ihrer Einrichtung (orgchart.xml) in einen zweispaltigen Bericht formatieren, indem Sie eine Variation dieses Ansatzes verwenden (siehe die folgenden beiden Beispiele).

Beispiel: Das Stylesheet columns-orgchat.xslt.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />
  <xsl:strip-space elements="*"/>
  <xsl:template match="employee[employee]">
    <xsl:value-of select="@name"/>
    <xsl:text>&#xA;</xsl:text>
    <xsl:call-template name="dup">
      <xsl:with-param name="input" select=" '-' "/>
      <xsl:with-param name="count" select="80"/>
    </xsl:call-template>
    <xsl:text>&#xA;</xsl:text>
    <xsl:for-each select="employee[(position() - 1) mod 2 = 0]">
      <xsl:value-of select="@name"/>
      <xsl:call-template name="dup">
        <xsl:with-param name="input" select=" ' ' "/>
        <xsl:with-param name="count" select="40 - string-length(@name)"/>
      </xsl:call-template>
      <xsl:value-of select="following-sibling::*[1]/@name"/>
      <xsl:text>&#xA;</xsl:text>
    </xsl:for-each>
    <xsl:text>&#xA;</xsl:text>
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template name="dup">
    <xsl:param name="input"/>
    <xsl:param name="count" select="1"/>
    <xsl:choose>
      <xsl:when test="not($count) or not($input)"/>
      <xsl:when test="$count = 1">
        <xsl:value-of select="$input"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:if test="$count mod 2">
          <xsl:value-of select="$input"/>
        </xsl:if>
        <xsl:call-template name="dup">
          <xsl:with-param name="input" select="concat($input,$input)"/>
          <xsl:with-param name="count" select="floor($count div 2)"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Beispiel: Ausgabe.

Jil Michel
-------------------------------------------------------------------------------
Nancy Pratt                                  Jane Doe
Mike Rosenbaum

Nancy Pratt
-------------------------------------------------------------------------------
Phill McKraken                             Ima Little

Ima Little
-------------------------------------------------------------------------------
Betsy Ross

Jane Doe
-------------------------------------------------------------------------------
Walter H. Potter                           Wendy B.K. McDonald

Wendy B.K. McDonald
-------------------------------------------------------------------------------
Craig F. Frye                                Hardy Hamburg
Rich Shaker

Mike Rosenbaum
-------------------------------------------------------------------------------
Cindy Post-Kellog                        Oscar A. Winner

Cindy Post-Kellog
-------------------------------------------------------------------------------
Allen Bran                                    Frank N. Berry
Jack Apple

Oscar A. Winner
-------------------------------------------------------------------------------
Jack Nicklaus                               Tom Hanks
Susan Sarandon

Jack Nicklaus
-------------------------------------------------------------------------------
R.P. McMurphy

Tom Hanks
-------------------------------------------------------------------------------
Forrest Gump                              Andrew Beckett

Susan Sarandon
-------------------------------------------------------------------------------
Helen Prejean

Ein Beispiel für rekursive Aggregation ist ein Stylesheet, das die komplette Provision berechnet, die an Vertreter gezahlt wird, deren Provision eine Funktion der Gesamtverkäufe über alle Produkte ist, wie in den nächsten beiden Beispielen gezeigt wird.

Beispiel: Das Stylesheet Total-commission.xslt.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="salesBySalesperson">
    <xsl:text>Gesamtprovision = </xsl:text>
    <xsl:call-template name="total-commission">
      <xsl:with-param name="salespeople" select="*"/>
    </xsl:call-template>
  </xsl:template>
  <!-- Standardmäßig erhalten Vertreter 2% Provision und keine Grundvergütung -->
  <xsl:template match="salesperson" mode="commission">
    <xsl:value-of select="0.02 * sum(product/@totalSales)"/>
  </xsl:template>
  <!-- Vertreter mit with seniority &gt; 4 erhalten $10000.00 Grundvergütung + 0.5% Provision -->
  <xsl:template match="salesperson[@seniority &gt; 4]" mode="commission" priority="1">
    <xsl:value-of select="10000.00 + 0.05 * sum(product/@totalSales)"/>
  </xsl:template>
  <!-- Vertreter mit seniority &gt; 8 erhalten (seniority * $2000.00) Grundvergütung + 0.8% Provision -->
  <xsl:template match="salesperson[@seniority &gt; 8]" mode="commission" priority="2">
    <xsl:value-of select="@seniority * 2000.00 + 0.08 * sum(product/@totalSales)"/>
  </xsl:template>
  <xsl:template name="total-commission">
    <xsl:param name="salespeople"/>
    <xsl:choose>
      <xsl:when test="$salespeople">
        <xsl:variable name="first">
          <xsl:apply-templates select="$salespeople[1]" mode="commission"/>
        </xsl:variable>
        <xsl:variable name="rest">
          <xsl:call-template name="total-commission">
            <xsl:with-param name="salespeople" select="$salespeople[position()!=1]"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="$first + $rest"/>
      </xsl:when>
      <xsl:otherwise>0</xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Beispiel: Ausgabe.

Gesamtprovision = 471315

XSLT 2.0

Wenn man 2.0 verwendet, kann man Funktionen und Templates kombinieren, um so Rekursion zu vermeiden, aber dennoch die Fähigkeiten von Templates zur Mustererkennung auszunutzen, um die Provisionen zu berechnen:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ckbk="http://www.oreilly.com/catalog/xsltckbk">
  <xsl:output method="text"/>
  <xsl:template match="salesBySalesperson">
    <xsl:text>Gesamtprovision = </xsl:text>
    <xsl:value-of select="ckbk:total-commission(*)"/>
  </xsl:template>
  <!-- Standardmäßig erhalten Vertreter 2% Provision und keine Grundvergütung -->
  <xsl:template match="salesperson" mode="commission" as="xs:double">
    <xsl:sequence select="0.02 * sum(product/@totalSales)"/>
  </xsl:template>
  <!-- Vertreter mit seniority &gt; 4 erhalten $10000.00 Grundvergütung + 0.5% Provision -->
  <xsl:template match="salesperson[@seniority &gt; 4]" mode="commission" priority="1" as="xs:double">
    <xsl:sequence select="10000.00 + 0.05 * sum(product/@totalSales)"/>
  </xsl:template>
  <!-- Vertreter mit seniority &gt; 8 erhalten (seniority * $2000.00) Grundvergütung + 0.8% Provision -->
  <xsl:template match="salesperson[@seniority &gt; 8]" mode="commission" priority="2" as="xs:double">
    <xsl:sequence select="@seniority * 2000.00 + 0.08 * sum(product/@totalSales)"/>
  </xsl:template>
  <xsl:function name="ckbk:total-commission" as="xs:double">
    <xsl:param name="salespeople" as="node( )*"/>
    <xsl:sequence select="sum(for $s in $salespeople return ckbk:commission($s))"/>
  </xsl:function>
  <xsl:function name="ckbk:commission" as="xs:double">
    <xsl:param name="salesperson" as="node( )"/>
    <xsl:apply-templates select="$salesperson" mode="commission"/>
</xsl:function>

Siehe auch

Michael Kay zeigt auf Seite 535 des Buchs XSLT Programmer's Reference (Wrox Press, 2001) ein schönes Beispiel für rekursive Aggregation. Er verwendet dieses Beispiel, um die Gesamtfläche für verschiedene Formen zu berechnen, wobei die Formel für die Fläche von der Art der Form abhängt.

Jeni Tennison bietet im Buch XSLT and XPath on the Edge (M&T Books, 2001) ebenfalls Beispiele für Rekursive Aggregation und alternative Methoden, um ähnliche Arten der Verarbeitung durchzuführen.

  

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