xsl-list
[Top] [All Lists]

RE: Stylesheet from a stylesheet

2005-03-14 00:57:40
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


--~------------------------------------------------------------------
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>
--~--



<Prev in Thread] Current Thread [Next in Thread>