JanineS: xsl | Elemente filtern und zusammenführen

Hallo zusammen,

ich habe ein XSL-Problem und bereits vieles probiert, komme aber nicht auf die Lösung. Dennoch habe ich das Gefühl, dass es einfach zu lösen sein muss. Frustrierend -.- Vielleicht könnt ihr mir ja helfen...

Ich habe folgende Quelldatei:

<roles>
   <role>
      <name>A</name>
      <part>1</part>
   </role>
   <role>
      <name>A</name>
      <part>2</part>
   </role>
   <role>
      <name>B</name>
      <part>1</part>
   </role>
</roles>

Und möchte in der Ausgabe die <name> filtern, sodass jeder Name nur 1x vorkommt, aber dafür alle <part> zusammenführen. Also in Textform so etwas: A: 1, 2 B: 1

Ich habe es mit preceding::sibling probiert, mit Variablen. Konnte keine Arrays ins XSL finden und bin nun überfragt. Habt ihr Ideen, die mich zu einer Lösung bringen könnten?

Besten Dank schonmal, Janine

akzeptierte Antworten

  1. Hallo Janine,

    Und möchte in der Ausgabe die <name> filtern, sodass jeder Name nur 1x vorkommt, aber dafür alle <part> zusammenführen. Also in Textform so etwas: A: 1, 2 B: 1

    Probiere es mit diesem XSLT-2.0-Ansatz:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:fn="http://www.w3.org/2005/xpath-functions"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="#all">
    
      <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
      <xsl:template match="roles">
    
        <xsl:variable name="names" select="fn:distinct-values(role/name)" as="xs:string*"/>
        <xsl:variable name="roles" select="role" as="node()*"/>
    
        <roles>
          <xsl:for-each select="$names">
            <xsl:variable name="name" select="."/>
            <role>
              <name>
                <xsl:value-of select="$name"/>
              </name>
              <part>
                <xsl:value-of select="fn:concat($name, ': ')"/>
                <xsl:for-each select="$roles/name[. = $name]">
                  <xsl:value-of select="following-sibling::part"/>
                  <xsl:if test="fn:position() != fn:last()">
                    <xsl:text>, </xsl:text>
                  </xsl:if>
                </xsl:for-each>
              </part>
            </role>
          </xsl:for-each>
        </roles>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Ergebnis:

    <?xml version="1.0" encoding="UTF-8"?>
    <roles>
      <role>
        <name>A</name>
        <part>A: 1, 2</part>
      </role>
      <role>
        <name>B</name>
        <part>B: 1</part>
      </role>
    </roles>
    

    Grüße,
    Thomas

    1. Hallo Thomas,

      besten Dank für deine schnelle Hilfe! Ich kam bisher mit den doppelten Schleifen nicht klar. Dank deiner Lösung konnte ich meinen ursprünglichen Code noch etwas umschreiben und bin nun zu der Lösung noch gekommen:

      <?xml version="1.0" encoding="UTF-8"?>
      <xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:fn="http://www.w3.org/2005/xpath-functions"
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
      
        <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
      
        <xsl:template match="roles">
      
          <roles>
            <xsl:for-each select="//role[not(text()=preceding-sibling::role/text())]">
              <xsl:variable name="name" select="name"/>
              <xsl:variable name="part" select="//role[name=$name]/part"/>
              <role>
                <name>
                  <xsl:value-of select="$name"/>
                </name>
                <part>
                  <xsl:value-of select="$part"/>
                </part>
              </role>
            </xsl:for-each>
          </roles>
        </xsl:template>
      
      </xsl:stylesheet>
      

      Ausgabe ist dann:

      <?xml version="1.0" encoding="UTF-8"?>
      <roles xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      	<role>
      		<name>A</name>
      		<part>1 2</part>
      	</role>
      	<role>
      		<name>A</name>
      		<part>1 2</part>
      	</role>
      	<role>
      		<name>B</name>
      		<part>1</part>
      	</role>
      </roles>
      

      Viele Grüße JanineS

      1. Hallo Janine,

        Ausgabe ist dann:

        <?xml version="1.0" encoding="UTF-8"?>
        <roles xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema">
        	<role>
        		<name>A</name>
        		<part>1 2</part>
        	</role>
        	<role>
        		<name>A</name>
        		<part>1 2</part>
        	</role>
        	<role>
        		<name>B</name>
        		<part>1</part>
        	</role>
        </roles>
        

        Sieht für mich aber nicht ganz wie in der Frage aus: "sodass jeder Name nur 1x vorkommt". Aber ok, wenn so gewollt.

        Hinweis: Wenn kein Zugriff auf die Namensräume fn bzw. xs erfolgt, können diese auch aus dem Stylesheet entfernt werden oder eben wie gezeigt mit exclude-result-prefixes="fn xs" | alle ="#all" von der Ausgabe ausschließen.

        Grüße,
        Thomas

        1. Hallo Thomas,

          du hast Recht, da habe ich den Wald vor lauter Bäumen übersehen. Meine ursprüngliche Datei ist wesentlich komplexer. Danke für den Hinweis. Da hat in der xsl:for-each nur ein name-Element gefehlt:

          <xsl:for-each select="//role[not(name/text()=preceding-sibling::role/name/text())]">
          

          LG JanineS