verenice: XSLT / Schleife und choose

Hallo,
ich möchte gern die Nummerierung der Überschriften bei meinem XML-File selbst steuern, daher, ich benutze nicht die praktische number()- Funktion.

Ich habe einen Parameter, der die Hauptkapitel durchnummieren soll.
Das funktioniert jetzt auch. Allerdings bleibt der Prozeß solange bei einer Überschrift im XML-File stehen, bis die Bedingung des Parameters nicht mehr zutrifft.:-/

Ist es möglich bei der Bedingung dynamisch zu ermitteln, wieviele Überschriften das XML-File hat. Mit count() und sum() habe ich es noch nicht geschafft. Oder kann ich auch eine xquery-Abfrage in mein XSLT-File einbauen?

Eigentlich wollte ich in der Kontrollstruktur choose zwei Auswahlmöglichkeiten haben und zwar:
<xsl:when test="@kapiteltyp='Hauptkapitel'">
und
<xsl:when test="@kapiteltyp='Unterkapitel'">
Allerdings bekomme ich das nicht mit dem Zählparameter hin.
Die Bedingung für den Parameter muß angeblich im choose-Konstrukt stehen.

Quelltext:
<?xml version="1.0" encoding="UTF-8"?>
<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:preserve-space elements="test"/>
<xsl:template match="/">
<html>
<head>
<title>Handbuch</title>
</head>
<body>
<pre>
<xsl:apply-templates select="Handbuch/Handbuchinhalt/Kapitel">
<xsl:with-param name="Zaehlen">1</xsl:with-param>
</xsl:apply-templates>
</pre>
</body>
</html>
</xsl:template>

<xsl:template match="Handbuch/Handbuchinhalt/Kapitel" name="Schleife">
<xsl:param name="Zaehlen"/>
xsl:choose
<!-- Bei der Verarbeitung bleibt der Prozeß bei jeder Überschrift 27 x stehen, daher werden 27 x 27 Überschriften ausgegeben. -->
<xsl:when test="$Zaehlen &lt;=27 and @kapiteltyp='Hauptkapitel'">
<xsl:value-of select="concat($Zaehlen, '.' , ' ' , //Kapitel[position()=$Zaehlen])"/>
<br />
<xsl:call-template name="Schleife">
<xsl:with-param name="Zaehlen" select="$Zaehlen + 1"/>
</xsl:call-template>
</xsl:when>
<!--
<xsl:when test="@kapiteltyp='Hauptkapitel'">
<h1>
<xsl:value-of select="concat($Zaehlen, '.' , ' ' , node())"/>
</h1>
<br/>
<xsl:call-template name="Schleife">
<xsl:with-param name="Zaehlen" select="$Zaehlen + 1"/>
</xsl:call-template>
</xsl:when>
-->
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Vielen Dank für Eure Hilfe schon mal im voraus!

Viele Grüße
verenice

  1. Hallo,

    Ist es möglich bei der Bedingung dynamisch zu ermitteln, wieviele Überschriften das XML-File hat.

    Ja, aber das kannst du auch statisch machen, schließlich wird dein XML nicht während der XSL-Transformation erweitert.

    (globale) variable name="anzahlKap" select="count(//Kapitel)"

    Mit count() und sum() habe ich es noch nicht geschafft.

    ???

    Oder kann ich auch eine xquery-Abfrage in mein XSLT-File einbauen?

    Nein, aber auch wenn ja, was würde das ändern?

    Die Bedingung für den Parameter muß angeblich im choose-Konstrukt stehen.

    Das verstehe ich nicht.

    Quelltext:

    <xsl:apply-templates select="Handbuch/Handbuchinhalt/Kapitel">
    <xsl:with-param name="Zaehlen">1</xsl:with-param>
    </xsl:apply-templates>

    Damit initiierst du das Template für _jedes einzelnen_ <Kapitel> mit dem Zähler 1 auf.

    <xsl:template match="Handbuch/Handbuchinhalt/Kapitel" name="Schleife">

    Das Template ergibt keinen Sinn.

    <!-- Bei der Verarbeitung bleibt der Prozeß bei jeder Überschrift 27 x stehen, daher werden 27 x 27 Überschriften ausgegeben. -->

    Ja, denn genau das ist im Template definiert:

    <xsl:when test="$Zaehlen &lt;=27 and @kapiteltyp='Hauptkapitel'">

    Sagt: wenn $x kleiner als 27 und ...

    <xsl:value-of select="concat($Zaehlen, '.' , ' ' , //Kapitel[position()=$Zaehlen])"/>

    Sagt: gib aus: "$x. TextVomKapitel[...]"

    <xsl:call-template name="Schleife">
    <xsl:with-param name="Zaehlen" select="$Zaehlen + 1"/>
    </xsl:call-template>

    *** Sagt: rufe Template (hier rekursiv) auf mit $x+1

    Und was passiert?
    Das Template wird aufgerufen ($x = 2) --> rattert durch erreicht *** und ruft Template (hier rekursiv) auf mit $x+1

    Und was passiert?
    Das Template wird aufgerufen ($x = 3) --> rattert durch erreicht *** und ruft Template (hier rekursiv) auf mit $x+1

    Und was passiert?
    Das Template wird aufgerufen ($x = 4) --> rattert durch erreicht *** und ruft Template (hier rekursiv) auf mit $x+1

    Und so witer ... bis Zähler 26 ist --> rattert durch erreicht *** und ruft Template (hier rekursiv) auf mit $x+1
    Das Template wird aufgerufen ($x = 27) --> Bedingung ( $x kleiner als 27) nicht mehr erfüllt, gehe zu nächsten when oder zu otherwiese und falls nicht vorhanden, bedende die Abarbeitung des Templates.

    </xsl:when>

    $Zaehlen = 27

    <!--
    <xsl:when test="@kapiteltyp='Hauptkapitel'">
    <h1>
    <xsl:value-of select="concat($Zaehlen, '.' , ' ' , node())"/>
    </h1>
    <br/>
    <xsl:call-template name="Schleife">
    <xsl:with-param name="Zaehlen" select="$Zaehlen + 1"/>
    </xsl:call-template>

    Ruft das Template mit 27+1 auf
    Das Template wird aufgerufen ($x = 28) --> Bedingung ( $x kleiner als 27) nicht mehr erfüllt, gehe zu nächsten when oder zu otherwiese.

    Das Template wird aufgerufen ($x = 29) --> Bedingung ( $x kleiner als 27) nicht mehr erfüllt, gehe zu nächsten when oder zu otherwiese.
    ...

    Und wir haben eine wunderbare Endlosschleife geschaffen, nur gut, dass das ganze zweite when auskommentiert war ;-)

    Wenn du für deinen Vergleich bereits position() verwendest "Kapitel[position()=$Zaehlen]" , warum gibst du nicht einfach die Position aus?
    Da ich aber nicht weiss, wie deine Nummerierung aussehen soll (unterkapitel??) kann ich auch keine Vorschläge machen.

    Grüße
    Thomas

    1. Hallo Thomas,
      vielen vielen Dank für Deine lange Antwort!!!

      (globale) variable name="anzahlKap" select="count(//Kapitel)".

      Danke für den Tip!!!
      (Das ist mir heute morgen auch eingefallen.)

      Quelltext:
      <?xml version="1.0" encoding="UTF-8"?>
      <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:preserve-space elements="test"/>

      <xsl:param name="CountKapitel" select="count(//Kapitel)" />

      <xsl:template match="/">
      <html>
      <head>
      <title>Handbuch</title>
      </head>
      <body>
      <pre>
      <xsl:apply-templates select="Handbuch/Handbuchinhalt/Kapitel"/>
      </pre>
      </body>
      </html>
      </xsl:template>

      <xsl:template match="Kapitel">
      <xsl:value-of select="$CountKapitel" />
      <br />
      </xsl:template>

      </xsl:stylesheet>

      Der Wert, den count() ermittelt ist, richtig.*freu*
      Damit kann ich also jetzt theoretisch die Bedingung steuern.

      Aber warum wird der Wert, den count() ermittelt, 20 x ausgegeben?:-/
      Verstehe ich nicht.

      Gruß
      verenice

      1. Hallo,

        <xsl:apply-templates select="Handbuch/Handbuchinhalt/Kapitel"/>

        Aber warum wird der Wert, den count() ermittelt, 20 x ausgegeben?:-/
        Verstehe ich nicht.

        Weil du das Template  mit apply-templates für jedes einzele der Kapitelelemente instantiierst. (Und offenbar hast du 20 Kapitel)

        Grüße
        Thomas

    2. Hallo Thomas,

      Wenn du für deinen Vergleich bereits position() verwendest "Kapitel[position()=$Zaehlen]" , warum gibst du nicht einfach die Position aus?
      Da ich aber nicht weiss, wie deine Nummerierung aussehen soll (unterkapitel??) kann ich auch keine Vorschläge machen.

      Auszug-XML-File:
      [...]

      <Kapitel id="7" kapiteltyp="Hauptkapitel"  titelAbk="Kapitel1">Kapitel blabla</Kapitel>

      <Kapitel id="22" kapiteltyp="Unterkapitel" titelAbk="Kapitel2">Kapitel test test</Kapitel>

      <Kapitel id="45" kapiteltyp="Unterkapitel"  titelAbk="Kapitel3">Kapitel la la la</Kapitel>

      [...]

      Die Funktion Position() habe ich schon getestet, allerdings kommt sie für mich nicht in Frage.
      Da ja die Position im XML-File ermittelt wird und das geht bei meinem XML-File mit Haupt- und Unterkapitel nicht so gut.
      Wenn das 8. Kapitel zwei Unterkapite hat, dann erhält das nächste Hauptkapitel die Nummer 11., da position() einfach weiterzählt.

      Daher habe ich es mit einer Zählvariablen (bzw. Parameter), die die Nummerierung der Hauptkapitel übernimmt, versucht zu lösen.

      Viele Grüße
      verenice

    3. Hallo Thomas,

      Quelltext:

      <xsl:apply-templates select="Handbuch/Handbuchinhalt/Kapitel">
      <xsl:with-param name="Zaehlen">1</xsl:with-param>
      </xsl:apply-templates>

      Damit initiierst du das Template für _jedes einzelnen_ <Kapitel> mit dem Zähler 1 auf.

      Wenn ich aber call-template benutze, um aus dem Starttemplate auf das nächste Template zuzugreifen und den Parameter zu übergeben, dann kann ich den Knoten Kapitel nicht matchen.:-/
      Allerdings wird mit Call-template die Anzahl der Kapitel anstatt 27x nur 1x ausgegeben, die ich mit Count() ermittelt habe.

      Quelltext:

      <?xml version="1.0" encoding="UTF-8"?>
      <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:preserve-space elements="test"/>

      <!-- Globaler Paramter, der die Anzahl der Kapitelüberschriften speichert -->
      <xsl:param name="CountKapitel" select="count(//Kapitel)" />

      <xsl:template match="/">
      <html>
      <head>
      <title>Handbuch</title>
      </head>
      <body>
      <pre>
      <!-- Lokaler Parameter, der die Nummerierung der Hauptkapitel steuern soll -->
      <xsl:call-template name="Schleife">
      <xsl:with-param name="HauptkapitelNr">0</xsl:with-param>
      </xsl:call-template>
      </pre>
      </body>
      </html>
      </xsl:template>

      <xsl:template match="Handbuch/Handbuchinhalt" name="Schleife">
      <xsl:param name="HauptkapitelNr" />

      <!-- Test: in welchem Knoten bin ich -->
      <xsl:value-of select="name()" />

      xsl:choose
      <xsl:when test="Kapitel[@kapiteltyp='Hauptkapitel'] ">

      <xsl:call-template name="Hauptkapitel" />

      </xsl:when>
      </xsl:choose>
      </xsl:template>

      [...]

      Viele Grüße
      verenice

      1. Hallo,

        Wenn ich aber call-template benutze, um aus dem Starttemplate auf das nächste Template zuzugreifen und den Parameter zu übergeben, dann kann ich den Knoten Kapitel nicht matchen.:-/

        Du verwendest in deinem Template weder Das Parameter $HauptkapitelNr noch $CountKapitel.

        Ich kann dir noch immer nicht wirklich helfen, denn:
        für diese Ausgabe der Hauptkapitel würde ich einfach ein Template mit mode schreiben und darin eine for-each verwenden, dann wäre es kein Problem mit position().
        ABER: ich weiss nicht, was du mit den Unterkapitel machen willst!
        Deshalb: schreibe mal bitte wie deine gewünsche Ausgabe aussehen soll.

        1. Kapitel (hautpt)
        2. Kapitel (hautpt)
        3. Kapitel (hautpt)
          3.1. Kapitel (unter)
          3.2. Kapitel (unter)

        oder wie auch immer, aber bis mir das nicht klar ist, nützten dir meine Vorschläge nichts, denn ich habe bedauerlicherweise keinen blassen Schimmer, was du eigentlich erreichen möchtest.

        Grüße
        Thomas

        1. Hallo Thomas,
          vielen Dank für Deine Antwort!

          Ich kann dir noch immer nicht wirklich helfen, denn:
          für diese Ausgabe der Hauptkapitel würde ich einfach ein Template mit mode schreiben und darin eine for-each verwenden, dann wäre es kein Problem mit position().

          Die Funktion position() zählt doch aber die Position des Elemetes im XML-File durch. Bei meiner XMl-File-Struktur ist position() leider nicht so gut geeignet.:-/
          Zum Beispiel habe ich drei Hauptkapitel, dann kommen für das dritte Hauptkapitel zwei Unterkapitel, dann würde position() das nächste Hauptkapitel einfach mit 6. nummerieren, obwohl es eigentlich dann das vierte Hauptkapitel wäre.

          Beispiel mit position():
          1. Hauptkapitel
          2. Hauptkapitel
          3. Hauptkapitel
          3.1. Unterkapitel
          3.2. Unterkapitel
          6. Hauptkapitel
          7. Hauptkapitel
          7.1. ...
          7.2. ...
          7.3. ...
          11. Hauptkapitel
          12. ...
          ...

          ABER: ich weiss nicht, was du mit den Unterkapitel machen willst!
          Deshalb: schreibe mal bitte wie deine gewünsche Ausgabe aussehen soll.

          1. Kapitel (hautpt)
          2. Kapitel (hautpt)
          3. Kapitel (hautpt)
              3.1. Kapitel (unter)
              3.2. Kapitel (unter)

          Ja, das wäre so genau richtig.
          So würde ich das gern haben.:o)

          Vielen Dank noch mal für Deine Hilfe!

          Viele Grüße
          verenice

          1. Hallo,

            Die Funktion position() zählt doch aber die Position des Elemetes im XML-File durch.

            Schon, aber for-each erzeugt eine Knotenmenge, position() innerhalb von for-each bezieht sich dann nur auf diese Knotenmenge.

            Zum Beispiel habe ich drei Hauptkapitel, dann kommen für das dritte Hauptkapitel zwei Unterkapitel, dann würde position() das nächste Hauptkapitel einfach mit 6. nummerieren, obwohl es eigentlich dann das vierte Hauptkapitel wäre.

            1. Kapitel (hautpt)
            2. Kapitel (hautpt)
            3. Kapitel (hautpt)
                3.1. Kapitel (unter)
                3.2. Kapitel (unter)

            Ja, das wäre so genau richtig.
            So würde ich das gern haben.:o)

            Test - XML (ich habe es mehrfach verschachtelt
            -------------------------------------------------------------
            <?xml version="1.0" encoding="iso-8859-1"?>
            <?xml-stylesheet type="text/xsl" href="verennice.xsl"?>
            <Handbuch>
             <Handbuchinhalt>
              <Kapitel kapiteltyp="Hauptkapitel">eins</Kapitel>
              <Kapitel kapiteltyp="Hauptkapitel">zwei</Kapitel>
              <Kapitel kapiteltyp="Hauptkapitel">drei
               <Kapitel kapiteltyp="Unterkapitel">drei eins</Kapitel>
               <Kapitel kapiteltyp="Unterkapitel">drei zwei</Kapitel>
              </Kapitel>
              <Kapitel kapiteltyp="Hauptkapitel">vier</Kapitel>
              <Kapitel kapiteltyp="Hauptkapitel">fünf
               <Kapitel kapiteltyp="Unterkapitel">fünf eins</Kapitel>
               <Kapitel kapiteltyp="Unterkapitel">fünf zwei
                <Kapitel kapiteltyp="Unterkapitel">fünf zwei eins</Kapitel>
                <Kapitel kapiteltyp="Unterkapitel">fünf zwei zwei
                 <Kapitel kapiteltyp="Unterkapitel">fünf zwei eins eins</Kapitel>
                 <Kapitel kapiteltyp="Unterkapitel">fünf zwei eins zwei</Kapitel>
                </Kapitel>
               </Kapitel>
               <Kapitel kapiteltyp="Unterkapitel">fünf drei</Kapitel>
              </Kapitel>
              <Kapitel kapiteltyp="Hauptkapitel">sechs</Kapitel>
              <Kapitel kapiteltyp="Hauptkapitel">sieben</Kapitel>
             </Handbuchinhalt>
            </Handbuch>
            ---------------------------------------------------------

            Hast du nur zwei Ebenen (Kap[haupt]/Kap[unter]) reicht folgendes XSL:
            ---------------------------------------------------------
            <?xml version="1.0" encoding="iso-8859-1"?>
            <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
            <xsl:template match="/">
             <html>
              <head>
               <title>kapitel</title>
              </head>
              <body>
               <xsl:apply-templates select="Handbuch/Handbuchinhalt" mode="kapitelzaehler" />
              </body>
             </html>
            </xsl:template>
            <xsl:template match="Handbuch/Handbuchinhalt" mode="kapitelzaehler">
             <xsl:for-each select="Kapitel">
              <xsl:variable name="pos" select="position()" />
              <xsl:value-of select="concat($pos, '. ', text())" /><br />
              <xsl:if test="Kapitel">
               <xsl:for-each select="Kapitel">
                <xsl:value-of select="concat($pos, '.', position(), '. ', text())" /><br />
               </xsl:for-each>
              </xsl:if>
             </xsl:for-each>
            </xsl:template>
            </xsl:stylesheet>

            -------------------------------------------------------------

            Hast du mehr als zwei Ebenen, solltest du sowas benützen (funktioniert natürlich auch bei nur zwei Ebenen):
            -------------------------------------------------------------

            <?xml version="1.0" encoding="iso-8859-1"?>
            <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
            <xsl:template match="/">
             <html>
              <head>
               <title>kapitel</title>
              </head>
              <body>
               <xsl:apply-templates select="Handbuch/Handbuchinhalt" mode="kapitelzaehler" />
              </body>
             </html>
            </xsl:template>
            <xsl:template match="Handbuch/Handbuchinhalt" mode="kapitelzaehler">
             <xsl:for-each select="Kapitel">
              <xsl:variable name="pos" select="position()" />
              <xsl:value-of select="concat($pos, '. ', text())" /><br />
              <xsl:if test="Kapitel">
               <xsl:for-each select="Kapitel">
                <xsl:call-template name="zaehler">
                 <xsl:with-param name="knoten" select="name()" />
                 <xsl:with-param name="pos_eltern" select="$pos" />
                </xsl:call-template>
               </xsl:for-each>
              </xsl:if>
             </xsl:for-each>
            </xsl:template>
            <xsl:template name="zaehler">
             <xsl:param name="knoten" />
             <xsl:param name="pos_eltern" />
             <xsl:variable name="pos" select="position()" />
             <xsl:value-of select="concat($pos_eltern, '.', $pos, '. ', text())" /><br />
              <xsl:if test="*[name() = $knoten]">
              <xsl:for-each select="*[name() = $knoten]">
               <xsl:call-template name="zaehler">
                <xsl:with-param name="knoten" select="name()" />
                <xsl:with-param name="pos_eltern" select="concat($pos_eltern, '.', $pos)" />
               </xsl:call-template>
              </xsl:for-each>
             </xsl:if>
            </xsl:template>
            </xsl:stylesheet>

            -----------------------------------------------------------------

            Das Template "zaehler" kann auch für andere Elemente als Kapitel verwendet werden.

            Die Ausgabe vom Test-XML mit dem letzen XSLT:

            1. eins
            2. zwei
            3. drei
            3.1. drei eins
            3.2. drei zwei
            4. vier
            5. fünf
            5.1. fünf eins
            5.2. fünf zwei
            5.2.1. fünf zwei eins
            5.2.2. fünf zwei zwei
            5.2.2.1. fünf zwei zwei eins
            5.2.2.2. fünf zwei zwei zwei
            5.3. fünf drei
            6. sechs
            7. sieben

            Grüße
            Thomas

            1. Hallo Thomas,
              vielen vielen Dank für die beiden Lösungen!!:o)

              Viele Grüße
              verenice