XPath / XPath-Funktionen / XPath: Sequenz-Funktionen / XPath: distinct-values
![]() |
![]() |
➪ Die XPath-Funktion distinct-values löscht mehrfache identische Einträge in einer Sequenz und reduziert die Ausgabe auf Einzelresultate.
Auf dieser Seite:Siehe auch concat, union, except, intersect.
Wenn es darum geht, mehrfach vorkommende Einzelwerte zu gruppieren, also so zusammenzufassen, dass sie jeweils nur einmal auftreten, steht auch die Funktion distinct-values zur Verfügung.
<xsl:for-each select="distinct-values(//Kauf/bez)">
<Artikel name="{.}"/>
</xsl:for-each>
Als Ergebnis finden Sie:
<Artikel name="Hemd"/>
<Artikel name="Hose"/>
<Artikel name="Schuhe"/>
Freilich geht in diesem Moment der Zugriff auf die Datenquelle verloren:
<xsl:for-each select="distinct-values(//Kauf/bez)">
<xsl:variable name="vartikel" select="."/>
<Artikel name="{$vartikel}">
<xsl:for-each select="//Mensch[Kauf/bez=$vartikel]">
<kunde/>
</xsl:for-each>
</Artikel>
</xsl:for-each>
Diesen Aufruf würde der XSL-Prozessor mit einem netten Gruß aus der Beschwerdeabteilung quittieren.
Fehlerlevel: fatal
XPTY0020: Leading '/'
cannot select the root node
of the tree containing the
context item: the context
item is not a node
URL: http://www.w3.org/TR/xpath20/#ERRXPTY0020
Ein suboptimaler, aber dennoch funktionierender Ausweg wäre, den gesamten XML-Input in eine Variable zu verpacken und die weitere Verarbeitung auf dieser Variablen zu gründen.
<xsl:variable name="vinput" select="."/>
<xsl:for-each select="distinct-values(//Kauf/bez)">
<xsl:variable name="vartikel" select="."/>
<Artikel name="{$vartikel}">
<xsl:for-each
select="$vinput//Mensch[Kauf/bez=$vartikel]">
<kunde nn="{name}"
vn="{vorname}"
wo="{../name}"/>
</xsl:for-each>
</Artikel>
</xsl:for-each>
Sie können distinct-values auch dazu verwenden, nach Elementnamen zu gruppieren. Nehmen Sie dieses XML-Dokument als Input:
<Personen>
<Person>
<Nachname>Sorglos</Nachname>
<Vorname>Siggi</Vorname>
<Geburtsjahr>1981</Geburtsjahr>
</Person>
<Person>
<Nachname>Wunschlos</Nachname>
</Person>
</Personen>
Sie interessieren sich dafür, welche Elementnamen unterhalb Person vorhanden sind?
<Elementnamen>
<eName>Nachname</eName>
<eName>Vorname</eName>
<eName>Geburtsjahr</eName>
</Elementnamen>
In XQuery können Sie so vorgehen:
<Elementnamen>
{
for $x in
distinct-values(/Personen/Person/child::*/name(.))
return
<eName>{$x}</eName>
}
</Elementnamen>
In XSLT 2.0 sieht das ganz ähnlich aus:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"></xsl:output>
<xsl:template match="/">
<Elementnamen>
<xsl:for-each
select="distinct-values(/Personen/Person/child::*/name(.))">
<eName>
<xsl:value-of select="."/>
</eName>
</xsl:for-each>
</Elementnamen>
</xsl:template>
</xsl:stylesheet>
Suboptimal ist die Arbeit mit distinct-values, wenn es um das Entfernen redundanter komplexer Elemente geht. Betrachten Sie dieses XML-Dokument, in dem Elemente mit demselben Eintrag mehrfach enthalten sind.
<Personen>
<Person>
<Nachname>Sorglos</Nachname>
<Vorname>Siggi</Vorname>
<Geburtsjahr>1981</Geburtsjahr>
</Person>
<Person>
<Nachname>Wunschlos</Nachname>
<Vorname>Wilma</Vorname>
<Geburtsjahr>1985</Geburtsjahr>
</Person>
<Person>
<Nachname>Sorglos</Nachname>
<Vorname>Siggi</Vorname>
<Geburtsjahr>1981</Geburtsjahr>
</Person>
<Person>
<Nachname>Wunschlos</Nachname>
<Vorname>Wilma</Vorname>
<Geburtsjahr>1985</Geburtsjahr>
</Person>
<Person>
<Nachname>Sorglos</Nachname>
<Vorname>Siggi</Vorname>
<Geburtsjahr>1981</Geburtsjahr>
</Person>
</Personen>
Ihr Ziel ist es, die doppelten Einträge zu löschen, übrig bleiben sollen:
<Personen>
<Person>
<Nachname>Sorglos</Nachname>
<Vorname>Siggi</Vorname>
<Geburtsjahr>1981</Geburtsjahr>
</Person>
<Person>
<Nachname>Wunschlos</Nachname>
<Vorname>Wilma</Vorname>
<Geburtsjahr>1985</Geburtsjahr>
</Person>
</Personen>
distinct-values bringt hier zwar ein Ergebnis, ...
<Personen>
<xsl:for-each
select="distinct-values(/Personen/Person)">
<Person>
<xsl:value-of select="."/>
</Person>
</xsl:for-each>
</Personen>
... aber kaum das gewünschte:
<Personen>
<Person>
Sorglos
Siggi
1981
</Person>
<Person>
Wunschlos
Wilma
1985
</Person>
</Personen>
Zielführender ist dagegen die Arbeit mit not(. = preceding::Person/.). Siehe hierzu auch Gruppieren in XPath 1.0/XSL 1.0.
<Personen>
<xsl:for-each
select="/Personen/Person[not(. = preceding::Person/.)]">
<xsl:copy-of select="."/>
</xsl:for-each>
</Personen>
Ähnlich elegant sieht das in XQuery aus:
<Personen>
{
for $x in /Personen/Person[not(. = preceding::Person/.)]
return $x
}
</Personen>
wg / 5. April 2019
Fragen? Anmerkungen? Tipps?
Bitte nehmen Sie Kontakt zu mir auf.
V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn
☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de