Reputation: 3218
If possible, I need to write an XSL file that transforms an XML syntax similar to this
<media address="1234 A St.">
<book title="My Book" isbn="1324-1123-1456-1566" />
<book title="Your Book" isbn="1232-1123-1456-1566" />
</media>
into a format like this
<library>
<information>
<building id="1">
<address>1234 A St.</address>
</building>
</information>
<medialist>
<book_definitions>
<book_definition id="2" />
<book_definition id="3" />
</book_definitions>
<book_metadata>
<metadata id="4">
<isbn>1324-1123-1456-1566</isbn>
<book_definition_id>2</book_definition_id>
</metadata>
<metadata id="5">
<isbn>1232-1123-1456-1566</isbn>
<book_definition_id>3</book_definition_id>
</metadata>
</book_metadata>
<book_instances>
<book_instance id="6">
<book_definition_id>2</book_definition_id>
<book_metadata_id>4</book_metadata_id>
<title>My Book</title>
</book_instance>
<book_instance id="7">
<book_definition_id>2</book_definition_id>
<book_metadata_id>5</book_metadata_id>
<title>Your Book</title>
</book_instance>
</book_instances>
</medialist>
</library>
I realize the target format is a bit convoluted, but I have no control over it.
I have successfully written XSL to transform most of the XML tags correctly using template modes. ie.
<xsl:template match="/media/book" mode="definitions">
<xsl:template match="/media/book" mode="metadata">
<xsl:template match="/media/book" mode="instance">
However, I have been trying to use <xsl:number> or some other trick to generate the id's correctly with little success.
The target format has two constraints on the ids: every id attribute, reguardless the <element>, name must be unique. The ids, once sorted, must be sequential, but they can appear in any order in the target format i.e. both (1,2,3,4,5) and (5,2,3,1,4) are acceptable but (1,2,4,5,6) is not.
Is there any way to accomplish this via XSL?
Upvotes: 0
Views: 393
Reputation: 11416
Though I just noticed that Ian Roberts already explained this approach, I already was about to write an XSLT for this, so I'll post it regardless - following XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<library>
<xsl:apply-templates />
</library>
</xsl:template>
<xsl:template match="media">
<xsl:variable name="current">
<xsl:number />
</xsl:variable>
<information>
<building>
<xsl:attribute name="id">
<xsl:value-of select="$current" />
</xsl:attribute>
<address>
<xsl:value-of select="@address" />
</address>
</building>
</information>
<medialist>
<book_definitions>
<xsl:apply-templates mode="definition">
<xsl:with-param name="current">
<xsl:value-of select="$current" />
</xsl:with-param>
</xsl:apply-templates>
</book_definitions>
<book_metadata>
<xsl:apply-templates mode="metadata">
<xsl:with-param name="current">
<xsl:value-of select="$current" />
</xsl:with-param>
</xsl:apply-templates>
</book_metadata>
<book_instances>
<xsl:apply-templates mode="instances">
<xsl:with-param name="current">
<xsl:value-of select="$current" />
</xsl:with-param>
</xsl:apply-templates>
</book_instances>
</medialist>
</xsl:template>
<xsl:template match="book" mode="definition">
<xsl:param name="current" />
<book_definition id="{(position() + $current)}" />
</xsl:template>
<xsl:template match="book" mode="metadata">
<xsl:param name="current" />
<metadata id="{(position() + $current + count(parent::media/book))}">
<isbn>
<xsl:value-of select="@isbn" />
</isbn>
<book_definition_id>
<xsl:value-of select="position() + $current" />
</book_definition_id>
</metadata>
</xsl:template>
<xsl:template match="book" mode="instances">
<xsl:param name="current" />
<book_instance id="{(position() + 2*count(parent::media/book) + $current)}">
<book_definition_id>
<xsl:value-of select="position() + $current" />
</book_definition_id>
<book_metadata_id>
<xsl:value-of select="position() + $current + count(parent::media/book)" />
</book_metadata_id>
<title>
<xsl:value-of select="@title" />
</title>
</book_instance>
</xsl:template>
</xsl:transform>
when applied to your input XML generates the output
<library>
<information>
<building id="1">
<address>1234 A St.</address>
</building>
</information>
<medialist>
<book_definitions>
<book_definition id="2"/>
<book_definition id="3"/>
</book_definitions>
<book_metadata>
<metadata id="4">
<isbn>1324-1123-1456-1566</isbn>
<book_definition_id>2</book_definition_id>
</metadata>
<metadata id="5">
<isbn>1232-1123-1456-1566</isbn>
<book_definition_id>3</book_definition_id>
</metadata>
</book_metadata>
<book_instances>
<book_instance id="6">
<book_definition_id>2</book_definition_id>
<book_metadata_id>4</book_metadata_id>
<title>My Book</title>
</book_instance>
<book_instance id="7">
<book_definition_id>3</book_definition_id>
<book_metadata_id>5</book_metadata_id>
<title>Your Book</title>
</book_instance>
</book_instances>
</medialist>
</library>
As it's not clear if the input XML consists of multiple media
elements - e.g. a next building that then should start with the id 8 - I used the number as parameter instead of just adding 1.
Note that this template won't work for a second media
element - this would just start with 2 as value for id
- and would have to be adjusted accordingly in case the real input XML contains multiple buildings / media
elements.
For book definition, the id is the sum of the position of the current book and the value of the parameter current
:
<book_definition id="{(position() + $current)}" />
The metadata id is the sum of the position of the current book, all books of the current / parent media
element and current
:
<metadata id="{(position() + $current + count(parent::media/book))}">
And the book instance id, taking into account the previously generated ids for metadata, is the sum of the position of the current book, current
and all books of the parent media
element * 2:
<book_instance id="{(position() + 2*count(parent::media/book) + $current)}">
Upvotes: 1
Reputation: 122364
It seems to me that since (apart from the building), you're always generating one ID per book for each different "kind" of node, you can guarantee unique and sequential ids by generating them according to a formula. If you have N books then you could "decree" that
book_definition
for book m is always 1+m
(so running from 2 up to N+1)(N+1)+m
(2N+1)+m
If you follow this scheme everywhere you can simply calculate the appropriate cross reference book_definition_id
etc. with no need for a lookup table.
Upvotes: 1