Janine S.: Variablen und Schleifen - Kopfknotenalarm!

Hallo allerseits,

ich kämpfe seit gestern mit einem dicken Kopfknoten und hoffe, dass ihr gute Lösungen oder Ideen habt! Ich habe folgende Ausgangssituation:

<list>
	<old>
		<id>1</id>
		<id>2</id>
		<id>3</id>
		<id>4</id>
	</old>
	<new>
		<id>2</id>
		<id>3</id>
		<id>4</id>
		<id>5</id>
	</new>
</list>

Was ich nun haben will: Eine Liste, die alle Elemente aus "old" und "new" vereint (also sowohl 1 und 5) als auch doppelte Einträge filtert (also nicht 2-4 doppelt):

<list>
	<id>1</id>
	<id>2</id>
	<id>3</id>
	<id>4</id>
	<id>5</id>
</list>

Das funktioniert mit folgendem Code schon ganz gut:

<xsl:template>
	<list>
		<xsl:for-each select="list/old/id">
			<id>
				<xsl:value-of select="id"/>
			</id>
		</xsl:for-each>
		<xsl:for-each select="list/new/id">
			<id>
				<xsl:value-of select="id"/>
			</id>
		</xsl:for-each>
	</list>
</xsl:template>

Und in einem zweiten Schritt die Liste (1, 2, 2, 3, 3, 4, 4, 5) filtern mit: <xsl:for-each select="id[not(.=following::id)]"> Geht das auch in einem Schritt? Z.B. über Variablen? Da bin ich aber gänzlich ratlos, wie man sowas anstellen könnte…

Schonmal besten Dank für die Hinweise!

LG Janine

akzeptierte Antworten

  1. Hallo Janine,

    Was ich nun haben will: Eine Liste, die alle Elemente aus "old" und "new" vereint (also sowohl 1 und 5) als auch doppelte Einträge filtert (also nicht 2-4 doppelt):

    <list>
    	<id>1</id>
    	<id>2</id>
    	<id>3</id>
    	<id>4</id>
    	<id>5</id>
    </list>
    

    Dieser XSLT-2.0-Ansatz führt zum Ziel:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
      <xsl:template match="list">
        <list>
          <xsl:for-each select="distinct-values(old/id | new/id)">
            <id>
              <xsl:value-of select="."/>
            </id>
          </xsl:for-each>
        </list>
      </xsl:template>
    </xsl:stylesheet>
    
    

    Grüße,
    Thomas

    1. Hallo Thomas,

      tausend Dank, deine Transformation funktioniert wunderbar. Habe nun auch den Unterschied zwischen dem Union Operator | und or kennengelernt.

      Ich habe mit fortschreitender Generierung nun noch ein neues Problem bekommen. Ausgangssituation ist:

      <list>
      	<gebiete>
      		<gebiet>010</gebiet>
      		<gebiet>020</gebiet>
      		<gebiet>030</gebiet>
      		<gebiet>040</gebiet>
      	</gebiete>
      	<allids>
      		<id>010001</id>
      		<id>010002</id>
      		<id>010003</id>
      		<id>020001</id>
      		<id>999001</id>
      	</allids>
      </list>
      

      Meine IDs bestehen also immer aus der dreistelligen Nummer der Gebiete und einer fortlaufenden Zahl. Außer im Fall "999001". Wie kann ich nun eine Abfrage schreiben, die zunächst prüft, ob es IDs gibt, die nicht mit einer Gebiets-Nummer beginnen und diese anschließend ausgibt? Ausgabe also: <list> <id>999001</id> </list>

      Schonmal danke fürs Nachdenken. Janine

      1. Hallo Janine,

        Meine IDs bestehen also immer aus der dreistelligen Nummer der Gebiete und einer fortlaufenden Zahl. Außer im Fall "999001". Wie kann ich nun eine Abfrage schreiben, die zunächst prüft, ob es IDs gibt, die nicht mit einer Gebiets-Nummer beginnen und diese anschließend ausgibt? Ausgabe also: <list> <id>999001</id> </list>

        Das lässt sich so lösen:

        <?xml version="1.0" encoding="UTF-8"?>
        <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
          <xsl:template match="list">
            <xsl:variable name="geb" select="gebiete/gebiet"/>
            <list>
              <xsl:for-each select="allids/id">
                <xsl:variable name="aktid" select="."/>
                <xsl:if test="not(index-of($geb, substring($aktid, 1, 3)) ge 1)">
                  <id>
                    <xsl:value-of select="$aktid"/>
                  </id>
                </xsl:if>
              </xsl:for-each>
            </list>
          </xsl:template>
        </xsl:stylesheet>
        
        

        Zur Erklärung: In $geb werden die Gebiete als Sequenz ('010', '020', '030', '040') eingelesen. Im for-each-Konstrukt erfolgt durch substring() der Abgleich mit den ersten drei Zeichen in den IDs. index-of() bestimmt die Position in der Sequenz (läuft ab 1). Gesucht sind also Ergebnisse, die nicht >= 1 sind (ge steht für greater or equal, hier könnte aber auch >= direkt stehen).

        Grüße,
        Thomas

        1. Guten Morgen Thomas,

          besten Dank für die schnelle Antwort. index-of() kannte ich bisher noch nicht, doch in diesem Beispiel wird die Anwendung sehr anschaulich. Ich habe mit deinem Template noch ein wenig rumprobiert, um es vollständig zu verstehen. Der XPath bietet viele Möglichkeiten - danke!

          Viele Grüße und bis zum nächsten Knoten ;)

          Janine

          1. Hallo Thomas (und alle anderen),

            da bin ich noch einmal mit einem letzten Knoten (danach reichts aber auch!):

            Ausgangs-XML ist wieder:

            <list>
            	<gebiete>
            		<gebiet>010</gebiet>
            		<gebiet>020</gebiet>
            		<gebiet>030</gebiet>
            		<gebiet>040</gebiet>
            	</gebiete>
            	<allids>
            		<id>010001</id>
            		<id>010002</id>
            		<id>010003</id>
            		<id>020001</id>
            		<id>020002</id>
            	</allids>
            </list>
            

            Doch diesmal will ich die Gebiete ausgeben, die auch zugehörige IDs besitzen (also nur 010 und 020).

            Mein Code sieht bisher so aus:

            <?xml version="1.0" encoding="UTF-8"?>
            <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
            	<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
            	<xsl:template match="list">
            		<xsl:for-each select="gebiete/gebiet">
            			<xsl:variable name="geb" select="."/>
            			<xsl:if test="substring(allids/id, 1, 3) = $geb">
            				<p>
            					<xsl:value-of select="$geb"/>
            				</p>
            			</xsl:if>
            		</xsl:for-each>
            	</xsl:template>
            </xsl:stylesheet>
            

            Doch es wird keine Ausgabe erzeugt. Die Fehlermeldung meint, dass "A sequence of more than one item is not allowed as the first argument of fn:substring() ("000010", "000020", ...) " Muss ich da mit verschachtelten Schleifen arbeiten oder auch hier einfach alle allids/id in eine Variable schreiben, um sie als Sequenz einzulesen?

            Viele Grüße

            Janine

            PS: fn:starts-with(allids/id, $geb) funktioniert leider genausowenig wie die Definition der <xsl:variable name="ids" select"allids/id"/>...

            1. Hallo Janine,

              Doch es wird keine Ausgabe erzeugt. Die Fehlermeldung meint, dass "A sequence of more than one item is not allowed as the first argument of fn:substring() ("000010", "000020", ...) " Muss ich da mit verschachtelten Schleifen arbeiten oder auch hier einfach alle allids/id in eine Variable schreiben, um sie als Sequenz einzulesen?

              Das klappt mit etwas Umschreibung der vorherigen Transformation:

              <?xml version="1.0" encoding="UTF-8"?>
              <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
                <xsl:template match="list">
                  <xsl:variable name="ids"
                                select="distinct-values(for $id in allids/id return substring($id, 1, 3))"/>
                  <list>
                    <xsl:for-each select="gebiete/gebiet">
                      <xsl:variable name="aktgeb" select="."/>
                      <xsl:if test="index-of($ids, $aktgeb) ge 1">
                        <gebiet>
                          <xsl:value-of select="$aktgeb"/>
                        </gebiet>
                      </xsl:if>
                    </xsl:for-each>
                  </list>
                </xsl:template>
              </xsl:stylesheet>
              
              

              Ergebnis:

              <?xml version="1.0" encoding="UTF-8"?>
              <list>
                <gebiet>010</gebiet>
                <gebiet>020</gebiet>
              </list>
              

              Die Belegung der Variable $ids ist hier etwas spezieller. Das for-in-return-Konstrukt liefert zunächst von allen IDs die ersten drei Zeichen und distinct-values() beschränkt das Ergebnis auf die individuellen Werte, also: ('010', '020'). Die weitere Verarbeitung läuft dann wie gehabt, nur ohne not().

              Grüße,
              Thomas

              1. Hallo Thomas,

                besten Dank. Ich konnte deinen Code endlich testen und auf meinen Anwendungsfall anpassen. Du hast mir sehr weitergeholfen!

                Viele Grüße Janine