Reputation: 5735
Could anyone please help me with the following transformation?
Here is the input xml:
<?xml version="1.0" encoding="UTF-8"?>
<book>
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author>
<name>Smith</name>
</author>
<author>
<name>Wallace</name>
</author>
<author>
<name>Brown</name>
</author>
</book>
<book>
<title>Other book</title>
<pages>100</pages>
<size>small</size>
<author>King</author>
</book>
<book>
<title>Pretty book</title>
<pages>150</pages>
<size>medium</size>
</book>
This is the desired output
<book style="even">
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author-name>Smith</author-name>
</book>
<book style="odd">
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author-name>Wallace</author-name>
</book>
<book style="even">
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author-name>Brown</author-name>
</book>
<book style="odd" >
<title>Other book</title>
<pages>100</pages>
<size>small</size>
<author-name>King</author-name>
</book>
<book style="even">
<title>Pretty book</title>
<pages>150</pages>
<size>medium</size>
<author-name />
</book>
I tried using xsl:for-each
loops, but I suppose they brought me to a dead end. The tricky part here is the "style" attribute that somehow needs to be "global" no matter how many author tags are placed in any book.
Upvotes: 4
Views: 2691
Reputation: 243449
This simple, pure XSLT 1.0 transformation (no conditionals, no xsl:for-each
, no parameter passing, no xsl:element
, no use of the infamously inefficient //
):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:names>
<n>odd</n>
<n>even</n>
</my:names>
<xsl:variable name="vStyles"
select="document('')/*/my:names/*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="book/author">
<xsl:variable name="vPos">
<xsl:number level="any" count="book/author|book[not(author)]"/>
</xsl:variable>
<book style="{$vStyles[$vPos mod 2 +1]}">
<xsl:copy-of select="@*|../node()[not(self::author)]"/>
<author-name>
<xsl:value-of select="normalize-space()"/>
</author-name>
</book>
</xsl:template>
<xsl:template match="book[author]">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="book[not(author)]">
<xsl:variable name="vPos">
<xsl:number level="any" count="book/author|book[not(author)]"/>
</xsl:variable>
<book style="{$vStyles[$vPos mod 2 +1]}">
<xsl:copy-of select="@*|node()"/>
<author-name/>
</book>
</xsl:template>
<xsl:template match="book[author]/*[not(self::author)]"/>
</xsl:stylesheet>
when applied to this XML document (the provided one wrapped into a single top element):
<t>
<book>
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author>
<name>Smith</name>
</author>
<author>
<name>Wallace</name>
</author>
<author>
<name>Brown</name>
</author>
</book>
<book>
<title>Other book</title>
<pages>100</pages>
<size>small</size>
<author>King</author>
</book>
<book>
<title>Pretty book</title>
<pages>150</pages>
<size>medium</size>
</book>
</t>
produces exactly the wanted, correct result:
<t>
<book style="even">
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author-name>Smith</author-name>
</book>
<book style="odd">
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author-name>Wallace</author-name>
</book>
<book style="even">
<title>My book</title>
<pages>200</pages>
<size>big</size>
<author-name>Brown</author-name>
</book>
<book style="odd">
<title>Other book</title>
<pages>100</pages>
<size>small</size>
<author-name>King</author-name>
</book>
<book style="even">
<title>Pretty book</title>
<pages>150</pages>
<size>medium</size>
<author-name/>
</book>
</t>
Explanation: Appropriate use of xsl:number
and templates/pattern matching.
Upvotes: 7
Reputation: 7279
This stylesheet first stores all author nodes as well as all book nodes without authors in a global variable. "/." sorts them in document order. Then the main template iterates over all nodes in this variable and generates output according to the position in the sequence.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="authors" select="(//author | //book[not(author)])/."/>
<xsl:template match="/">
<xsl:element name="result">
<xsl:for-each select="$authors">
<xsl:apply-templates select=".">
<xsl:with-param name="position" select="position()+1"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:element>
</xsl:template>
<xsl:template match="text()|title|pages|size">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="book">
<xsl:param name="position" select="1"/>
<xsl:element name="book">
<xsl:attribute name="style">
<xsl:if test="$position mod 2 = 0">even</xsl:if>
<xsl:if test="$position mod 2 = 1">odd</xsl:if>
</xsl:attribute>
<xsl:apply-templates select="title"/>
<xsl:apply-templates select="pages"/>
<xsl:apply-templates select="size"/>
<xsl:element name="author-name"/>
</xsl:element>
</xsl:template>
<xsl:template match="author">
<xsl:param name="position" select="1"/>
<xsl:element name="book">
<xsl:attribute name="style">
<xsl:if test="$position mod 2 = 0">even</xsl:if>
<xsl:if test="$position mod 2 = 1">odd</xsl:if>
</xsl:attribute>
<xsl:apply-templates select="../title"/>
<xsl:apply-templates select="../pages"/>
<xsl:apply-templates select="../size"/>
<xsl:element name="author-name">
<xsl:if test="name">
<xsl:value-of select="name"/>
</xsl:if>
<xsl:if test="not(name)">
<xsl:value-of select="."/>
</xsl:if>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 7044
(revised)
<xsl:for-each "//author | /book[not(author)]" />
<xsl:variable name="style" select="('even','odd')[(position() mod 2) + 1]" />
<xsl:apply-templates select="."><xsl:with-param name="style" select="$style" /></xsl:apply-templates>
</xsl:for-each>
Then in your author template you can construct the book elements using ..
Upvotes: 0