Mengengleichheit nach Wert ermitteln

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie müssen feststellen, ob die Knoten in einer Knotenmenge (nach Wert) gleich den Knoten in einer anderen Knotenmenge sind (die Reihenfolge wird ignoriert).

Lösung

Dieses Problem ist ein bisschen raffinierter, als es auf den ersten Blick aussieht. Betrachten Sie eine offensichtliche Lösung, die in vielen Fällen funktioniert:

<xsl:template name="vset:equal-text-values">
  <xsl:param name="nodes1" select="/.."/>
  <xsl:param name="nodes2" select="/.."/>
  <xsl:choose>
    <!--Leere Knotenmengen haben gleiche Werte -->
    <xsl:when test="not($nodes1) and not($nodes2)">
      <xsl:value-of select="true( )"/>
    </xsl:when>
    <!--Knotenmengen ungleicher Größen können keine gleichen Werte haben -->
    <xsl:when test="count($nodes1) != count($nodes2)"/>
    <!-- Wenn ein Element von nodes1 in nodes2 vorhanden ist, dann haben die Knotenmengen gleiche Werte, falls die Knotenmengen ohne das gemeinsame Element gleiche Werte haben -->
    <xsl:when test="$nodes1[1] = $nodes2">
      <xsl:call-template name="vset:equal-text-values">
        <xsl:with-param name="nodes1" select="$nodes1[position( ) &gt; 1]"/>
        <xsl:with-param name="nodes2" select="$nodes2[not(. = $nodes1[1])]"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>

Wir haben einen Namen für diesen Gleichheitstest gewählt, um den Kontext zu betonen, in dem er angewandt werden sollte. Dieser Kontext ist gegeben, wenn Wertgleichheit Stringwertgleichheit bedeutet. Dieses Template liefert sicher nicht das richtige Ergebnis, wenn die Gleichheit auf Attributen oder komplexeren Kriterien beruht. Dieses Template hat aber noch ein anderes Problem. Es geht stillschweigend davon aus, dass die verglichenen Knotenmengen in Bezug auf Stringwertgleichheit echte Mengen sind (d. h. keine Duplikate enthalten). Unter manchen Umständen ist das nicht der Fall. Betrachten Sie das folgende XML, das Personen repräsentiert, die Bücher aus einer Bibliothek ausgeliehen haben:

<?xml version="1.0" encoding="UTF-8"?>
<library>
  <book>
    <name>High performance Java programming.</name>
    <borrowers>
      <borrower>James Straub</borower>
    </borrowers>
  </book>
  <book>
    <name>Exceptional C++</name>
    <borrowers>
      <borrower>Steven Levitt</borower>
    </borrowers>
  </book>
  <book>
    <name>Design Patterns</name>
    <borrowers>
      <borrower>Steven Levitt</borower>
      <borrower>James Straub</borower>
      <borrower>Steven Levitt</borower>
    </borrowers>
  </book>
  <book>
    <name>The C++ Programming Language</name>
    <borrowers>
      <borrower>James Straub</borower>
      <borrower>James Straub</borower>
      <borrower>Steven Levitt</borower>
    </borrowers>
  </book>
</library>

Wenn der Name einer Person mehr als einmal auftaucht, dann bedeutet das ganz einfach, dass die Person das Buch mehr als einmal ausgeliehen hat. Falls Sie nun eine Abfrage geschrieben hätten, um alle Bücher zu ermitteln, die von den gleichen Leuten ausgeliehen wurden, dann müssten Sie zustimmen, dass Design Patterns und The C++ Programming Language zwei solcher Bücher sind. Hätten Sie jedoch vset:equal-text-values in der Implementierung dieser Abfrage verwendet, dann hätten Sie dieses Ergebnis nicht erhalten, da in diesem Fall davon ausgegangen worden wäre, dass Mengen keine Duplikate enthalten. Mit folgenden Änderungen können Sie vset:equal-text-values so anpassen, dass es Duplikate toleriert:

<xsl:template name="vset:equal-text-values-ignore-dups">
  <xsl:param name="nodes1" select="/.."/>
  <xsl:param name="nodes2" select="/.."/>
  <xsl:choose>
    <!--Leere Knotenmengen haben gleiche Werte -->
    <xsl:when test="not($nodes1) and not($nodes2)">
      <xsl:value-of select="true( )"/>
    </xsl:when>
    <!-- Wenn ein Element von nodes1 in nodes2 vorhanden ist, dann haben die Knotenmengen gleiche Werte, falls die Knotenmengen ohne das gemeinsame Element gleiche Werte haben -->
    <!--Diese Zeile löschen<xsl:when test="count($nodes1) != count($nodes2)"/> -->
    <xsl:when test="$nodes1[1] = $nodes2">
      <xsl:call-template name="vset:equal-text-values">
        <xsl:with-param name="nodes1"select="$nodes1[not(. = $nodes1[1])]"/>
        <xsl:with-param name="nodes2" select="$nodes2[not(. = $nodes1[1])]"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>

Beachten Sie, dass wir den Test für ungleiche Größen auskommentiert haben, weil dieser Test beim Vorhandensein von Duplikaten nicht gültig ist. Beispielsweise könnte eine Menge drei Vorkommen eines Elements mit dem Stringwert foo haben, während die andere ein einzelnes Element foo enthält. Wenn Duplikate ignoriert werden, sollten diese Mengen gleich sein. Sie müssen außerdem mehr tun, als nur das erste Element im rekursiven Schritt zu entfernen; Sie sollten alle Elemente mit dem gleichen Wert wie das erste Element entfernen, wie Sie es auch bei der zweiten Menge tun. Dies stellt sicher, dass Duplikate bei jedem rekursiven Durchlauf voll berücksichtigt werden. Diese Änderungen sorgen dafür, dass alle Gleichheitstests, die auf dem Textwert beruhen, korrekt ausgeführt werden, allerdings ist bei Mengen, die offensichtlich ungleich sind, zusätzliche Arbeit erforderlich.

Diese Gleichheitstests sind nicht so allgemein wie die Wertmengenoperationen, die im Rezept Mengenoperationen auf Knotenmengen mit Hilfe von Wertsemantiken ausführen erzeugt wurden, weil sie voraussetzen, dass der einzige Begriff von Gleichheit, um den Sie sich kümmern, die Gleichheit von Textwerten ist. Sie können die Tests verallgemeinern, indem Sie die gleiche Technik einsetzen, die Sie für das Testen der Mitgliedschaft auf der Grundlage eines Tests auf Elementgleichheit verwendet haben, der von einem importierenden Stylesheet überschrieben werden kann:

<xsl:template name="vset:equal">
  <xsl:param name="nodes1" select="/.."/>
  <xsl:param name="nodes2" select="/.."/>
  <xsl:if test="count($nodes1) = count($nodes2)">
    <xsl:call-template name="vset:equal-impl">
      <xsl:with-param name="nodes1" select="$nodes1"/>
      <xsl:with-param name="nodes2" select="$nodes2"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>
<!-- Sobald wir wissen, dass die Mengen die gleiche Anzahl an Elementen haben, müssen wir nur noch testen, dass jedes Mitglied der ersten Menge auch ein Mitglied der zweiten ist -->
<xsl:template name="vset:equal-impl">
  <xsl:param name="nodes1" select="/.."/>
  <xsl:param name="nodes2" select="/.."/>
  <xsl:choose>
    <xsl:when test="not($nodes1)">
      <xsl:value-of select="true( )"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="test">
        <xsl:apply-templates select="$nodes2" mode="vset:member-of">
          <xsl:with-param name="elem" select="$nodes1[1]"/>
        </xsl:apply-templates>
      </xsl:variable>
      <xsl:if test="string($test)">
        <xsl:call-template name="vset:equal-impl">
          <xsl:with-param name="nodes1" select="$nodes1[position( ) &gt; 1]"/>
          <xsl:with-param name="nodes2" select="$nodes2"/>
        </xsl:call-template>
      </xsl:if>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Falls Sie verallgemeinerte Gleichheit haben wollen, die auch beim Vorhandensein von Duplikaten funktioniert, müssen Sie einen eher Brute-Force-artigen Ansatz anwenden, der zwei Durchläufe über die Mengen unternimmt:

<xsl:template name="vset:equal-ignore-dups">
   <xsl:param name="nodes1" select="/.."/>
   <xsl:param name="nodes2" select="/.."/>
   <xsl:variable name="mismatch1">
     <xsl:for-each select="$nodes1">
       <xsl:variable name="test-elem">
         <xsl:apply-templates select="$nodes2" mode="vset:member-of">
           <xsl:with-param name="elem" select="."/>
         </xsl:apply-templates>
       </xsl:variable>
       <xsl:if test="not(string($test-elem))">
         <xsl:value-of select=" 'false' "/>
       </xsl:if>
     </xsl:for-each>
   </xsl:variable>
   <xsl:if test="not($mismatch1)">
     <xsl:variable name="mismatch2">
       <xsl:for-each select="$nodes2">
         <xsl:variable name="test-elem">
           <xsl:apply-templates select="$nodes1" mode="vset:member-of">
             <xsl:with-param name="elem" select="."/>
           </xsl:apply-templates>
         </xsl:variable>
         <xsl:if test="not(string($test-elem))">
           <xsl:value-of select=" 'false' "/>
         </xsl:if>
      </xsl:for-each>
    </xsl:variable>
    <xsl:if test="not($mismatch2)">
      <xsl:value-of select="true( )"/>
    </xsl:if>
  </xsl:if>
</xsl:template>

Dieses Template arbeitet, indem es über die erste Menge iteriert und nach Elementen sucht, die kein Mitglied der zweiten Menge sind. Wenn kein solches Element gefunden wird, ist die Variable $mismatch1 null. In diesem Fall muss der Test in der anderen Richtung wiederholt werden, indem über die zweite Menge iteriert wird.

Diskussion

In Abfragen besteht oft die Notwendigkeit, die Mengengleichheit zu testen. Betrachten Sie die folgenden Aufgaben:

  • Suchen aller Bücher, die die gleichen Autoren haben.
  • Suchen aller Lieferanten, die die gleichen Teile vorrätig haben.
  • Suchen aller Familien mit Kindern des gleichen Alters.

Immer wenn Sie es mit Eins-zu-viele-Beziehungen zu tun haben und an Elementen interessiert sind, die die gleiche Menge verknüpfter Elemente aufweisen, müssen Sie die Mengengleichheit testen.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de