Einfügen von Debug-Ausgaben automatisieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten Ihr Stylesheet in ein anderes Stylesheet transformieren, das mit Debug-Traces angereichert ist.

Lösung

Oliver Becker hat eine praktische Stylesheet-Transformation entwickelt, die aus einem beliebigen Eingabe-Stylesheet ein Ausgabe-Stylesheet mit Trace-Aufrufen erzeugt:

<!--
   Trace-Utility; modifiziert ein Stylesheet so, dass es Trace-Meldungen produziert
   Version 0.2
   GPL (c) Oliver Becker, 2002-02-13
   obecker@informatik.hu-berlin.de
-->
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:trace="http://www.obqo.de/XSL/Trace" xmlns:alias="http://www.w3.org/TransformAlias" exclude-result-prefixes="alias">
  <xsl:namespace-alias stylesheet-prefix="alias" result-prefix="xsl" />
  <!-- <xsl:output indent="yes" /> -->
  <!-- XSLT-Wurzelelement -->
  <xsl:template match="xsl:stylesheet | xsl:transform">
    <xsl:copy>
      <!-- Wir brauchen den trace-Namensraum für Namen und Modi -->
      <xsl:copy-of select="document('')/*/namespace::trace" />
      <!-- dito: Vielleicht wurde ein Namensraum nur als Attributwert benutzt -->
      <xsl:copy-of select="namespace::*|@*" />
      <xsl:apply-templates />
      <!-- hänge Hilfs-Templates an -->
      <xsl:copy-of select="document('')/*/xsl:template[@mode='trace:getCurrent' or @name='trace:getPath']" />
      <!-- berechne geringste Priorität, und füge ein Standard-Template mit einer niedrigeren Priorität für Elementknoten hinzu -->
      <xsl:variable name="priority" select="xsl:template/@priority[not(. &gt; current( )/xsl:template/@priority)]" />
      <xsl:variable name="newpri">
        <xsl:choose>
          <xsl:when test="$priority < −1">
            <xsl:value-of select="$priority - 1" />
          </xsl:when>
          <!-- falls es nur eine größere oder gar keine Priorität gibt -->
          <xsl:otherwise>-2</xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
      <!-- kopiere nur die Inhalte -->
      <alias:template match="*" priority="{$newpri}">
        <xsl:copy-of select="document('')/*/xsl:template[@name='trace:defaultRule']/node( )" />
      </alias:template>
    </xsl:copy>
  </xsl:template>
  <!-- XSLT-Templates -->
  <xsl:template match="xsl:template">
    <xsl:copy>
      <xsl:copy-of select="@*" />
      <!-- zuerst: Parameter kopieren -->
      <xsl:apply-templates select="xsl:param" />
      <alias:param name="trace:callstack" />
      <xsl:choose>
        <xsl:when test="@name">
          <alias:variable name="trace:current" select="concat($trace:callstack,'/{@name}')" />
        </xsl:when>
        <xsl:otherwise>
          <alias:variable name="trace:current" select="concat($trace:callstack,'/{count(preceding-sibling::xsl:template)+1}')" />
        </xsl:otherwise>
      </xsl:choose>
      <!-- gib eine Meldung aus -->
      <alias:message>
        <alias:call-template name="trace:getPath" />
        <alias:text>&#xA;   stack: </alias:text>
        <alias:value-of select="$trace:current" />
        <xsl:if test="@match or @mode">
          <alias:text> (</alias:text>
          <xsl:if test="@match">
            <alias:text>match="<xsl:value-of select="@match" />"</alias:text>
            <xsl:if test="@mode">
              <alias:text><xsl:text> </xsl:text></alias:text>
            </xsl:if>
          </xsl:if>
          <xsl:if test="@mode">
            <alias:text>mode="<xsl:value-of select="@mode" />"</alias:text>
          </xsl:if>
          <alias:text>)</alias:text>
        </xsl:if>
        <xsl:apply-templates select="xsl:param" mode="traceParams" />
      </alias:message>
      <!-- bearbeite Kinder außer Parametern -->
      <xsl:apply-templates select="node( )[not(self::xsl:param)]" />
    </xsl:copy>
  </xsl:template>
  <!-- füge callstack-Parameter für apply-templates und call-template hinzu -->
  <xsl:template match="xsl:apply-templates | xsl:call-template">
    <xsl:copy>
      <xsl:copy-of select="@*" />
      <alias:with-param name="trace:callstack" select="$trace:current" />
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
  <!-- gib Parameterwerte aus -->
  <xsl:template match="xsl:param" mode="traceParams">
    <alias:text>&#xA;   param: name="<xsl:value-of select="@name" />" value="</alias:text>
    <alias:value-of select="${@name}" />" <alias:text />
    <!--
    <alias:copy-of select="${@name}" />" <alias:text />
    -->
  </xsl:template>
  <!-- gibt Variablenwerte aus -->
  <xsl:template match="xsl:variable">
    <xsl:copy>
      <xsl:copy-of select="@*" />
      <xsl:apply-templates />
    </xsl:copy>
    <xsl:if test="ancestor::xsl:template">
      <alias:message>   variable: name="<xsl:value-of select="@name" />" value="<alias:text />
      <alias:value-of select="${@name}" />" </alias:message>
    </xsl:if>
  </xsl:template>
  <!-- kopiere alle unbearbeiteten Knoten -->
  <xsl:template match="*|@*">
    <xsl:copy>
      <xsl:apply-templates select="@*" />
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
  <!-- ************************************************************************ -->
  <!-- Die folgenden Templates werden in das modifizierte Stylesheet kopiert.   -->
  <!-- ************************************************************************ -->
  <!--
   | trace:getPath
   | berechne den absoluten Pfad des Kontextknotens
   + -->
  <xsl:template name="trace:getPath">
    <xsl:text>node: </xsl:text>
    <xsl:for-each select="ancestor::*">
      <xsl:value-of select="concat('/', name( ), '[',count(preceding-sibling::*[name( )=name(current( ))])+1, ']')" />
    </xsl:for-each>
    <xsl:apply-templates select="." mode="trace:getCurrent" />
  </xsl:template>
  <!--
   | trace:getCurrent
   | berechne den letzten Schritt des Lokalisierungspfades
   | je nach Knotentyp
   + -->
  <xsl:template match="*" mode="trace:getCurrent">
    <xsl:value-of select="concat('/', name( ), '[',count(preceding-sibling::*[name( )=name(current( ))])+1, ']')" />
  </xsl:template>
  <xsl:template match="@*" mode="trace:getCurrent">
    <xsl:value-of select="concat('/@', name( ))" />
  </xsl:template>
  <xsl:template match="text( )" mode="trace:getCurrent">
    <xsl:value-of select="concat('/text( )[', count(preceding-sibling::text( ))+1,']')" />
  </xsl:template>
  <xsl:template match="comment( )" mode="trace:getCurrent">
    <xsl:value-of select="concat('/comment( )[',count(preceding-sibling::comment( ))+1, ']')" />
  </xsl:template>
  <xsl:template match="processing-instruction( )" mode="trace:getCurrent">
    <xsl:value-of select="concat('/processing-instruction( )[',count(preceding-sibling::processing-instruction( ))+1, ']')" />
  </xsl:template>
  <!--
   | trace:defaultRule
   | Standardregel mit Parameterübergabe
   + -->
  <xsl:template name="trace:defaultRule">
    <xsl:param name="trace:callstack" />
    <xsl:message>
      <xsl:call-template name="trace:getPath" />
      <xsl:text>&#xA;   default rule applied</xsl:text>
    </xsl:message>
    <xsl:apply-templates>
      <xsl:with-param name="trace:callstack" select="$trace:callstack" />
    </xsl:apply-templates>
  </xsl:template>
</xsl:transform>

Diskussion

Betrachten Sie das folgende Beispiel einer Debug-Ausgabe, die sich ergibt, wenn diese Transformation auf postorder.orgchart.xslt aus dem Rezept Durchführen eines Postorder Traversal angewendet wird:

node: /employee[1]
   stack: /1 (match="/employee")
node: /employee[1]/employee[1]
    stack: /1/2 (match="employee[employee]")
node: /employee[1]/employee[1]/employee[1]
    stack: /1/2/3 (match="employee")
node: /employee[1]/employee[1]/employee[2]
    stack: /1/2/2 (match="employee[employee]")
node: /employee[1]/employee[1]/employee[2]/employee[1]
    stack: /1/2/2/3 (match="employee")
node: /employee[1]/employee[1]/employee[2]
    stack: /1/2/2/reportsTo
node: /employee[1]/employee[1]/employee[2]
    stack: /1/2/2/HimHer
node: /employee[1]/employee[1]
    stack: /1/2/reportsTo
node: /employee[1]/employee[1]
    stack: /1/2/HimHer
node: /employee[1]/employee[2]
    stack: /1/2 (match="employee[employee]")
node: /employee[1]/employee[2]/employee[1]
    stack: /1/2/3 (match="employee")
node: /employee[1]/employee[2]/employee[2]
    stack: /1/2/2 (match="employee[employee]")
node: /employee[1]/employee[2]/employee[2]/employee[1]
    stack: /1/2/2/3 (match="employee")
node: /employee[1]/employee[2]/employee[2]/employee[2]
    stack: /1/2/2/3 (match="employee")
node: /employee[1]/employee[2]/employee[2]/employee[3]
    stack: /1/2/2/3 (match="employee")
node: /employee[1]/employee[2]/employee[2]
    stack: /1/2/2/reportsTo
node: /employee[1]/employee[2]/employee[2]
    stack: /1/2/2/HimHer
node: /employee[1]/employee[2]
    stack: /1/2/reportsTo
node: /employee[1]/employee[2]
    stack: /1/2/HimHer
node: /employee[1]/employee[3]
    stack: /1/2 (match="employee[employee]")
node: /employee[1]/employee[3]/employee[1]
    stack: /1/2/2 (match="employee[employee]")
node: /employee[1]/employee[3]/employee[1]/employee[1]
    stack: /1/2/2/3 (match="employee")
node: /employee[1]/employee[3]/employee[1]/employee[2]
    stack: /1/2/2/3 (match="employee")
node: /employee[1]/employee[3]/employee[1]/employee[3]
    stack: /1/2/2/3 (match="employee")
node: /employee[1]/employee[3]/employee[1]
    stack: /1/2/2/reportsTo
node: /employee[1]/employee[3]/employee[1]
    stack: /1/2/2/HimHer
node: /employee[1]/employee[3]/employee[2]
    stack: /1/2/2 (match="employee[employee]")
node: /employee[1]/employee[3]/employee[2]/employee[1]
    stack: /1/2/2/2 (match="employee[employee]")
node: /employee[1]/employee[3]/employee[2]/employee[1]/employee[1]
    stack: /1/2/2/2/3 (match="employee")
node: /employee[1]/employee[3]/employee[2]/employee[1]
    stack: /1/2/2/2/reportsTo
node: /employee[1]/employee[3]/employee[2]/employee[1]
    stack: /1/2/2/2/HimHer
node: /employee[1]/employee[3]/employee[2]/employee[2]
    stack: /1/2/2/2 (match="employee[employee]")
node: /employee[1]/employee[3]/employee[2]/employee[2]/employee[1]
    stack: /1/2/2/2/3 (match="employee")
node: /employee[1]/employee[3]/employee[2]/employee[2]/employee[2]
    stack: /1/2/2/2/3 (match="employee")
node: /employee[1]/employee[3]/employee[2]/employee[2]
    stack: /1/2/2/2/reportsTo
node: /employee[1]/employee[3]/employee[2]/employee[2]
    stack: /1/2/2/2/HimHer
node: /employee[1]/employee[3]/employee[2]/employee[3]
    stack: /1/2/2/2 (match="employee[employee]")
node: /employee[1]/employee[3]/employee[2]/employee[3]/employee[1]
    stack: /1/2/2/2/3 (match="employee")
node: /employee[1]/employee[3]/employee[2]/employee[3]
    stack: /1/2/2/2/reportsTo
node: /employee[1]/employee[3]/employee[2]/employee[3]
    stack: /1/2/2/2/HimHer
node: /employee[1]/employee[3]/employee[2]
    stack: /1/2/2/reportsTo
node: /employee[1]/employee[3]/employee[2]
    stack: /1/2/2/HimHer
node: /employee[1]/employee[3]
   stack: /1/2/reportsTo
node: /employee[1]/employee[3]
   stack: /1/2/HimHer
node: /employee[1]
   stack: /1/reportsTo
node: /employee[1]
   stack: /1/HimHer

Das modifizierte Stylesheet gibt Trace-Meldungen mit Hilfe des xsl:message-Mechanismus aus. Dabei sieht das Format bei allen verarbeiteten Knoten wie folgt aus:

node: [XPath zu diesem Knoten]
   stack: [Aufruf-Stack der aufgerufenen Templates]
   param: name="[Parametername]" value="[Parameterwert]"
   weitere Parameter ...
   variable: name="[Variablenname]" value="[Variablenwert]"
   weitere Variablen ...

Der Aufruf-Stack hat die Form eines Pfades (mit / als Trennzeichen) und umfasst alle übergebenen Templates. Falls ein Template über ein name-Attribut verfügt, dann wird dieser Name verwendet. Ansonsten erscheint die Nummer (die Position) des Templates im Stack. Sollte das aktuelle Template keinen Namen haben, dann wird das match-Attribut angezeigt. Falls ein mode-Attribut angegeben ist, dann wird dessen Wert angezeigt.

Ein bekanntes Problem besteht darin, dass bei Parametern oder Variablen die Ausgabe in deren String-Wert besteht (der mit xsl:value-of erzeugt wird). Bei Knotenmengen und Teilen von Ergebnisbäumen macht das keinen Sinn. Allerdings führt ein xsl:copy-of zu einem Fehler, falls die Variable elternlose Attribut- oder Namensraumknoten enthält.

Siehe auch

Den Quellcode von trace.xslt sowie weitere Beispiele finden Sie unter Oliver's XSLT page.

  

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