[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message]

RE: Stylesheet from a stylesheet

Subject: RE: Stylesheet from a stylesheet
From: "Aron Bock" <aronbock@xxxxxxxxxxx>
Date: Mon, 14 Mar 2005 07:57:40 +0000
tokenize xsl
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="' &#x9;&#xA;'" />


 <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


Current Thread

PURCHASE STYLUS STUDIO ONLINE TODAY!

Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced!

Buy Stylus Studio Now

Download The World's Best XML IDE!

Accelerate XML development with our award-winning XML IDE - Download a free trial today!

Don't miss another message! Subscribe to this list today.
Email
First Name
Last Name
Company
Subscribe in XML format
RSS 2.0
Atom 0.3
Site Map | Privacy Policy | Terms of Use | Trademarks
Free Stylus Studio XML Training:
W3C Member
Stylus Studio® and DataDirect XQuery ™are products from DataDirect Technologies, is a registered trademark of Progress Software Corporation, in the U.S. and other countries. © 2004-2013 All Rights Reserved.