Thank you so much! It looks great. I'll test it.. and see if I need
anything else.
One requirement is left - mapping namespaces in two XMLs. I'll
probably take care of it later. If you have time, you may give a
modified version of your XSLT to take care of namespaces as well. Or
probably somebody else could..
I am wondering if the tool you have written is able to generate
optimized xsl:template definitions(just like we write XSLT files
manually) and also takes care of overlapping (nested tags).. I'll have
to test it and say further..
Thanks a lot!
Best regards,
On Mon, 14 Mar 2005 07:57:40 +0000, Aron Bock <aronbock(_at_)hotmail(_dot_)com>
wrote:
Midsummer Sun,
Here's how I approached it. At first blush it seems extensible, but your
mileage may vary. The idea is to design a "little-language", with a grammar
and a processor. Also, unfortunately, this post is rather long, and I
couldn't make it any shorter. While one could argue that some of the
templates in the "processor" below could be combined, I prefer to start with
multiple points of abstraction.
Anyway, here's our input file: x1.xml
<x>
<p a="1">1</p>
<q b="2">2</q>
</x>
We want to turn it into this: x2.xml
<x>
<u><w m="1">1</w></u>
<v n="2">2</v>
</x>
First I manually wrote a stylesheet to do this. We start with an "identity
transform", then supply templates to manipulate specific nodes. Here it is:
x.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!-- Start with identity transform: copies input to output -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="x/p">
<u>
<w>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</w>
</u>
</xsl:template>
<xsl:template match="x/p/@a">
<xsl:attribute name="m">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="x/q">
<v>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</v>
</xsl:template>
<xsl:template match="x/q/@b">
<xsl:attribute name="n">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Then I created a "mapping file" with enough information so that I could
mechanixally generate the stylesheet above. I opted to go with flat <map>
elements, and to have element and attribute maps at the same level. That's
because I tend not to like special cases, but again this is just my
preference. Note that element @to mappings show just what the terminal
element will be replaced; not the new path-from-root. Here it is: map.xml
<maps>
<map from="/x/p" to="u/w"/>
<map from="/x/p/@a" to="@m"/>
<map from="/x/q" to="v"/>
<map from="/x/q/@b" to="@n"/>
</maps>
Lastly I wrote a stylesheet to take map.xml as input, and to generate an
output xml file, like x.xsl, that's a stylesheet capable of transforming
x1.xml to x2.xml. It imports another stylesheet; see below. Here is
map.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:xalan="http://xml.apache.org/xalan"
exclude-result-prefixes="xalan">
<xsl:import href="str.tokenize.xsl"/>
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:call-template name="write-stylesheet"/>
</xsl:template>
<xsl:template name="write-stylesheet">
<xsl:element name="xsl:stylesheet">
<xsl:attribute
name="xmlns:xsl">http://www.w3.org/1999/XSL/Transform</xsl:attribute>
<xsl:attribute name="version">1.0</xsl:attribute>
<xsl:element name="xsl:output">
<xsl:attribute
name="omit-xml-declaration">yes</xsl:attribute>
</xsl:element>
<xsl:call-template name="write-identity"/>
<xsl:apply-templates select="/maps/map"/>
</xsl:element>
</xsl:template>
<!-- Writes identity-transform template -->
<xsl:template name="write-identity">
<xsl:element name="xsl:template">
<xsl:attribute name="match">node() | @*</xsl:attribute>
<xsl:element name="xsl:copy">
<xsl:element name="xsl:apply-templates">
<xsl:attribute name="select">node() | @*</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<!-- Processes each mapping -->
<xsl:template match="/maps/map">
<xsl:element name="xsl:template">
<xsl:attribute name="match"><xsl:value-of
select="@from"/></xsl:attribute>
<xsl:call-template name="write-to">
<xsl:with-param name="to" select="@to"/>
</xsl:call-template>
</xsl:element>
</xsl:template>
<!-- Writes the "to" part of each mapping -->
<xsl:template name="write-to">
<xsl:param name="to" select="/.."/>
<xsl:variable name="path">
<xsl:call-template name="tokenize-path">
<xsl:with-param name="path" select="$to"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="write-to-path">
<xsl:with-param name="path"
select="xalan:nodeset($path)/token"/>
</xsl:call-template>
</xsl:template>
<!--
Continuation of template "write-to"; writes the templates
that creates the "to" elements/attributes
-->
<xsl:template name="write-to-path">
<xsl:param name="path" select="/.."/>
<xsl:choose>
<xsl:when test="not($path)">
<!-- Nothing to do (not needed; here just as safeguard) -->
</xsl:when>
<!-- If attribute, this is a 1:1 transform -->
<xsl:when test="starts-with($path[1], '@')">
<xsl:element name="xsl:attribute">
<xsl:attribute name="name">
<xsl:value-of select="substring($path[1], 2)"/>
</xsl:attribute>
<xsl:element name="xsl:value-of">
<xsl:attribute name="select">.</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:when>
<!-- Must be (we assume) an element; recursively write target
nodes, in order -->
<xsl:otherwise>
<xsl:element name="{$path[1]}">
<xsl:choose>
<xsl:when test="not($path[position() != 1])">
<xsl:element name="xsl:apply-templates">
<xsl:attribute
name="select">@*</xsl:attribute>
</xsl:element>
<xsl:element name="xsl:apply-templates"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="write-to-path">
<xsl:with-param name="path"
select="$path[position() != 1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Calls an utility tokenizing template -->
<xsl:template name="tokenize-path">
<xsl:param name="path" select="/.."/>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="$path"/>
<xsl:with-param name="delimiters" select="'/'"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
The stylesheet it imports is str.tokenize.xsl. I got it from Sal Mangano's
"XSLT Cookbook", and he attributes it to Jeni Tennison. Here is
str.tokenize.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template name="tokenize">
<xsl:param name="string" select="''" />
<xsl:param name="delimiters" select="'
'" />
<xsl:choose>
<!-- Nothing to do if empty string -->
<xsl:when test="not($string)" />
<!-- No delimiters signals character level tokenization. -->
<xsl:when test="not($delimiters)">
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="$string" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="$delimiters" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="_tokenize-characters">
<xsl:param name="string" />
<xsl:if test="$string">
<token><xsl:value-of select="substring($string, 1, 1)" /></token>
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="substring($string, 2)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="_tokenize-delimiters">
<xsl:param name="string" />
<xsl:param name="delimiters" />
<xsl:param name="last-delimit"/>
<!-- Extract a delimiter -->
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)" />
<xsl:choose>
<!-- If the delimiter is empty we have a token -->
<xsl:when test="not($delimiter)">
<token><xsl:value-of select="$string"/></token>
</xsl:when>
<!-- If the string contains at least one delimiter we must split it -->
<xsl:when test="contains($string, $delimiter)">
<!-- If it starts with the delimiter we don't need to handle the -->
<!-- before part -->
<xsl:if test="not(starts-with($string, $delimiter))">
<!-- Handle the part that comes befor the current delimiter -->
<!-- with the next delimiter. If ther is no next the first test -->
<!-- in this template will detect the token -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string"
select="substring-before($string, $delimiter)" />
<xsl:with-param name="delimiters"
select="substring($delimiters, 2)" />
</xsl:call-template>
</xsl:if>
<!-- Handle the part that comes after the delimiter using the -->
<!-- current delimiter -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string"
select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiters" select="$delimiters" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- No occurances of current delimiter so move on to next -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string"
select="$string" />
<xsl:with-param name="delimiters"
select="substring($delimiters, 2)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Regards,
--A
_________________________________________________________________
Is your PC infected? Get a FREE online computer virus scan from McAfee®
Security. http://clinic.mcafee.com/clinic/ibuy/campaign.asp?cid=3963
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail:
<mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--