Eine Hierarchie darstellen

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie wollen eine Textausgabe erzeugen, die eingerückt oder kommentiert ist, um die hierarchische Struktur des Original-XML widerzuspiegeln.

Lösung

Bei der offensichtlichsten hierarchischen Darstellung wird die hierarchische Struktur des Quell-XML durch Einrückung imitiert. Sie können ein generisches Stylesheet erzeugen (siehe die beiden folgenden Beispiele), das vernünftige Vorschläge für die Zuordnung der Informationen im Eingabedokument zu einer hierarchischen Ausgabe macht.

Beispiel: text.hierarchy.xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://www.ora.com/XSLTCookbook/namespaces/strings">
  <xsl:include href="../strings/str.dup.xslt"/>
  <xsl:include href="../strings/str.replace.xslt"/>
  <xsl:output method="text"/>
  <!--Die Ebenen werden standardmäßig durch zwei Leerzeichen eingerückt -->
  <xsl:param name="indent" select=" ' ' "/>
  <xsl:template match="*">
    <xsl:param name="level" select="count(./ancestor::*)"/>
    <!-- Dieses Element einrücken -->
    <xsl:call-template name="str:dup" >
      <xsl:with-param name="input" select="$indent"/>
      <xsl:with-param name="count" select="$level"/>
    </xsl:call-template>
    <!--Verarbeiten des Elementnamens. Standardmäßig wird local-name ausgegeben -->
    <xsl:apply-templates select="." mode="name">
      <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
    <!--Den Anfang der Verarbeitung der Attribute signalisieren. Standardmäßig wird '(' ausgegeben. -->
    <xsl:apply-templates select="." mode="begin-attributes">
      <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
    <!--Verarbeiten der Attribute. Standardmäßig wird name="value" ausgegeben. -->
    <xsl:apply-templates select="@*">
      <xsl:with-param name="element" select="."/>
      <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
    <!--Das Ende der Verarbeitung der Attribute signalisieren. Standardmäßig wird ')' ausgegeben. -->
    <xsl:apply-templates select="." mode="end-attributes">
      <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
    <!-- Verarbeiten des Elementwertes. -->
    <!-- Standardmäßig wird der Wert eines Blattelements so formatiert, dass es auf der nächsten Zeile eingerückt ist. -->
    <xsl:apply-templates select="." mode="value">
      <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
    <xsl:apply-templates select="." mode="line-break">
      <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
    <!-- Verarbeiten der Kinder -->
    <xsl:apply-templates select="*">
      <xsl:with-param name="level" select="$level + 1"/>
    </xsl:apply-templates>
  </xsl:template>
  <!--Vorgegebene Behandlung der Elementnamen. -->
  <xsl:template match="*" mode="name">[<xsl:value-of select="local-name(.)"/></xsl:template>
  <!--Vorgegebene Behandlung des Beginns der Attribute. -->
  <xsl:template match="*" mode="begin-attributes">
    <xsl:if test="@*"><xsl:text> </xsl:text></xsl:if>
  </xsl:template>
  <!--Vorgegebene Behandlung der Attribute. -->
  <xsl:template match="@*">
    <xsl:value-of select="local-name(.)"/>="<xsl:value-of select="."/>"<xsl:text/>
    <xsl:if test="position() != last()">
      <xsl:text> </xsl:text>
    </xsl:if>
  </xsl:template>
  <!--Vorgegebene Behandlung des Endes der Attribute. -->
  <xsl:template match="*" mode="end-attributes">]</xsl:template>
  <!--Vorgegebene Behandlung der Elementwerte. -->
  <xsl:template match="*" mode="value">
    <xsl:param name="level"/>
    <!-- Nur Ausgabe der Werte für Blätter. -->
    <xsl:if test="not(*)">
      <xsl:variable name="indent-str">
        <xsl:call-template name="str:dup" >
          <xsl:with-param name="input" select="$indent"/>
          <xsl:with-param name="count" select="$level"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:text>&#xa;</xsl:text>
      <xsl:value-of select="$indent-str"/>
      <xsl:call-template name="str:replace">
        <xsl:with-param name="input" select="."/>
        <xsl:with-param name="search-string" select=" '&#xa;' "/>
        <xsl:with-param name="replace-string" select="concat('&#xa;',$indent-str)"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
  <xsl:template match="*" mode="line-break">
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Beispiel: Ausgabe, wenn ExpenseReport.xml verarbeitet wird.

[ExpenseReport statementNum="123"]
    [Employee]
       [Name]
       Salvatore Mangano
       [SSN]
       999-99-9999
       [Dept]
       XSLT Hacking
       [EmpNo]
       1
       [Position]
       Koch
       [Manager]
       Großer Chef O'Reilly
    [PayPeriod]
       [From]
       1/1/02
       [To]
       1/31/02
    [Expenses]
       [Expense]
          [Date]
          12/20/01
          [Account]
          12345
          [Desc]
          Herumgehangen, anstatt zur Konferenz zu fahren.
          [Lodging]
          500.00
          [Transport]
          50.00
          [Fuel]
          0
          [Meals]
          300.00
          [Phone]
          100
          [Entertainment]
          1000.00
          [Other]
          300.00
       [Expense]
          [Date]
          12/20/01
          [Account]
          12345
          [Desc]
          Am Strand
          [Lodging]
          500.00
          [Transport]
          50.00
          [Fuel]
          0
          [Meals]
          200.00
          [Phone]
          20
          [Entertainment]
          300.00
          [Other]
          100.00

XSLT 2.0

Mit XSLT 2.0 können Sie den gezeigten Code verbessern. So können Sie etwa die in XPath 2.0 integrierte Funktion replace sowie die im Rezept Einen in Spalten gesetzten Bericht erzeugen präsentierte Füllfunktion verwenden.

Diskussion

Sie könnten die speziellen Vorschläge beanstanden, die von diesem Stylesheet gemacht wurden, um die Informationen im Quelldokument auf ein hierarchisches Layout abzubilden. Diese Kritik ist in Ordnung, weil das Stylesheet dazu gedacht ist, angepasst zu werden. Möglicherweise gefallen Ihnen ja die Ergebnisse besser, die mit den Anpassungen in den beiden folgenden Beispielen erzielt wurden.

Beispiel: Angepasstes ExpenseReport-Stylesheet.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="text.hierarchy.xslt"/>
  <!--Ignorieren der Attribute -->
  <xsl:template match="@*"/>
  <xsl:template match="*" mode="begin-attributes"/>
  <xsl:template match="*" mode="end-attributes"/>
  <xsl:template match="*" mode="name">
    <!--Anzeigen des lokalen Elementnamens-->
    <xsl:value-of select="local-name(.)"/>
    <!--Gefolgt von einem Doppelpunkt und einem Leerzeichen, wenn es sich um ein Blatt handelt -->
    <xsl:if test="not(*)">: </xsl:if>
  </xsl:template>
  <xsl:template match="*" mode="value">
    <xsl:if test="not(*)">
      <xsl:value-of select="."/>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Beispiel: Ausgabe mit neuer Formatierung.

ExpenseReport
    Employee
       Name: Salvatore Mangano
       SSN: 999-99-9999
       Dept: XSLT Hacking
       EmpNo: 1
       Position: Koch
       Manager: Großer Chef O'Reilly
    PayPeriod
       From: 1/1/02
       To: 1/31/02
    Expenses
       Expense
          Date: 12/20/01
          Account: 12345
          Desc: Herumgehangen, anstatt zur Konferenz zu fahren.
          Lodging: 500.00
          Transport: 50.00
          Fuel: 0
          Meals: 300.00
          Phone: 100
          Entertainment: 1000.00
          Other: 300.00
       Expense
          Date: 12/20/01
          Account: 12345
          Desc: Am Strand
          Lodging: 500.00
          Transport: 50.00
          Fuel: 0
          Meals: 200.00
          Phone: 20
          Entertainment: 300.00
          Other: 100.00

Vielleicht gefällt Ihnen ja auch das Format aus den beiden folgenden Beispielen, die von Jeni Tennison inspiriert sind.

Beispiel: tree-control.xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="text.hierarchy.xslt"/>
  <!--Ignorieren der Attribute -->
  <xsl:template match="@*"/>
  <xsl:template match="*" mode="begin-attributes"/>
  <xsl:template match="*" mode="end-attributes"/>
  <xsl:template match="*" mode="name">
    <!--Anzeigen des lokalen Elementnamens-->
    <xsl:text>[</xsl:text>
    <xsl:value-of select="local-name(.)"/>
    <!--Gefolgt von einer eckigen Klammer und einem Leerzeichen, wenn es sich um ein Blatt handelt -->
    <xsl:text>] </xsl:text>
  </xsl:template>
  <xsl:template match="*" mode="value">
    <xsl:if test="not(*)">
      <xsl:value-of select="."/>
    </xsl:if>
  </xsl:template>
  <xsl:template match="*" mode="indent">
    <xsl:for-each select="ancestor::*">
      <xsl:choose>
        <xsl:when test="following-sibling::*"> | </xsl:when>
        <xsl:otherwise><xsl:text> </xsl:text></xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
    <xsl:choose>
      <xsl:when test="*"> o-</xsl:when>
      <xsl:when test="following-sibling::*"> +-</xsl:when>
      <xsl:otherwise> `-</xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <xsl:template match="*" mode="line-break">
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Beispiel: Ausgabe mit baumartiger Formatierung.

o-[ExpenseReport]
    o-[Employee]
    | +-[Name] Salvatore Mangano
    | +-[SSN] 999-99-9999
    | +-[Dept] XSLT Hacking
    | +-[EmpNo] 1
    | +-[Position] Koch
    | `-[Manager] Großer Chef O'Reilly
    o-[PayPeriod]
    | +-[From] 1/1/02
    | `-[To] 1/31/02
    o-[Expenses]
       o-[Expense]
       | +-[Date] 12/20/01
       | +-[Account] 12345
       | +-[Desc] Herumgehangen, anstatt zur Konferenz zu fahren.
       | +-[Lodging] 500.00
       | +-[Transport] 50.00
       | +-[Fuel] 0
       | +-[Meals] 300.00
       | +-[Phone] 100
       | +-[Entertainment] 1000.00
       | `-[Other] 300.00
       o-[Expense]
          +-[Date] 12/20/01
          +-[Account] 12345
          +-[Desc] Am Strand
          +-[Lodging] 500.00
          +-[Transport] 50.00
          +-[Fuel] 0
          +-[Meals] 200.00
          +-[Phone] 20
          +-[Entertainment] 300.00
          `-[Other] 100.00

Sie können diesen Ansatz sogar noch weitertreiben, indem Sie ein Stylesheet erzeugen, das tree-control.xslt importiert und einen globalen Parameter entgegennimmt, der eine Liste von Elementnamen enthält, die verborgen oder ausgeblendet sein sollen. Ausgeblendete Ebenen werden durch das Präfix x gekennzeichnet (siehe die beiden folgenden Beispiele).

Beispiel: Stylesheet, das ausgeblendete Ebenen erzeugt.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="tree-control.xslt"/>
  <xsl:param name="collapse"/>
  <xsl:variable name="collapse-test" select="concat(' ',$collapse,' ')"/>
  <xsl:template match="*" mode="name">
    <xsl:if test="not(ancestor::*[contains($collapse-test,concat(' ',local-name(.),' '))])">
      <xsl:apply-imports/>
    </xsl:if>
  </xsl:template>
  <xsl:template match="*" mode="value">
    <xsl:if test="not(ancestor::*[contains($collapse-test,concat(' ',local-name(.),' '))])">
      <xsl:apply-imports/>
    </xsl:if>
  </xsl:template>
  <xsl:template match="*" mode="line-break">
    <xsl:if test="not(ancestor::*[contains($collapse-test,concat(' ',local-name(.),' '))])">
      <xsl:apply-imports/>
    </xsl:if>
  </xsl:template>
  <xsl:template match="*" mode="indent">
    <xsl:choose>
      <xsl:when test="self::*[contains($collapse-test,concat(' ',local-name(.),' '))]">
        <xsl:for-each select="ancestor::*">
          <xsl:text> </xsl:text>
        </xsl:for-each>
        <xsl:text> x-</xsl:text>
      </xsl:when>
      <xsl:when test="ancestor::*[contains($collapse-test,concat(' ',local-name(.),' '))]"/>
      <xsl:otherwise>
        <xsl:apply-imports/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Beispiel: Ausgabe mit $collapse="Employee PayPeriod".

o-[ExpenseReport]
      x-[Employee]
      x-[PayPeriod]
      o-[Expenses]
          o-[Expense]
          | +-[Date] 12/20/01
          | +-[Account] 12345
          | +-[Desc] Herumgehangen, anstatt zur Konferenz zu fahren.
          | +-[Lodging] 500.00
          | +-[Transport] 50.00
          | +-[Fuel] 0
          | +-[Meals] 300.00
          | +-[Phone] 100
          | +-[Entertainment] 1000.00
          | `-[Other] 300.00
         o-[Expense]
             +-[Date] 12/20/01
             +-[Account] 12345
             +-[Desc] Am Strand
             +-[Lodging] 500.00
             +-[Transport] 50.00
             +-[Fuel] 0
             +-[Meals] 200.00
             +-[Phone] 20
             +-[Entertainment] 300.00
             `-[Other] 100.00

Es gibt praktisch keine Beschränkung der Baumformate, die Sie erzeugen können, indem Sie das zugrunde liegende Stylesheet überschreiben. In objektorientierten Kreisen wird diese Technik als Template-Method-Pattern bezeichnet. Dabei erstellen Sie zunächst das Gerüst eines Algorithmus. Subklassen dürfen dann bestimmte Schritte neu definieren, ohne die Struktur des Algorithmus zu ändern. Im Fall von XSLT nehmen importierende Stylesheets die Stelle der Subklassen ein. Die Stärke dieses Beispiels leitet sich nicht aus der Tatsache her, dass das Anlegen einer baumartigen Struktur schwierig wäre – das ist es nämlich nicht. Stattdessen liegt die Stärke in der Fähigkeit, die Struktur des Beispiels wiederzuverwenden, während nur solche Aspekte betrachtet werden, die Sie ändern wollen.

  

zum Seitenanfang

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