Reputation: 3880
i have to build an XSLT stylesheet that transforms an xml like this:
<message>
<line/>
<silence/>
<dot/><line/><line/>
<silence/>
<dot/>
<silence/>
<line/><dot/><dot/><dot/>
</message>
into something like this:
<complexMessage>
<word code="-"/>
<word code=".--"/>
<word code="."/>
<word code="-..."/>
</complexMessage>
(notice how each word
element gets closed after a silence
element)
how could I do that?
Upvotes: 3
Views: 332
Reputation: 66714
I believe that this achieves what you are looking for:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="firstSilenceID" select="generate-id(//silence[1])" />
<xsl:template match="/">
<complexMessage>
<xsl:apply-templates select="/message//silence"/>
</complexMessage>
</xsl:template>
<xsl:template name="message">
</xsl:template>
<xsl:template match="silence" >
<!--If this is the first <silince> element, generate words for everything before it -->
<xsl:if test="$firstSilenceID = generate-id(current())">
<xsl:call-template name="complexMessage">
<xsl:with-param name="word" select="preceding-sibling::*"/>
</xsl:call-template>
</xsl:if>
<!--Generate words for everything after THIS <silence> element -->
<xsl:call-template name="complexMessage">
<xsl:with-param name="word" select="following-sibling::*[generate-id(preceding-sibling::silence[1]) = generate-id(current())]"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="complexMessage">
<xsl:param name="word"/>
<word>
<xsl:attribute name="code">
<xsl:apply-templates select="$word" />
</xsl:attribute>
</word>
</xsl:template>
<xsl:template match="dot">
<xsl:text>.</xsl:text>
</xsl:template>
<xsl:template match="line">
<xsl:text>-</xsl:text>
</xsl:template>
</xsl:stylesheet>
Upvotes: 2
Reputation: 243459
This solution is both: slightly shorter and, more importantly, more efficient due to the use of keys:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kPhraseByMorse"
match="*[not(self::silence) and not(self::message)]"
use="generate-id(preceding-sibling::silence[1])"/>
<xsl:template match="/">
<complexMessage>
<word>
<xsl:call-template name="makeCode"/>
</word>
<xsl:apply-templates select="*/silence"/>
</complexMessage>
</xsl:template>
<xsl:template match="silence">
<word>
<xsl:call-template name="makeCode">
<xsl:with-param name="pId" select="generate-id()"/>
</xsl:call-template>
</word>
</xsl:template>
<xsl:template name="makeCode">
<xsl:param name="pId"/>
<xsl:attribute name="code">
<xsl:apply-templates select="key('kPhraseByMorse', $pId)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="dot">.</xsl:template>
<xsl:template match="line">-</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided source XML, the correct result is produced:
<complexMessage>
<word code="-"/>
<word code=".--"/>
<word code="."/>
<word code="-..."/>
</complexMessage>
Upvotes: 3
Reputation: 1062502
How about:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/message">
<complexMessage>
<xsl:apply-templates select="*[1]"/>
</complexMessage>
</xsl:template>
<xsl:template match="line" mode="value">-</xsl:template>
<xsl:template match="dot" mode="value">.</xsl:template>
<xsl:template match="silence" mode="value"/>
<xsl:template match="silence" name="write">
<xsl:param name="prev" select="''"/>
<word code="{$prev}"/>
<xsl:apply-templates select="following-sibling::*[1]"/>
</xsl:template>
<xsl:template match="line | dot">
<xsl:param name="prev" select="''"/>
<xsl:variable name="value"><xsl:apply-templates select="." mode="value"/></xsl:variable>
<xsl:choose>
<xsl:when test="following-sibling::*">
<xsl:apply-templates select="following-sibling::*[1]">
<xsl:with-param name="prev" select="concat($prev,$value)"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="write">
<xsl:with-param name="prev" select="concat($prev,$value)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
For large documents it might trigger infinite recursion detection - in which case I'd try to look at something involving <xsl:apply-templates select="*[1] | silence"/>
, and in each case only go forward as far as the next silence or EOF (if you see what I mean). Let me know if you want a refactored version to show this...
Upvotes: 1