Stephan Daratha: preceding-sibling mit Bedingung

Hi

Ich habe ein Problem

Ich habe folgene Struktur:

<prices>
<priceObject date="20070824" weekday="Fri" adults="4" childs="0" minLength="1" status="R">
<price day="1" date="2007-08-25" weekday="Sat">447</price>
<price day="2" date="2007-08-26" regular="447" weekday="Sun">324</price>
<price day="3" date="2007-08-27" regular="447" weekday="Mon">353</price>
<price day="7" date="2007-08-31" weekday="Fri">447</price>
<price day="14" date="2007-09-07" weekday="Fri">863</price>
<price day="21" date="2007-09-14" weekday="Fri">1280</price>
</priceObject>
<priceObject date="20070831" weekday="Fri" adults="4" childs="0" minLength="1">
<price day="1" date="2007-09-01" weekday="Sat">447</price>
<price day="2" date="2007-09-02" regular="447" weekday="Sun">324</price>
<price day="3" date="2007-09-03" regular="447" weekday="Mon">353</price>
<price day="7" date="2007-09-07" weekday="Fri">447</price>
<price day="14" date="2007-09-14" weekday="Fri">863</price>
<price day="21" date="2007-09-21" weekday="Fri">1280</price>
</priceObject>
</prices>

Dort stehen halt zu einem Objekt zu verschiedenen Zeiten verschiede Preis.

Man übergibt halt ein Datum und eine Dauer und er soll die passende Daten raussuchen.

<xsl:for-each select="/prices/priceObject[(($only = 1 and price/@day = $difference) or ($only = '' and price/@day &gt;= $difference)) and @adults = $firstAdult]">
<xsl:sort select="@adults"/>
<xsl:sort select="@date" order="descending"/>
<xsl:if test="(@date &gt;= $date and ($date &gt; preceding-sibling::priceObject[1]/@date) or (position() = count(/prices/priceObject[(($only = 1 and price/@day = $difference) or ($only = '' and price/@day &gt;= $difference)) and @adults = $firstAdult]) and @date &lt; $date))">     xsl:choose
  <xsl:when test="position() = count(/prices/priceObject[(($only = 1 and price/@day = $difference) or ($only = '' and price/@day &gt;= $difference)) and @adults = $firstAdult])">    <xsl:value-of select="@date"/>     </xsl:when>      xsl:otherwise       <xsl:value-of select="preceding-sibling::priceObject[1]/@date"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each>

Das Problem ist das im preceding-sibling::priceObject[1] immer das vorherige drin steht und nicht das vorherige der for-eac schleife.
Gibt es da noch etwas anderes? Oder hat da jemand nen anderen Tip?

Danke

  1. Hallo,

    Das Problem ist das im preceding-sibling::priceObject[1] immer das vorherige drin steht und nicht das vorherige der for-eac schleife.

    Die kurze, cheatende Antwort wäre: »Du missverstehst (wieder) die for-each-Anweisung. Denke nicht iterativ und benutzte das select-Attribut zum Filtern. Wenn nötig auch mehrmals verschachtelt.«

    Das wäre aber unhöflich und nicht hilfreich, also hier mal in länger:

    Aus Deinem Quelltext-Klumpen ist es etwas schwierig zu entnehmen, was Du eigentlich willst, deswegen habe ich mir diese Theorie zum Erklären zurecht gelegt:

    • Du hast einen Haufen von Angeboten (<priceObject>) für Ferienwohnungen oder sonstige Objekte, jedes zu bestimmten Bedingungen (Erwachsene, Kinder ...) und zu einem bestimmten Zeitraum. In diesem Zeitraum hast Du verschiedene Kalkulationen (<price>) für potentielle Buchungen.

    • Ein interessierter urlauben Wollender hat nun seine Bedingungen und sein Wunsch-Datum, gibt diese irgendwo ein, stößt damit Dein XSLT an und dieses soll ihm dann passende Angebote und deren Kalkulationen zurück geben.

    • Verkompliziert wird dieses durch die UI-Option, dass der Fragende nach einer Mindest-Buchdauer fragen kann oder nach einer Buchdauer, die genau auf seine gewünschte Urlaubsspanne passt. So interpretiere ich zumindest Deine ominöse Variable $only.

    Dein Problem scheint nun zu sein, dass Du Dir xsl:for-each wie eine iterative Schleife vorstellst. Du startest Deine gedachte Schleife, gehst die Elemente nach für nach durch und wendest einen Filter auf jedes Element an. Und dann willst Du noch auf vorherige Schleifeniterationen zugreifen, so deute ich zumindest Dein anscheinenden Wunsch des vermeintlichen Nutzen von preceding-sibling. Und damit machst Du Dir das Leben schwer.

    Denn XSLT funktioniert so nicht. xsl:for-each ist keine iterative Schleife, bei den es Schleifenschritte und ein Davor vor dem aktuellen Schleifenschritt oder ein Danach gibt. Die Reihenfolge der Ausführung von for-each kann man nicht beeinflussen, es ist wurst, ob das gleichzeitig oder nacheinander oder ganz durcheinander geschieht. Warum das nicht ganz so abstrus ist, wie das erstmal klingt, darüber gab es vor einiger Zeit mal einen Thread.

    Sämtliche Angebote nach den passenden zu filtern, wie Du es willst, macht man dann in XSLT nicht innerhalb von for-each, wenn man nicht muss. Idealerweise filtert man schon vorher, direkt im select-Attribut des for-each-Elementes. So muss man sich nur noch um die genau passende reduzierte Sequenz von Knoten kümmern. Und das hast Du schon ganz gut geschafft. Meine zum eigenen Verständnis neu erstellte Variante sieht so aus:

    ~~~xml <xsl:for-each select="/prices/priceObject[(@adults = $firstAdult) and
                                                (@date = $date) and
                                                (if ($only)
                                                 then
                                                     (price/@day = $dauer)
                                                 else
                                                     (price/@day >= $dauer)
                                                )
                                               ]">

      
    Neu formatiert, damit man übersichtlicher und einfacher neue Klauseln dran hängen kann, die bei Dir ja noch kommen, wie z.B. (@childs = $childs) oder (@status != "reserviert"). Dieser Select macht ja schon fast alles, was man braucht. Es wählt nur priceObject-Elemente zum passenden Datum und mit der passenden Anzahl an Erwachsenen aus. Und je nach Wert von $only wählt es auch nur priceObject-Elemente aus, die Angebote größer der gewünschten Dauer oder genau gleich der gewünschten Dauer haben, wenn $only wahr ist.  
      
    In Bezug auf die Angebote ist hier schon alles Filtern komplett erledigt. Die Angebote muss man nur noch formatieren. Wenn man dann aber pro Angebot die Kalkulationen (die price-Elemente) formatieren will, muss man diese noch mal Filtern. Denn: Bislang sind nur die Angebote gefiltert. Dadurch sind in manchen Angebots-Elementen aber noch überflüssige price-Elemente übrig geblieben, die nicht passen, denn priceObject-Elemente werden immer mit kompletten Kind-Elementen übergeben.  
      
    Anstatt da großartig mit xsl:if und xsl:choose rumzuspielen, filtert man diese einfach nochmal, mit for:each am besten:  
      
      ~~~xml
    <xsl:for-each select="if ($only)  
                            then  
                                price[@day = $dauer]  
                            else  
                                [@day >= $dauer]">
    

    Hier werden die price-Elemente einfach wieder in Abhängigkeit von $only gefiltert. Konsequenterweise kommen in der inneren Sequenz nur die jeweils passenden Elemente an. Ganz zusammen gepackt, sieht das bei mir so aus:

    ~~~xml <xsl:variable name="firstAdult" select="4"/>
      <xsl:variable name="date" select="20070831"/>
      <xsl:variable name="dauer" select="7"/>
      <xsl:variable name="only" select="true()"/>

    <xsl:for-each select="/prices/priceObject[(@adults = $firstAdult) and
                                                (@date = $date) and
                                                (if ($only)
                                                 then
                                                     (price/@day = $dauer)
                                                 else
                                                     (price/@day >= $dauer)
                                                )]">
        <h1>
          xsl:textDas Angebot X vom </xsl:text>
          <xsl:value-of select="@date"/>
          xsl:text bis zum ...</xsl:text>
        </h1>

    <ul>
        <xsl:for-each select="if ($only)
                              then
                                  price[@day = $dauer]
                              else
                                  price[@day >= $dauer]">
          <li>
            <xsl:value-of select="@date"/>
            xsl:text (</xsl:text>
            <xsl:value-of select="@day"/>
            xsl:text Tage) kostet </xsl:text>
            <xsl:value-of select="."/>
            xsl:text Euro.</xsl:text>
          </li>
        </xsl:for-each>
        </ul>

    </xsl:for-each>

      
    Angewandt auf Dein Beispiel erhalte ich dieses Ergebnis:  
      
      ~~~html
    <h1>Das Angebot X vom 20070831 bis zum ...</h1>  
      <ul>  
        <li>2007-09-07 (7 Tage) kostet 447 Euro.</li>  
      </ul>
    

    Setze ich $only auf false() dieses:

    ~~~html <h1>Angebot X vom 20070831 bis zum ...</h1>
      <ul>
        <li>2007-09-07 (7 Tage) kostet 447 Euro.</li>
        <li>2007-09-14 (14 Tage) kostet 863 Euro.</li>
        <li>2007-09-21 (21 Tage) kostet 1280 Euro.</li>
      </ul>

      
    Und ich glaube, so ungefähr, nur mit netterer Formatierung, willst Du das doch auch, oder?  
      
    Tim