# XSL rekursiv



## guni (22. Sep 2009)

Hallo,

habe eine XML, die wie folgt aussieht:
[XML]<toc>
  <marker title="Ueberschrift1">
    <marker title="UnterpunktA" />
    <marker title="UnterpunktB" />
      <marker title="Anmerkung I" />
   </marker>
  </marker>
  <marker title="Ueberschrift2">
    <marker title="UnterpunktX">
      <marker title="AnmerkungY">
    </marker>
  </marker>
</toc>[/XML]
mit andern Worten ein Inhaltsverzeichnis, das beliebig viele Nodes in beliebiger Tiefe haben kann.
wie muss ich nun mein XSL schreiben um die marker zu durchlaufen?

lg, guni


----------



## SlaterB (22. Sep 2009)

na korrekt, wie sonst,

das klingt jetzt so allgemein wie 'in Java habe ich ein Baum, wie muss mein Programm aussehen um den Baum zu durchlaufen',
was soll man darauf antworten, ein 500 Zeilen Programm?

xsl:for-each select="//marker"

oder ein Template für marker-Nodes


----------



## guni (22. Sep 2009)

ok - ich war wohl zu ungenau.

ich will das ganze in eine xul-fragment umwandeln; d.h. aus
[XML]<toc>
  <marker title="Ueberschrift1">
    <marker title="UnterpunktA" />
    <marker title="UnterpunktB" />
      <marker title="Anmerkung I" />
   </marker>
  </marker>
  <marker title="Ueberschrift2">
    <marker title="UnterpunktX">
      <marker title="AnmerkungY">
    </marker>
  </marker>
</toc>[/XML]
wird
[XML]
<tree>
  <treechildren>
    <treeitem>
      <treerow>
        <treecell label="Ueberschrift1">
      </treerow>
      <treechildren>
        <treeitem>
          <treerow>
            <treecell label="UnterpunktA">
          </treerow>
        </treeitem>
        <treeitem>
          <treerow>
            <treecell label="UnterpunktB">
          </treerow>
          <treechildren>
            <treeitem>
              <treerow>
                <treecell label="AnmerkungI">
              </treerow>
            </treeitem>
          </treechildren>
        </treeitem>
      </treechildren>
    </treeitem>
    <treeitem>
      <treerow>
        <treecell label="Ueberschrift2">
      </treerow>
      <treechildren>
        <treeitem>
          <treerow>
            <treecell label="UnterpunktX">
          </treerow>
          <treechildren>
            <treeitem>
              <treerow>
                <treecell label="AnmerkungY">
              </treerow>
            </treeitem>
          </treechildren>
        </treeitem>
      </treechildren>
    </treeitem>
  </treechildren>
</tree>
[/XML]
habe im Netz einige Templates für Rekursionen gefunden;
aber ich versteh nicht, was ich als Parameter übergeben muss,
wie ich den "Function-Body" von meinem Template bau und wo ich es dann wie im "Hauptteil" meines XSL's aufrufe ...

lg, guni


----------



## SlaterB (22. Sep 2009)

<xsl:template match="marker">
anfang
<xsl:apply-templates select="marker">
ende
</xsl:template>


----------



## guni (22. Sep 2009)

bis jetzt hab ich folgenden - absolut primitiven - Ansatz:
[XML]
	<xsl:template name="generatetree">
		<xslaram name="parent" select="./marker"/>
		<xsl:choose>
			<xsl:when test="$parent">
				<treeitem>
					<treerow>
						<treecell>
							<xsl:attribute name="label"><xsl:value-of select="@title"/></xsl:attribute>
						</treecell>
					</treerow>
				</treeitem>
			</xsl:when>
			<xsltherwise>
				<xsl:call-template name="generatetree">
					<xsl:with-param name="parent" select="./marker"/>
				</xsl:call-template>
			</xsltherwise>
		</xsl:choose>
	</xsl:template>
[/XML]
mein Problem: wie setze ich die Abbruchbedingung?
was übergebe ich an die Funktion, wenn sie sich selbst wieder aufruft?
und wie lege ich fest, dass bis zum tiefsten marker ein treeitem mit treechildren erzeugt werden muss?!

danke für eure Unterstützung.
lg, guni


----------



## SlaterB (22. Sep 2009)

vielleicht wirklich erstmal in Java an einem Baum versuchen, könnte leichter sein da Fehler durch falsche XSL-Kommandos in Java bei Java-Befehlen wohl weniger auftreten

die Abbruchbedingung ist, ob eine Unterelement marker vorhanden ist, das wird auch an die Funktion (das Template) übergeben,


in deinem Code muss das otherwise leer sein, wenn kein marker da ist, wieso der rekursive Aufruf?
der gehört dagegen zwischen Zeile 10 und 11, nachdem das eigene </treerow> fertig ist, kommen die Kinder ran,

es können mehrere sein dafür braucht es xsl:for-each, oder apply-templates wenn du mit match-Templates arbeitest

der  <treechildren>-Tag fehlt bisher überall, aber der ist Kosmetik



siehe auch mein Post von 13:56


----------



## guni (22. Sep 2009)

ok ... was haltet ihr von diesem Ansatz:

[XML]
	<xsl:template name="generatetree">
		<xslaram name="current" select="marker"/> <!-- Übergabe: die aktuelle Node -->

		<treeitem>
			<treerow>
				<treecell>
					<xsl:attribute name="label"><xsl:value-of select="@title"/></xsl:attribute>
				</treecell>
				<!-- wenn child-elemente existieren -->
				<treechildren>
					<xsl:for-each select="marker">
						<xsl:call-template name="generatetree">
							<xsl:with-param name="current" select="."/>
						</xsl:call-template>
					</xsl:for-each>
				</treechildren>
				<!-- ende: wenn child elemente existieren -->
			</treerow>
		</treeitem>
	</xsl:template>
[/XML]

die Frage ist: wie muss ich mein if schreiben?
muss ich es überhaupt schreiben?
zuerst hab ich mir gedacht, dass die Schleife doch eigentlich reichen würde, aber ich will die treechildren-tags ja nur dann setzen, wenn es auch treechildren gibt! da kann ich das if dann nicht verhindern, oder?!

mfg, guni


----------



## SlaterB (22. Sep 2009)

wir, also ich denke, dass du dafür ein xsl:if brauchst, ja


----------



## guni (24. Sep 2009)

ok. habe meine Funktion jetzt hinbekommen.
Für alle die's interessiert: sie sieht so aus:
[XML]
<xsl:template name="generatetree">
		<xslaram name="current" select="marker"/>
		<treeitem>
			<treerow>
				<treecell>
					<xsl:attribute name="label"><xsl:value-of select="@title"/></xsl:attribute>
					<xsl:attribute name="id">treecell<xsl:value-of select="@id"/></xsl:attribute>
				</treecell>
				<xsl:if test="child::marker">
					<treechildren>
						<xsl:for-each select="marker">
							<xsl:call-template name="generatetree">
								<xsl:with-param name="current" select="."/>
							</xsl:call-template>
						</xsl:for-each>
					</treechildren>
				</xsl:if>
			</treerow>
		</treeitem>
	</xsl:template>
[/XML]


----------



## Oding99 (24. Nov 2010)

Hi zusammen,
ich weiß, der Thread ist uralt, aber ich steh im Moment vor nem ähnlichen Problem:
Ich will ne rekursive HTML-Ausgabe via XSL programmieren. Und zwar soll jeweils ne Tabelle mit 2 Zellen angelegt werden, wobei in die 2 Zelle wieder ne Tabelle mit gleichem Aufbau mit dem Inhalt des nächsten Knotens (sind ursprünglich alle in gleicher Tiefe) erzeugt werden soll, und darin wieder in der 2. Zelle der Inhalt des nächsten Knotens...
So lange, bis alle Knoten durch sind.
Aber ich hab keine Ahnung, wie ich das hinbekommen soll.
Ich bin mir darüber im Klaren, dass Tabelle in Tabelle in Tabelle nie eine perfekte Lösung ist, aber ich kann meiner Meinung nach folgende Augabe (hauptsächlich wegen den Borders) nicht erzeugen:
Imageshack - tabellenaufbau.jpg


----------



## SlaterB (24. Nov 2010)

fange mit einfacher Textausgabe unteinander oder mit der ersten Tabellenstufe an, ruhig ohne die Unterknoten rekursiv zu betrachten,
wenn man alles auf einmal lösen will ist es kompliziert, einzelne Schritte vielleicht nicht so, es sei denn du hast komplett keine Ahnung von XSL

----

wenn der erste Anfang geschafft ist, poste bisschen davon, auch die genaue Struktur der Nodes anhand eines Beispiels, am besten XML


----------



## Oding99 (24. Nov 2010)

Danke zunächst für die Antwort.
Also komplett keine Ahnung hab ich nicht, bin aber auch kein Profi.
Ich will eigentlich ein benanntes Template in sich selber aufrufen, so lange, bis alle Knoten abgearbeitet sind.

Hier mal ein Ausschnitt der XML:
[XML]<Sektor>
<Merkmal>
<Bezeichnung>Baugröße</Bezeichnung>
<Beispielwert>25</Beispielwert>
</Merkmal>
</Sektor>
<Sektor>
<Merkmal>
<Bezeichnung>Hub [mm]</Bezeichnung>
<Beispielwert>500</Beispielwert>
</Merkmal>
</Sektor>
	<Sektor>
		<Merkmal>
			<Bezeichnung>Antriebsfunktion</Bezeichnung>
			<Beispielwert>SP</Beispielwert>
			<Wert>
				<Beschreibung>Spindel</Beschreibung>
				<Kürzel>SP</Kürzel>
			</Wert>
		</Merkmal>
		<Merkmal>
			<Bezeichnung>Zubehör</Bezeichnung>
			<Beispielwert>+ZUB</Beispielwert>
			<Wert>
				<Beschreibung>Zubehör lose beigelegt</Beschreibung>
				<Kürzel>ZUB</Kürzel>
			</Wert>
		</Merkmal>
	</Sektor>
<Sektor>
[/XML]


----------



## Oding99 (24. Nov 2010)

Und ein Ausschnitt aus der XSL:
[XML]	<xsl:template name="generatetable">
		<xslaram name="current" select="Sektor"/>
		<xsl:for-each select="//Sektor">
			<xsl:choose>
		<xsl:when test="self::node[position() != last()]">
			<table rules="all" frame="box">
				<tr>
					<xsl:for-each select="./Merkmal">
					<td/>
					<td>
						<xsl:value-of select="./Beispielwert"/>
					</td>
					</xsl:for-each>							
					<td>
						<xsl:call-template name="generatetable">
						<xsl:with-param name="current" select="."/>
						</xsl:call-template>
					</td>
				</tr>
			</table>
		</xsl:when>
				<xsltherwise>
				</xsltherwise>
			</xsl:choose>
		</xsl:for-each>
	</xsl:template>
[/XML]


----------



## SlaterB (24. Nov 2010)

das ist ja schon ne Menge, was konkret funktioniert davon nicht? 
was ist z.B. die Ausgabe in HTML für die obige Eingabe oder welche Fehlermeldung kommt?


grob angesehen scheint mir die Rekursion nicht gut, 
von Aufruf A übergibst du das aktuelle Element nochmal,
der folgende Aufruf B unterscheidet sich dann vom vorherigen A überhaupt nicht

> for-each select="//Sektor"
ist auch schlecht, damit fragst du global alle ab, nicht die aktuellen Elemente

aus dem Stand im Editor geändert versuche es mal mit
[XML]
    <xsl:template name="generatetable">
        <xsl:if test="self::node[position() != last()]">
            <table rules="all" frame="box">
                <tr>
                    <xsl:for-each select="./Merkmal">
                      <td/>
                      <td>
                        <xsl:value-of select="./Beispielwert"/>
                      </td>
                    </xsl:for-each>
                    <td>
						<xsl:for-each select="./Select">
							<xsl:call-template name="generatetable"/>
                        </xsl:for-each>
                    </td>

                </tr>
            </table>        
       </xsl:if>
    </xsl:template>
[/XML]

mit Zeile 12-14 auch initial aufrufen, funktioniert nur noch wenn das aktuelle .-Element ein Select ist


----------



## Odin99 (24. Nov 2010)

Danke fürs Ändern oben 
Für die obige Eingabe gibt er schlichtweg nichts aus. Bzw. Das ganze steckt widerum in ner Tabelle und er zeigt eine leere Zelle an. Fehlermeldung gibt es keine.
Das Gleiche zeigt er mir, wenn ich stattdessen Deinen Code verwende (wie folgt dann):
[xml]
	<xslutput method="html"/>
	<xsl:template match="/">
		<html>
			<head/>
			<body>
				<table rules="all" frame="box">
					<tr>
						<td>
							<xsl:call-template name="generatetable"/>
						</td>
					</tr>
				</table>
			</body>
		</html>
	</xsl:template>

	<!--rekursiv-->
	<xsl:template name="generatetable">
		<xsl:if test="self::node[position() != last()]">
			<table rules="all" frame="box">
				<tr>
					<xsl:for-each select="./Merkmal">
						<td/>
						<td>
							<xsl:value-of select="./Beispielwert"/>
						</td>
					</xsl:for-each>
					<td>
						<xsl:for-each select="./Sektor">
							<xsl:call-template name="generatetable">
							</xsl:call-template>
						</xsl:for-each>	
					</td>
				</tr>
			</table>
		</xsl:if>
	</xsl:template>
</xsl:stylesheet>
[/xml]

Wobei ich Dein "Select" noch in "Sektor" geändert habe (hoffentlich korrekterweise).


----------



## SlaterB (24. Nov 2010)

wie oben geschrieben bzw. editiert muss der initiale Aufruf auch

<xsl:for-each select="./Sektor">
    <xsl:call-template name="generatetable"/>
</xsl:for-each>

lauten, denn es wird nicht mehr erst in generatetable nach Sektoren gesucht sondern davon aus gegangen dass ein Sektor aktuell gewählt ist,
wenn du von / aus generatetable aufrufst, dann passiert in der Tat nicht viel, zum einen weil das if vielleicht schon Ärger macht (gibt nur ein /),
zum anderen weil kein ./Merkmal oder ähnliches gefunden wird 

-----

wie gesagt einfach anfangen:
das allererste muss der Aufruf für ein oder alle Sektoren sein und das Template dazu brauch nicht mehr als "TEST" ausgeben,
wenn das klappt (3x TEST erscheint für die drei Hauptsektoren), dann kann man anfang das if einzubauen -> nur noch 2x TEST
und dann den rekursiven Aufruf -> paar mehr TEST,
und dann so langsam etwas andere Ausgabe als TEST, z.B. jeweils den Merkmal-Text hinschreiben,
ganz am Ende komplizierte Tabellenstrukturen


----------



## Oding99 (24. Nov 2010)

Ah, ok, dass der initiale Aufruf auch im for-each geschehen muss ist ne neue Erkenntnis für mich, leuchtet aber ein.
Was mir weiterhin nicht einleuchtet ist, dass Du folgendes schreibst:
[xml]
                        <xsl:for-each select="./Select">
                            <xsl:call-template name="generatetable"/>
                        </xsl:for-each>
[/xml]

Da dürfte er nichts finden, weil es keine Sektoren innerhalb von Sektoren gibt, die liegen ja alle in der gleichen Tiefe. Er geht ja, wie Du sagst, davon aus, dass er bereits einen Sektor gewählt hat.
Desweiteren findet er normalerweise schon auch Merkmale, weil er ja über eine größere XML (bzw. eigentlich Dita)-Datei läuft. Nur eben nicht immer.
Ich denke ich werde aber auch einfach mal so vorgehen, wie Du vorschlägst.


----------



## SlaterB (24. Nov 2010)

dass die Sektoren verschachtelt sind ist überhaupt erst die Grundlage für Rekursion bzw. für dieses Thema ursprünglich
(2009, ich behaupte mal ich erinnere mich noch  ),
sehe jetzt dass es in deinem Beispiel XML nicht so ist, bisschen war mir das schon aufgefallen, durch die Einrückung aber blenden lassen,
dein ursprünglicher Text ist auch eindeutlich formuliert jetzt nochmal gelesen, 
tja dumm gelaufen,

also nochmal von vorne:
wenn du gleiche Elemente der Reihe nach durchlaufen willst, dann bietet sich eine Schleifenverarbeitung an,
die gibts aber nicht in XSL und deshalb landet man durchaus auch wieder bei Rekursion

habe ich selber noch nicht benutzt, deswegen versuche ich gar nicht erst XSL zu posten
google-Suche dazu ist z.B. 'xsl recursive loop'

Tip: Loop with recursion in XSLT
liefert ein Beispiel-Template mit einen Index-Parameter

dazu muss man noch das i-te Sektor-Element auswählen,
hier
How to make a for loop in xslt (not for-each)? (XSLT, XML)
mit dann
<xsl:for-each select="//Sektor[position() = index]"> 
könnte hinkommen


----------



## Odin99 (25. Nov 2010)

Prinzipiell geht das ja mit for-each. Das Problem liegt eher darin, dass er bei der Ausgabe sozusagen einen Eintrag in der vorigen Schleife machen muss, sprich die neue Tabelle in die zuvor erstellte Tabelle einfügen.
Und da liegt der Haken.
An für sich ist ne Schleifenverarbeitung mit for-each einwandfrei umsetzbar.

Mein Ansatz hierfür war ja das Template in sich selbst zu referenzieren:
[xml]
<template name="Beispiel">
      <table>
      <call-template name="Beispiel"/>
      </table>
</template>
[/xml]

So auf die Art. Ich denke auch, dass dies der richtige Ansatz ist, die Umsetzung hapert aber einfach noch. 
Die Sache mit dem Index ("dazu muss man noch das i-te Sektor-Element auswählen") hatte ich ja folgendermaßen eingebaut:
[xml]<xsl:when test="self::node[position() != last()]">[/xml]
was je ein korrekter xpath Ausdruck ist. Also: Wenn der Sektor nicht der letzte ist mach weiter, andernfalls...


----------



## SlaterB (25. Nov 2010)

ok, kein loop ist falsch, eher 'keine for-Schleife mit Index', darum geht es in den beiden Links,
du brauchst bei jedem Aufruf des Templates genau einen Index, erst 1, dann 2, 3, 4 (oder bei 0 beginnned),
mit dem Test auf last() ist es nicht getan, die anderen müssen auch irgendwann drankommen und zwar immer nur eins pro Template-Aufruf und in Reihenfolge,

wie es geht habe ich schon erklärt, neues gibts aktuell nichts zu sagen


----------



## Odin99 (25. Nov 2010)

Hey, danke Dir recht herzlich für die links. Sind beide wirklich super aufschlussreich. Werde es mit der Variante dann mal versuchen und ggf. bei nem funktionierenden Ergebnis den Code posten.
Und danke dafür, dass Du Dich meiner angenommen hast


----------



## Odin99 (25. Nov 2010)

So, mit Hilfe Deiner links hat es letztendlich geklappt:

[xml]
<xsl:template name="generatetable">
	<xslaram name="count" select="count(//Sektor)"/>
	<xsl:if test="$count > 0">
		<table rules="all" frame="box">
			<tr>
				<td>
					<xsl:call-template name="generatetable">
						<xsl:with-param name="count" select="$count - 1"/>
					</xsl:call-template>
				</td>
			</tr>
		</table>
	</xsl:if>

	</xsl:template>
[/xml]

Bin doch auch vorher ganz schön knapp dran gewesen.
Vielen lieben Dank nochmals für alles.


----------

