Skip to content

XSLT Traps

by Matthias Fraass on Mai 21st, 2008

Mit XSLT ist alles Friede Freude Eierkuchen bis man zu den fiesen 20% kommt die dann 150% der Zeit beanspruchen. Ich hatte beispielsweise die Aufgabe, Einträge einer XML-Datei nach “SampleCount” zu sortieren. Das gestaltete sich schwieriger als es zunächst klingt.

Man beachte daß die Reihenfolge in der Datei wirklich willkürlich war:

<MetaData>
  <QualityReport>
    <QualityEvents>
      <Event num="23" pos="Begin">
        <Mode>M</Mode>
        <Priority>0</Priority>
        <Name>Break</Name>
        <BwfName>BreakOn</BwfName>
        <SampleCount>0341072385</SampleCount>
        <Status>Undefined</Status>
      </Event>
      <Event num="1" pos="Begin">
        <Mode>A</Mode>
        <Priority>0</Priority>
        <Name>SeparationTape</Name>
        <BwfName>SeparationTapeOn</BwfName>
        <SampleCount>0000003072</SampleCount>
        <Status>Undefined</Status>
      </Event>
      <Event num="2" pos="Begin">
        <Mode>A</Mode>
        <Priority>0</Priority>
        <Name>Modulation</Name>
        <BwfName>StartModulation</BwfName>
        <SampleCount>0000920496</SampleCount>
        <Status>Undefined</Status>
        <Comment>Start modulation comment</Comment>
      </Event>
 ...

Soweit so gut:

<xsl:for-each select="//MetaData/QualityReport/QualityEvents/Event">
<xsl:sort select="number(SampleCount)" data-type="number" order="ascending"/>

Eine weitere Anforderung war aber, einen Eintrag aus einem anderen Subbaum zeitlich mit einfließen zu lassen:

<MetaData>
...
    <CueSheet>
        <CuePoint num="1">
            <SampleCount>0017299408</SampleCount>
            <Name>StartProgram</Name>
        </CuePoint>
...

Da, wie man sieht, die Bäume eine unterschiedliche Struktur haben, habe ich ein Merge gar nicht erst versucht. Falls das geht, würde ich mich über einen Hinweis freuen.

Meine erste Taktik war, sich den einzelnen CuePoint zu merken und im Loop nachzuschauen, ob er gerade reinpaßt (Gedächtnisprotokoll, kann sein daß das syntaktisch nicht ganz richtig ist):

<xsl:variable name="cueStartProgram" select="number(MetaData/CueSheet/CuePoint[Name='StartProgram']/SampleCount)"/>
  ...
<xsl:for-each select="//MetaData/QualityReport/QualityEvents/Event">
<xsl:sort select="number(SampleCount)" data-type="number" order="ascending"/>
<xsl:variable name="nextEvent" select="following-sibling::node()"/>
<xsl:if test="number(SampleCount) < $cueStartProgram and $cueStartProgram < number($nextEvent/SampleCount)">
  ... CuePoint einfügen
</xsl:if>

Der CuePoint wurde zwar eingefügt, aber an der falschen Stelle! Ich habe da wirklich lange dran printf-debuggt (wahrscheinlich werden XSLT-Gurus hier lachen) bis mir die Erleuchtung kam, daß sich following-sibling ja auf den in der ursprünglichen XML-Struktur folgenden Sibling bezieht – also auf die unsortierte Version!

Von einer 2-Pass-Transformation hatte ich zwar gehört aber mir gefiel das irgendwie nicht. Also versuchte ich, die sortierte Liste in einer Variablen abzuspeichern:

<xsl:variable name="sortedEvents">
<xsl:for-each select="//MetaData/QualityReport/QualityEvents/Event">
<xsl:sort select="number(SampleCount)" data-type="number" order="ascending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$sortedEvents/Event">
<xsl:variable name="nextEventSampling" select="following-sibling::node()"/>
<xsl:if test="$nextEventSampling">
<xsl:if test="(number($cueStartProgram) <= number($nextEventSampling[1]/SampleCount)) and (number(SampleCount) <= number($cueStartProgram))">
  ... CuePoint einfügen
</xsl:if>

Und hier stieg der bis dato gut funktionierende Xalan aus:

  Can not convert #RTREEFRAG to a NodeList!

Ich lernte dann, daß XSTL 1.0 die Ergebnisse als Reference-Tree an die Variable bindet und nicht als neue NodeList. Die zusätzliche Funktion node-list() , die mittlerweile auch ihren Weg in den fertigen Xalan gefunden hat, soll dieses Manko zwar beseitigen aber es hat bei mir nicht funktioniert. XSLT 2.0 soll das besser machen. Aber anscheinend hat die OpenSource-Entwickler nach XSLT 1.0 die Lust verlassen, denn in allen Libs ist XSLT 2.0 nur teilweise umgesetzt.

Schließlich fand ich aber dennoch die wohl einzige freie und vollständige XSLT 2.0 Lib: Saxon. Es gibt eine kommerzielle Variante die auch Schema unterstützt aber mir genügte die freie.

Und damit hat’ dann auch endlich geklappt wie oben beschrieben. Nicht vergessen oben

<xsl:stylesheet version="2.0"

hinzuschreiben :-) .

TL;DR:
Sollte jemand mal etwas mit XSLT zu tun haben – tut Euch den gefallen und nehmt XSTL 2.0 und Saxon.

From → Coding

No comments yet

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS