[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message] RE: Stylesheet from a stylesheet
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
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
<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 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>
<!-- 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>
<xsl:call-template name="write-to-path"> <xsl:with-param name="path" select="xalan:nodeset($path)/token"/> </xsl:call-template> </xsl:template>
<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>
</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
|
PURCHASE STYLUS STUDIO ONLINE TODAY!Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced! Download The World's Best XML IDE!Accelerate XML development with our award-winning XML IDE - Download a free trial today! Subscribe in XML format
|