XPath: Vorheriges Geschwisterelment auswählen

level6

Grünschnabel
Hallo,

ich bräuchte mal Hilfe bei einem XPath Statement:

Ich bekomme aus einem CMS eine XML-Struktur, die mit XSLT in einen HTML-Seite
umgewandelt wird. Dabei muss ich an einer Stelle wissen, ob das aktuelle Element ein
vorheriges Geschwisterelement hat, und in diesem Geschwisterelement ein Unterelement leer ist, oder nicht.
Hier das Beispiel konkreter:

XML:
Code:
<elements>
   <Paragraph>
      <Headline>Headline P1</Headline>
      <Longtext>Longtext 1</Longtext>
   </Paragraph>
   <Paragraph>
      <Headline>Headline P2</Headline>
      <Longtext></Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML List ohne Longtext im P darueber</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
   <Paragraph>
      <Headline>Headline p3</Headline>
      <Longtext>Longtext</Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML List mit Longtext im P darueber</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>3 Liste </Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
</elements>
Beim Transformieren muss ich wissen ob vor einem HTMLList Element ein Paragraph Element steht,
welches eine leeres Longtext Element hat. In einem solchen Fall möchte ich in ein anderes "match"-Template für die HTMLListe "verzweigen"
(über mode="..."), um die andere Visualität der HTHMListe zu realisieren.

Mein XSLT schaut grob so aus:

Code:
   <xsl:template match="/">
      <xsl:apply-templates select="elements" />
   </xsl:template>

   <xsl:template match="elements">
      <xsl:apply-templates select="Paragraph" />
      <xsl:choose>
            <xsl:when test=" ? ">
                  <xsl:apply-templates select="HTMLList" mode="1"/>
              </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="HTMLList" mode="2"/>
            </xsl:otherwise>
      </xsl:choose>
   </xsl:template>

   <xsl:template match="Paragraph"> 
       Paragraph
   </xsl:template>

   <xsl:template match="HTMLList" mode="1">
       HTML List - vorher kein Longtext
       </xsl:template>
   <xsl:template match="HTMLList" mode="2">
   HTML List Standard
   </xsl:template>

</xsl:stylesheet>
Ich denke so kann ich das machen, nur ist mein Problem das ich Schwierigkeiten bei
der richtigen Formulierung des XPath Statements habe, der dort stehen muss, wo die ? stehen.

Ich habe schon folgendes probiert:
Code:
<xsl:when test="string-length(//HTMLList/preceding-sibling::Paragraph/Longtext) &lt; 1">
Eine andere Idee, die ich hatte: Man könnte auch in XPath ein passendes "match" aufzubauen,
der mir ein passendes Node-set zurück liefert. Aber auch da komme ich nicht weiter. Ich habe zu viele Knoten, oder gar keine.

Ist es überhaupt möglich von einem Element auf das vorherige Element zu schauen und dort ein Unterelement zu prüfen, wie in diesem Fall?

Danke im Vorraus für Eure Hilfe.

Gruss Level6
 
Hi.

Bei deinem XPath Ausdruck suchst du ja immer alle HTMLList Elemente aus dem gesamten Dokument heraus. Versuch's mal so:
Code:
HTMLList/preceding-sibling::Paragraph[Longtext = '']

Evtl. hab ich dich auch etwas missverstanden...

Gruß
 
Hallo,

danke Dir, aber das war es nicht.
Ich bekomme nichts zurück. Wenn ich vorne jedoch mit // arbeite, also:

Code:
//HTMLList/preceding-sibling::Paragraph[Longtext = '']

...bekomme ich alle Paragraph Elemente zurück, die vor einer HTMLList sind
und die einen leeren Longtext habe. Jedoch brauch ich ja nur den Vorgänger von
dem aktuellen HTMLList-Element/Knoten. So wie ich Deinen Code und meinen verstehe
bekommt man aber Paragraphen Listelement zurück. Besser wäre es doch, wenn
man ein HTMLList-Element zurück bekommt, die man dann in einem Template verarbeitet.

Gruss
 
Hi.
danke Dir, aber das war es nicht.
Ich bekomme nichts zurück.
Das kann nicht sein. Bei mir funktioniert es wie erwartet.
Für das folgende XML Dokument
XML:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xml" href="els.xsl"?>

<root>

<elements>
   <Paragraph>
      <Headline>Headline P1</Headline>
      <Longtext>Longtext 1</Longtext>
   </Paragraph>
   <Paragraph>
      <Headline>Headline p3</Headline>
      <Longtext>Longtext</Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML List mit Longtext im P darueber</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>3 Liste </Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
</elements>

<elements>
   <Paragraph>
      <Headline>Headline P1</Headline>
      <Longtext>Longtext 1</Longtext>
   </Paragraph>
   <Paragraph>
      <Headline>Headline P2</Headline>
      <Longtext></Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML List ohne Longtext im P darueber</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
   <Paragraph>
      <Headline>Headline p3</Headline>
      <Longtext>Longtext</Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML List mit Longtext im P darueber</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>3 Liste </Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
</elements>

<elements>
   <Paragraph>
      <Headline>Headline P1</Headline>
      <Longtext>Longtext 1</Longtext>
   </Paragraph>
   <Paragraph>
      <Headline>Headline P2</Headline>
      <Longtext></Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML List ohne Longtext im P darueber</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
   <Paragraph>
      <Headline>Headline p3</Headline>
      <Longtext>Longtext</Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML List mit Longtext im P darueber</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>3 Liste </Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 1</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
   </HTMLList>
</elements>
</root>
und deinem Stylesheet mit dem XPath Ausdruck anstelle des ? erhalte ich die folgende Ausgabe:
Code:
       Paragraph
    
       Paragraph
   
   HTML List Standard
   
   HTML List Standard
    
       Paragraph
    
       Paragraph
    
       Paragraph
   
       HTML List - vorher kein Longtext
       
       HTML List - vorher kein Longtext
       
       HTML List - vorher kein Longtext
        
       Paragraph
    
       Paragraph
    
       Paragraph
   
       HTML List - vorher kein Longtext
       
       HTML List - vorher kein Longtext
       
       HTML List - vorher kein Longtext

Wenn ich vorne jedoch mit // arbeite, also:

Code:
//HTMLList/preceding-sibling::Paragraph[Longtext = '']

...bekomme ich alle Paragraph Elemente zurück, die vor einer HTMLList sind
und die einen leeren Longtext habe. Jedoch brauch ich ja nur den Vorgänger von
dem aktuellen HTMLList-Element/Knoten.
Du hast doch gar kein aktuelles HTMLList-Element. In dem Template bist du doch an einem elements Knoten, was soll denn jetzt das aktuelle HTMLList-Element sein?

Evlt. möchtest du ja in einem for-each die HTMLList-Elemente des aktuellen element-Knotens verarbeiten? Und dann für jedes einzelne dieser Elemente das jeweilige Template aufrufen? Kann es sein, dass du evlt. nur den Longtext von dem Paragraph-Element unmittelbar vor einem HTMLList Element prüfen möchtest? Zeig am besten mal was deiner Meinung nach bei dem XML Beispiel herauskommen soll.
So wie ich Deinen Code und meinen verstehe bekommt man aber Paragraphen Listelement zurück. Besser wäre es doch, wenn man ein HTMLList-Element zurück bekommt, die man dann in einem Template verarbeitet.
Das wäre dann ein anderer Ansatz.
 
Zuletzt bearbeitet von einem Moderator:
Hallo Deepthroat,

erstmal Entschuldigung, da ich so lange nichts von mir hören lassen habe.
War leider krank und jetzt muss ich erstmal in meinem Projekt wichtigere
Fehler bearbeiten, da ist dieses Problem nicht ganz so wichtig ist.

Und auch Danke Dir, das Du immer so schnell geantwortet hast.
Und ja, ich möchte:
nur den Longtext von dem Paragraph-Element unmittelbar vor einem HTMLList Element prüfen

Wenn Du noch eine Idee hast.

Danke und Gruss aus HH von
level6
 
Hi.

Mit diesem Stylesheet:
XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:output method='text' />
  
  <xsl:template match="/">
	<xsl:apply-templates select="//elements" />
  </xsl:template>

  <xsl:template match="elements">
ELEMENT
	<xsl:apply-templates select="Paragraph" />
	<xsl:apply-templates select="HTMLList" />
   </xsl:template>

   <xsl:template match="Paragraph"> 
       Paragraph
   </xsl:template>

   <xsl:template match="HTMLList[preceding-sibling::Paragraph[1][Longtext = '']]">
   HTML List without Longtext
   </xsl:template>

   <xsl:template match="HTMLList[preceding-sibling::Paragraph[1][Longtext != '']]">
   HTML List Standard with Longtext
   </xsl:template>

   <xsl:template match="text()" />
</xsl:stylesheet>
Erhalte ich folgende Ausgabe:
Code:
ELEMENT
	 
       Paragraph
    
       Paragraph
   
ELEMENT
	 
       Paragraph
    
       Paragraph
    
       Paragraph
   
   HTML List without Longtext
   
   HTML List Standard with Longtext
   
   HTML List Standard with Longtext
   
ELEMENT
	 
       Paragraph
    
       Paragraph
    
       Paragraph
   
   HTML List without Longtext
   
   HTML List Standard with Longtext
   
   HTML List Standard with Longtext
Vermutlich (du hast kein Beispiel gemacht) möchtest du die Reihenfolge der Elemente nicht verändern, dann müßtest du einfach nur apply-templates aufrufen.

Gruß
 
Zuletzt bearbeitet von einem Moderator:
Hallo,

so jetzt bin ich wieder an diesem Problem dran.

Deinen letzten Vorschlag verstehe ich besser, jedoch gibt es da noch
ein Problem. Ich brauch nur eine Verzweigung beim Element "HTMLList", wenn das vorherige Element ein Paragraph ist und dann dort in diesem Paragraph der Longtext leer ist. Jedoch ist beim folgenden XML Ausschnitt:
Code:
<Paragraph>
      <Headline>Headline p3</Headline>

      <Longtext>Longtext</Longtext>

   </Paragraph>

   <HTMLList>

      <HTMLListItem EnumLevel="1">

         <Longtext>HTML List mit Longtext im P darueber</Longtext>

      </HTMLListItem>

      <HTMLListItem EnumLevel="1">

         <Longtext>Punkt 1</Longtext>

      </HTMLListItem>

      <HTMLListItem EnumLevel="1">

         <Longtext>Punkt 2</Longtext>

      </HTMLListItem>

   </HTMLList>

   <HTMLList>

      <HTMLListItem EnumLevel="1">

         <Longtext>3 Liste </Longtext>

      </HTMLListItem>

      <HTMLListItem EnumLevel="1">

         <Longtext>Punkt 1</Longtext>

      </HTMLListItem>

      <HTMLListItem EnumLevel="1">

         <Longtext>Punkt 2</Longtext>

      </HTMLListItem>
...der match:
Code:
<xsl:template match="HTMLList[preceding-sibling::Paragraph[1][Longtext = '']]">
auch beim 2. HTMLListe Element erfolgreich, da ja "preceding-sibling::paragraph[1]" doch das erste vorherigen Geschwister vom Typ Paragraph auswählt. Dies ist aber das gleiche Element wie der match bei der 1. HTML Liste.

Aber vielleicht habe ich mich auch nicht richtig ausgedrückt. Soll ich es nochmal versuchen aufzuschreiben?

Ach ja und die Reihenfolge soll nicht geändert werden. Das XML sind Daten aus einen CMS welche in HTML dargestellt werden soll.

Danke und Gruss level6
 
Hi.
Aber vielleicht habe ich mich auch nicht richtig ausgedrückt. Soll ich es nochmal versuchen aufzuschreiben?
Warum machst du nicht einfach mal ein Beispiel was dann zum Schluss herauskommen sollte?

Probier's mal so:
XML:
<xsl:template match="elements">
ELEMENT
	  <xsl:apply-templates />
</xsl:template>

   <xsl:template match="HTMLList[preceding-sibling::*[1][name() = 'Paragraph' and Longtext = '']]">
   HTML List without Longtext
   </xsl:template>

   <xsl:template match="HTMLList[preceding-sibling::*[1][name() = 'Paragraph' and Longtext != '']]">
   HTML List Standard with Longtext
   </xsl:template>
Gruß
 
Zuletzt bearbeitet von einem Moderator:
Hallo,

inzwischen habe ich es hinbekommen. Ist Deiner Lösung ähnlich.
Aber für die Nachwelt versuche ich das Problem verständlicher zu beschreiben.
Die XML Daten sehen so aus:

Code:
<elements>
   <Paragraph>
      <Headline>Headline P1</Headline>
      <Longtext>Longtext P1</Longtext>
   </Paragraph>
   <Paragraph>
      <Headline>Headline P2</Headline>
      <Longtext></Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML Liste ohne Longtext im Paragraph davor</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 3</Longtext>
      </HTMLListItem>
   </HTMLList>
    <Paragraph>
      <Headline>Headline P3</Headline>
      <Longtext></Longtext>
   </Paragraph>
   <Paragraph>
      <Headline>Headline P4</Headline>
      <Longtext>Longtext P4</Longtext>
   </Paragraph>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML Liste mit Longtext im Paragraph davor</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 3</Longtext>
      </HTMLListItem>
   </HTMLList>
   <HTMLList>
      <HTMLListItem EnumLevel="1">
         <Longtext>HTML Liste kein Paragraph davor</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 2</Longtext>
      </HTMLListItem>
      <HTMLListItem EnumLevel="1">
         <Longtext>Punkt 3</Longtext>
      </HTMLListItem>
   </HTMLList>
</elements>
Und es soll dann folgendes HTML nach der Transformation herrauskommen:

Code:
<h1>Headline P1</h1>
<p>Longtext P1</p>

<h1>Headline P2</h1>

<ul class="noLongtextInPOver">
  <li>HTML Liste ohne Longtext im Paragraph davor</li>
  <li>Punkt 2</li>
  <li>Punkt 3</li>
</ul> 

<h1>Headline P3<h1>

<h1>Headline P4</h1>
<p>Longtext P4</p>

<ul>
  <li>HTML Liste mit Longtext im Paragraph davor</li>
  <li>Punkt 2</li>
  <li>Punkt 3</li>
</ul>

<ul>
  <li>HTML Liste aber kein Paragraph davor</li>
  <li>Punkt 2</li>
  <li>Punkt 3</li>
</ul>
Wie man im HTML sehen kann, muss die Liste die hinter einem Paragraphen kommt eine extra CSS-Klasse haben, wenn in diesem Paragraphen der Longtext leer ist. Diesen speziellen Fall musste ich finden.

Wie man auch erkennt, soll die Reihenfolge der Element nicht verändert werden.

Mein konkretes XML ist sehr viel komplexer, aber die Lösung habe ich wie folgt gefunden. Mit:

Code:
<xsl:apply-templates select="Paragraph | SelectList | Separator | HTMLList | HTMLList[name(preceding-sibling::*[1]) = 'Paragraph'][preceding-sibling::Paragraph[1][Longtext = '']] | Table01 | Boxes | List | LinkList | LinkListParagraph"/>
...werden die jeweiligen Templates aufgerufen ohne die Reihenfolge zu verändern. Das speziele Template in der in dem <ul> Tag die extra CSS-Klasse gesetzt wird, wird mit:

Code:
HTMLList[name(preceding-sibling::*[1]) = 'Paragraph'][preceding-sibling::Paragraph[1][Longtext = '']]
...gefunden.

Es gibt dann natürlich noch die folgende Templatedefinition:

Code:
<xsl:template match="HTMLList[name(preceding-sibling::*[1]) = 'Paragraph'][preceding-sibling::Paragraph[1][Longtext = '']]">
....
</xsl:template>
So kann man es lösen. Oder doch Fehler drin?
Bei mir funktioniert das jetzt so klasse.

Ein Grossen Dank an "deepthroat" (ungewöhnlich Namenswahl :) )
 
Zurück