Elemente in Attribute umwandeln

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie haben ein Dokument, das Informationen mit Hilfe von Kindelementen kodiert, und würden stattdessen lieber Attribute verwenden.

Lösung

Wie beim Rezept Attribute in Elemente umwandeln können Sie hier den überschreibenden Kopiervorgang verwenden. Wenn Sie jedoch Elemente in Attribute umwandeln, müssen Sie selektiv feststellen, wo die Transformation angewandt wird. Es ist nämlich so, dass die Idee, alle Elemente in Attribute zu transformieren, unsinnig ist. Das folgende Stylesheet kehrt die Attribut-zu-Element-Transformation um, die wir im vorherigen Rezept ausgeführt haben:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="copy.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8"/>
  <xsl:template match="person">
    <xsl:copy>
      <xsl:for-each select="*">
        <xsl:attribute name="{local-name(.)}">
          <xsl:value-of select="."/>
        </xsl:attribute>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Diskussion

XSLT 1.0

Die Umwandlung von Elementen in Attribute ist nicht so einfach wie die Umwandlung in die umgekehrte Richtung. Wenn die Elemente, die in Attribute umgewandelt werden sollen, selbst Attribute besitzen, müssen Sie entscheiden, was aus ihnen werden soll. In der gerade gezeigten Lösung wären sie verloren. Eine andere Alternative würde es sein, sie dem neuen Elternelement bekannt zu machen:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="copy.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8"/>
  <xsl:template match="person">
    <xsl:copy>
      <xsl:for-each select="*">
        <xsl:attribute name="{local-name(.)}">
          <xsl:value-of select="."/>
        </xsl:attribute>
        <xsl:copy-of select="@*"/>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Das funktioniert jedoch nur, wenn alle in Frage kommenden Attributnamen eindeutig sind. Falls das nicht der Fall ist, müssen Sie die Attribute umbenennen, etwa so:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="copy.xslt"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8"/>
  <xsl:template match="person">
    <xsl:copy>
      <xsl:for-each select="*">
        <xsl:attribute name="{local-name(.)}">
          <xsl:value-of select="."/>
        </xsl:attribute>
        <xsl:variable name="elem-name" select="local-name(.)"/>
        <xsl:for-each select="@*">
          <xsl:attribute name="{concat($elem-name,'-',local-name(.))}">
            <xsl:value-of select="."/>
          </xsl:attribute>
        </xsl:for-each>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Eine weitere Komplikation tritt auf, wenn die Geschwisterelemente keine eindeutigen Namen tragen, weil diese dann beim Übergang zum Attribut kollidieren würden. Eine andere mögliche Strategie sieht so aus, dass ein Attribut nur dann aus einem Element erzeugt wird, wenn das Element selbst keine Attribute oder Elementkinder besitzt, sich nicht in seinem Elternelement wiederholt und Eltern ohne Attribute hat:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="copy.xslt"/>
  <xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8"/>
  <!-- Elemente erfassen, die Eltern sind -->
  <xsl:template match="*[*]">
    <xsl:choose>
      <!-- Kinder nur dann konvertieren, wenn dieses Element selbst keine Attribute besitzt -->
      <xsl:when test="not(@*)">
        <xsl:copy>
          <!-- Kinder in Attribute konvertieren, wenn das Kind keine Kinder oder Attribute besitzt und unter seinen Geschwistern einen eindeutigen Namen trägt -->
          <xsl:for-each select="*">
            <xsl:choose>
              <xsl:when test="not(*) and not(@*) and not(preceding-sibling::*[name( ) = name(current( ))]) and not(following-sibling::*[name( ) = name(current( ))])">
                <xsl:attribute name="{local-name(.)}">
                  <xsl:value-of select="."/>
                </xsl:attribute>
              </xsl:when>
              <xsl:otherwise>
                <xsl:apply-templates select="."/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
        </xsl:copy>
      </xsl:when>
      <xsl:otherwise>
        <xsl:copy>
          <xsl:apply-templates/>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

XSLT 2.0

Hier vereinfachen und beschleunigen Sie die 1.0-Lösung teilweise, indem Sie xsl:for-each-group einsetzen. Der Trick besteht darin, group-by="name( )" einzusetzen, um festzustellen, ob es Geschwister mit identischen Namen gibt. Außerdem macht diese Lösung die Attribute des umgewandelten Elements den Eltern bekannt, was Sie auch in der 1.0-Lösung tun können:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="copy.xslt"/>
  <xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8"/>
  <!-- Elemente erfassen, die Eltern sind -->
  <xsl:template match="*[*]">
    <xsl:choose>
      <!-- Nur Kinder konvertieren, wenn dieses Element keine eigenen Attribute besitzt -->
      <xsl:when test="not(@*)">
        <xsl:copy>
          <!-- Kinder in Attribute umwandeln, wenn das Kind keine Kinder oder Attribute besitzt und unter seinen Geschwistern einen eindeutigen Namen trägt -->
          <xsl:for-each-group select="*" group-by="name( )">
            <xsl:choose>
              <xsl:when test="not(*) and count(current-group( )) eq 1">
                <xsl:attribute name="{local-name(.)}">
                  <xsl:value-of select="."/>
                </xsl:attribute>
                <!-- Attribute des Kindes auf das Elternelement kopieren -->
                <xsl:copy-of select="@*"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:apply-templates select="current-group( )"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </xsl:copy>
      </xsl:when>
      <xsl:otherwise>
        <xsl:copy>
          <xsl:apply-templates select="@*| node( )"/>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Achtung!
Sowohl im 1.0- als auch im 2.0-Stylesheet gibt es eine Beschränkung dahingehend, dass sie eine bestimmte Art von kanonischer Struktur annehmen, bei der auf einer bestimmten Stufe alle Elemente, die sich für eine Konvertierung in Attribute qualifiziert haben, vor den Elementen stehen, bei denen das nicht der Fall ist. Nachfolgend sehen Sie ein Beispieldokument, das diese Annahme verletzt:

<E1>
  <E2>
    <e31>a</e31>
    <e32>b</e32>
    <e33>c</e33>
  </E2>
  <test>a</test>
  <E2>
    <e31>u</e31>
    <e32>v</e32>
    <e33>w</e33>
  </E2>
  <E2>
    <e31>x</e31>
    <e32>y</e32>
    <e33>z</e33>
  </E2>
</E1>

Beachten Sie, wie das test-Element theoretisch in ein Attribut von E1 konvertiert werden könnte. Das Stylesheet versucht auch, genau diese Konvertierung auszuführen. Sie schlägt jedoch wegen einer Randbedingung in XSLT fehl, laut der Attribute nur dann auf einen Knoten kopiert werden können, bevor alle anderen Knoten kopiert werden. Wenn Sie es mit solch scheußlichen Dokumenten wie diesem zu tun haben, brauchen Sie zwei Durchgänge. Während des ersten Durchgangs konvertieren Sie die Elemente noch nicht in Attribute, sondern kopieren sie als Elemente, die mit einem besonderen Attribut gekennzeichnet sind, das anzeigt, dass sie für die Konvertierung geeignet sind. Im zweiten Durchgang konvertieren Sie dann alle entsprechend gekennzeichneten Elemente zuerst in Attribute ihrer Eltern und kopieren dann alle anderen Kindelemente unverändert zu ihren Eltern.

  

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