XSLT v1.0-ban kellett egy XML file-ból pofás kis HTML riportot generálnom. Mivel ez volt az első ütközésem az XSLT-vel, némileg bele kellett jönnöm. A belejövés folyamatát idedokumentáltam egy kicsit.
Egy kis bevezetés: w3schools.com. Sajnos ez a legnagyobb trükkről egy szót sem ejt, azaz, hogy az XSLT-ben egy definiált változót nem tudunk később megváltoztatni. Már a feltételtől függő értékadás is érdekes.
Referencia doksik:
- XSLT 1.0 referencia: w3.org xslt v1.0
- XSLT 2.0 referencia: XSLT v2.0, hogy egy kis kitekintésünk is legyen.
- És egy jó kis szakácskönyv: java2s.com. Nekem sok ötletet adott.
Az xslt-vel xml doksikból XHTML doksikat gyártani. Illetve egy pici trükközéssel bármilyen más formátumú file-t is. Elég egyszerű a nyelv (lásd csatolt linket), de a logikája nagyon nem nem triviális.
A nyelv elemei nagyon vázlatosan:
- <xsl:template match="...">: Ezek egy adott al-csomópontot keresnek meg és a hozzá tartozó al-fát (mégsem írhattam, hogy alfát) dolgozzák fel. Direketben is meghívható az <xsl:apply-templates select="..."> hivatkozással. A select-ben több template-re is hivatkozhatunk "tag1|tag2|tag2". Ekkor azt a template-et fogja hívni, amilyen tag-et talál az adott xml al-fában.
- <xsl:value-of select="...">: Egy adott csomópont értékét veszi ki. Hasonló az <xsl:copy-of select="...">, de ekkor a HTML formázó karaktereket is kiírja.
- <xsl:for-each select="...">: az adott csomópont alatti ismétlődő csomópontokon megy végig. A csomópont alatti elemeket a már jól ösmert <xsl:value-of>-fal lehet kiszedni. Ezen belül az <xsl:sort select="...">-tal lehet valamelyik csomópont tartalma alapján sorrendbe tenni.
- <xsl:if test="...">: egy feltétel teljesülését ellenőrizhetjük. Van '<', '>' stb., de a leírásból nem derült ki számomra, hogyan lehet megnézni, hogy egy adott csomópont definiálva van-e.
<xsl:if test="not(title)"> - a title node nincs definiálva
<xsl:if test="title"> - a title node definiálva van
<xsl:if test="title=''"> - title node nincs definiálva, VAGY a title node definiálva van és üres (<title></title>, vagy <title/>). A végén két aposztróf van és egy macskaköröm.
<xsl:if test="title!=''"> - a title node definiálva van és nem üres (<title>akármi</title>) - <xsl:choose>, <xsl:when test="expression">, <xsl:otherwise>: Megnézni, hogy egy érték egyenlő-e valamilyen értékekkel (l. switch-case-...-default)
- <xsl:apply-templates select="...">: a template-tel definiált template-eket meghívja. Ha abban is van apply-remplates, akkor lehet rekurzívan is hívni a template-eket. Paramétereket is átadhatunk (call-template, with-param).
- <xsl:variable name="..." select="..."/>, vagy <xsl:variable name="...">...</xsl:variable> - változó definiálsása. Ha egy változót definiáltunk, akkor soha többé nem változtathatjuk meg az értékét (a miértekről >itt<). Ne próbáljunk if-ben definiálni változót, mert a if-fen kívül nem lesz használható. Ha azt szeretnénk, hogy az if-fen kívül is használhassuk, akkor így kell tennünk:
<xsl:variable name="bgcolor">
Az sajna nem megoldás, akár mennyire is tetszetősnek tűnik, hogy a choose-on belül definiálunk egy változót, mert a scope-ja csak a choose-n belül lesz értelmes. Sőt az sem jó, ha a <tr>-t így, vagy úgy írjuk ki a choos-on belül. Valami miatt ha egy blokk nem tartalmazza a lezáró tag-et is, akkor szintaktikai hibát ad. Szóval ez sem jó:
<xsl:choose>
<xsl:when test="valamilyen_tag">blue</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr bgcolor="{$bgcolor}">
<xsl:choose>
<xsl:when test="profile"><tr bgcolor="blue"></xsl:when>
<xsl:otherwise><tr></xsl:otherwise>
<xsl:choose>
További referencia az elemekről és a függvényekről. Persze elég sokféle matematikai művelet végzésére is rávehetjük.
Nézzünk egy kicsit komplexebb példákat:
- Adva van egy XML ami kb. így néz ki:
...
No és ez sokszor ismétlődik. A lasttag értékei lehetnek azonosak, de lehetnek eltérőek is. Egy lasttag érték szerepelhet több basetag-ban is. A name-ek minden <basetag>-ban egyediek. Hogyan tudunk olyan táblázatot csinálni, ami úgy néz ki, hogy "lasttag | melyik basetag(ok)-ban van használva". Én úgy csináltam, hogy először egy uniq listán megyek végig, ami a <lasttag>-okat tartalmazza (abc sorrdendben), majd megnézem, hogy mely basetag-oknak van az adott lasttag-gal egyező eleme. Kb. így:
<basetag>
<name>ez_a_neve</name>
...
<lasttag>akarmi</lasttag>
...
<lasttag>akarmi</lasttag>
</basetag>
<basetag>
...
<!-- valahol az elején kell definiálni -->
Hát nem szép? Hát nem... de működik! A nagy trükk a "descendant::" használata volt. Bár valszeg a legbelső if-ben nem kell a count, de így mégis csak beszédesebb.
<xsl:key name="lasttags" match="basetag//*/lasttag" use="."/>
...
<table>
<xsl:for-each select="basetag//*/lasttag[generate-id() = generate-id(key('lasttags', .)[1])]"> <!-- uniq -->
<xsl:sort select="."/> <!-- sort -->
<xsl:variable name="lasttag" select="."/>
<tr>
<td><xsl:value-of select="$lasttag"/></td>
<td>
<xsl:for-each select="//basetag">
<xsl:variable name="basetag" select="name"/>
<xsl:if test="count(descendant::*[lasttag=$lasttag])>0">
<xsl:value-of select="$basetag"/><br/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
Persze lehetne a uniq kulcs ciklust egyszerűbben is létrehozni a distinct-values() függvénnyel, ami XSLT 2.0-ában defriniálva vagyon. Csak az msxml nem nagyon ösmeri. MSXML 3 által támogatott függvények listáját >itt< tekinthetjük meg. - Szeretnénk regular expressiont használni. Sajnos az msxml nem támogatja az <xsl:if test="matches(...)"> függvényt (pedig még a fejlécben is próbáltam a <xsl:stylesheet version="2.0"-t nem jött be.), de pl. JavaScript kódot lehet hívni. Kicsit trükkös, de végül sikerült zöld ágra vergődnöm vele. A megfelelő eredményhez a fejlécben is hozzá kell nyúlni:
<xsl:stylesheet version="1.0"
Ez után már meg is írhatjuk a függvényünket:
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:JS="urn:my-scripts">
<msxsl:script language="JavaScript" implements-prefix="JS">
Ezt már csak meg kellene tudni hívni a xslt kódból. Első próba nem sikerült:
<![CDATA[
function xxx(value) {
val = "x_" + typeof(value) + "_x";
return val;
}
]]>
</msxsl:script><xsl:value-of select="JS:xxx(name)"/>
Érdekes, hogy ha a return-ban visszaadjuk a value-t változtatás nélkül, akkor az átadott értéket kapjuk. A return "xxx" is jó. De a return "xxx" + value már nem működik, mert a value mindig üres string lesz. Ha megnézzük, hogy mi a typeof(value), akkor egy az "object" string-et kapjuk. Tehát valami konverziós problema van. Próbálkoztam trükközni a val = new String()-gel, de nem sikerült. A megoldás az, hogy a hívásnál kell konvertálni!<xsl:value-of select="JS:xxx(string(name))"/>
Ekkor a typeof(value) már "string" lesz! Egy számokat tartalmazó változó értéke "number" lesz. - Megpróbáltam a JS kóddal visszaadni egy teljesen beformázott HTML sort is, de csak a string jelenik meg én nem szúrja be a HTML tag-eket, inkább a '<' és '>'-t lecseréli < és >-re. Próbálkoztam value-of-fal és copy-of-fal is.
Megpróbáltam a value-of végére hozzáírni a disable-output-escaping="yes"-t (<xsl:value-of select="JS:..." disable-output-escaping="yes" />), És így már ment is!
Amit nem sikerült megoldanom:
- Megpróbáltam egy select="JS:xxx(//tag_name)" hívást is. A //tag_name kifejezés egy tag listát ad vissza. JS egy objektum tömböt lát, amelynek minden tagja objektum típusú. De hogy hogyan lehet ebből kinyerni, hogy mi a tartalma az egyes objektumoknak, arra nem jöttem rá. No és hogyan lehet egy ilyen listát visszaadni? Mondjuk ha szeretnénk egy for-each-ban használni...
Formázódjunk minden nap!
+jegyzések