You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by Jon Stevens <jo...@latchkey.com> on 2000/11/22 04:57:20 UTC

[update] velocity version of XSL

Hey all,

Good news, I have a proof of concept of this idea actually working! It runs
as a fairly simple Ant task and it is SO much cleaner and easier to use than
XSL it is silly. It still embodies 100% of the general concept that XSL
tries to inflict on people. :-)

It works by building up a JDOM Document object of your .xml file and placing
that into Velocity's context. There is then a .vsl file that gets processed
by Velocity. Within there, it is very similar to a XSL file. You create
velocimacro's that reference the portions of your XML document as needed
through JDOM's Document API. This is very similar to XSL's
<xsl:apply-template match=""> functionality but it is actually quite a bit
more powerful and easy to use and understand. The output of the processing
of the .vsl document is then saved as standard HTML files.

The cool things is that the recently added Velocimacro's are really proven
as a "good thing" given that what I'm working on is heavily dependent on
them.

I also think that this will turn out to be substantially faster than Xalan
as well. This means that we could actually incorporate something like this
into Turbine or another framework (ie: cocoon) for building dynamic websites
using the XSL concepts, but without the XSL overhead and slowness.

I'm still trying to resolve some issues and bugs with Velocity that I'm
hitting, but Geir is working with me on it...so, once those are resolved, I
will clean up my code some more and post it for everyone to have fun with.
:-)

This is very cool.

-jon


on 11/21/2000 10:49 AM, "Jon Stevens" <jo...@latchkey.com> wrote:

> It would have to be $sort_select or something like that and it would be
> #macro instead of #template_match.
> 
> Ok, I spent a lot of time dreaming about this last night and I think I have
> a pretty good handle on what it would take:
> 
> An Ant task to do the following:
> gather a list of .xml files to process
> passed in a .vsl (instead of .xsl) template for the processing
> 
> the task would then use JDOM to build up an object representation of the
> .xml file which would then be put into velocity's context and made
> available to the .vsl file.
> 
> Then, the Ant task would execute the .vsl page using Velocity and that page
> would work from top to bottom in execution. Within the page would be
> #macro's similar to the above one. The page would execute against the JDOM
> object in the context and do the substitutions there. You could refer to
> your specific objects like <body> from within the object in the context to
> get the values.
> 
> With the recent addition of Velocitimacro's I think that this is going to
> make the .vsl page look and work excellently and be very similar in concept
> to a .xsl page.
> 
> So, I think that I have a way to get 100% (and actually more because of the
> ability to stuff whatever I want into Velocity's Context object) of the
> functionality of XSL, but without ANY of the headache.
> 
> On top of it, I can do stuff within the Ant task such as only process the
> files which have changed by comparing the timestamp of the .xml file with
> the timestamp of the output file and if it has changed, then only
> process/output as necessary.
> 
> Summary: Essentially, I'm replacing the Xalan bit with Velocity. The entire
> overall big picture concept of XSLT execution is still there.
> 
> Comments? Someone want to help me code this up? :-)
> 
> thanks,
> 
> -jon

-- 
twice of not very much is still a lot more than not very much


Re: velocity version of XSL

Posted by Christoph Reck <Ch...@dlr.de>.
This sounds very interesting, I agree that XSL is not a pretty 
templating language; up to now there was no real alternative and 
it is a *real* standard - which does not mean it could be 
replaced by something better.

Can I do something joining in?

I already wrote a context tool to load and handle XML/XSL, maybe some
of its methods can be re-used (see below). I had done the transformation
before with a template and XPath, but it looked really ugly :I (yes,
programming within a VL template should be avoided, this should
be placed in VM/VSL and control templates):
 #set $xmlSpec = "AttributeValueDescriptor[AttributeId='$gipAttributeId']"
 #set $xmlNodes = $xpath.selectNodeList($gipValids, $xmlSpec)
 #if ( $xmlNodes )
   <select name="$_gipAttributeId" size="1">
     <option value="">Any
    #set $loopCount = 0
  #### would need a 'for'-loop here
    #foreach ( $dummy in $Context.newArray($xmlNodes.Length) )
      #set $xmlNode = $_mlNodes.item($_loopCount)
      #set $value = $xpath.eval($xmlNode, "AttributeValue").toString()
      #set $name  = $xpath.eval($_xmlNode, "ShortName").toString()
     <option value="$value"#if ($selected == $value) selected="1"#end>$name
      #set $_loopCount = $Math.sum($_loopCount, 1)
    #end
   </select>
 #end

So I went to XSL and desigend a way in XmlTool.xslt to automagically
populate the <xsl:param> within a XSL template with the corresponding 
context variables - see XmlTool code below (velocimacros are clearly 
a better solution). This is currently xalan_1.1, with xalan_1.2 it
is even cleaner to do.

I did like the feature that one can directly call static methods
(and instantiate java classes as extensions).
See how I used the ORO regexp and the Tokenizer.tokenize:
-------------------------- cut here -----------------------------
<!-- reformat abstract into text (multiline, fixed-space font) -->
<xsl:preserve-space elements="Abstract" />
<xsl:template match="Abstract">
  <xsl:variable name="regexp" 
    select="java:org.apache.oro.text.perl.Perl5Util.new()"/>
  <!-- add space to empty lines to render it after the EOL tokenizer -->
  <xsl:variable name="text" select="java:substitute($regexp,
    's/&#xA;&#xA;/&#xA; &#xA;/gs', string(text()))" />
  <tt>
    <xsl:for-each select=
      "java:org.apache.xalan.xslt.extensions.Tokenizer.tokenize($text, '&#xA;')" >
      <xsl:if test="position() &gt; 1"><br /></xsl:if>
      <!-- skip emitting empty lines -->
      <xsl:if test="string(.) != ' '">
        <!-- emit string after replacing spaces with nbsp's -->
        <xsl:value-of
          select="java:substitute($regexp, 's/  /&#160;&#160;/g', string(.))" />
      </xsl:if>
    </xsl:for-each>
  </tt>
</xsl:template>
-------------------------- cut here -----------------------------
For this reason I have placed a ClassTool into the context to 
instantiate any class. This may be a security issue. In
turbine/velocity the controller would place the required 
instances into the context... WM has a bug it cannot call a method
of a Class.forName() instance of a static class, so it requires
creating a real class instance (which is not possible if the
constructor is private...). 


Here is a simple VL example how I used the XML/XSL tool:
-------------------------- cut here -----------------------------
## ------------------------------------------------------------------------
## File:        templates/control/explain.vm
##
## Description: Controller to show details on an attribute.
##
## Input:       QueryData: attributeId and attributeValue
##              Loads the 'gipValids' using a #parse (possibly from cache)
##
## Output:      Attribute help screen.
##
## Date:        2000-11-08
## Author:      Christoph.Reck@dlr.de
## Copyright:   (c) 2000 Deutsches Zentrum fuer Luft und Raumfahrt
## ------------------------------------------------------------------------
##
#set $page.Title = "GIP Attribute Descriptor"
##
## -------- import attribute valids (cached) --------
#parse ("control/include/gipValids.vm")
##
#set $attributeId    = $data.Parameters.getString("AttributeId")
#set $attributeValue = $data.Parameters.getString("AttributeValue", "")
##
#set $_xslName = "/templates/xslt/explain.xsl" ### make dependant of ENV
#set $_xslFile = $data.ServletContext.getRealPath($_xslName)
#set $_xslDoc = $Xml.loadXSL($_xslFile);
##
## get it by processing the stylesheet
$Xml.format( $Xml.xslt($gipValids, $_xslDoc), 2, $indent)
-------------------------- cut here -----------------------------


This shows a simple way to cache things in the session:
-------------------------- cut here -----------------------------
## ------------------------------------------------------------------------
## File:        templates/control/include/gipValids.vm
##
## Description: Loads the gipValids (caches it in the session).
##
## Input:       file /export/GDS/GUA___.xml
##
## Output:      $xpath (XPathAPI)
##              $gipValids (DOM Tree) cached in session
##
## Date:        2000-11-08
## Author:      Christoph.Reck@dlr.de
## Copyright:   (c) 2000 Deutsches Zentrum fuer Luft und Raumfahrt
## ------------------------------------------------------------------------
##
#set $xpath = $Class.newInstance("org.apache.xpath.XPathAPI")
##
## ------------ import attribute valids -------------
## try getting the gipValids as cached within the session
#set $gipValids = $data.session.getAttribute("gipValids")
#if ( !$gipValids )
  #set $xmlName = "/export/GDS/GUA___.xml" ### make dependant of ENV
  #set $xmlFile = $data.ServletContext.getRealPath($xmlName)
  ## get DOM root
  #set $gipValids = $Xml.loadXML($xmlFile).getDocumentElement()
  ## save in session
  #set $dummy = $data.session.setAttribute("gipValids", $gipValids)
  ## cleanup
context (with Velocimacros this can be made obsolete!)
  #set $dummy = $Context.remove( ["xmlName", "xmlFile"] )
#end
-------------------------- cut here -----------------------------

This is the XmlTool, specially loadXML(), xstl(), and format()
methods are interesting...
-------------------------- cut here -----------------------------
package de.dlr.dfd.naomi.turbine.util;

import java.io.*;
import java.util.Vector;
import org.apache.velocity.Context;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.apache.xpath.XPathAPI;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xerces.dom.*;
import org.apache.xml.serialize.*;
import org.apache.xalan.xslt.*;

public class XmlTool
{
    /** The context to extract XSLT parameters **/
    protected Context context = null;

    /** The indention used for formatting the output **/
    protected int indent = 2;

    /**
     * Constructor needs a backpointer to the context.
     *
     * @param context A Context.
     */
    public XmlTool(Context context)
    {
        this.context = context;
    }

    /**
     * Set the default indentation for the formatting.
     *
     * @param indent The default indentation amount to use.
     */
    public void setIndent(int indent)
    {
        this.indent = indent;
    }

    /**
     * Get the default indentation for the formatting.
     *
     * @return The default indentation amount.
     */
    public int getIndent()
    {
        return this.indent;
    }

    /**
     * Format XML indented nicely. Uses the default
     * indentation, and no indent offset.
     *
     * @param xml The XML node to pretty print.
     * @return A string with the XML prettely formatted.
     * @see #setIndent(int)
     */
    public String format(Node xml)
    {
        return format(xml, indent, "");
    }


    /**
     * Format XML indented nicely.
     *
     * @param xml The XML node to pretty print.
     * @return A string with the XML prettely formatted.
     */
    public String format(Node xml, int indent, String indentOffset)
    {
        OutputFormat fmt = new OutputFormat();
        fmt.setIndent(indent);
        fmt.setLineSeparator("\n" + indentOffset);
        fmt.setOmitXMLDeclaration(true);
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        HTMLSerializer serializer = new HTMLSerializer(buf, fmt);
        try {
            if (xml instanceof Element)
              serializer.serialize( (Element) xml );
            else if (xml instanceof Document)
              serializer.serialize( ((Document) xml).getDocumentElement() );
            else
              serializer.serialize( xml.getOwnerDocument() );
        } catch (Exception ex) {
            // ignore
        }

        return buf.toString();
    }

    /**
     * Load an XML document into a DOM tree.
     *
     * @param fileName The name of the file to load.
     * @returns an XML document.
     */
    public static Document loadXML(String fileName)
           throws FileNotFoundException, IOException, SAXException
    {
        DOMParser parser = new DOMParser();
        FileInputStream fileIn = new FileInputStream(fileName);
        InputSource xmlIn = new InputSource(fileIn);
        parser.parse(xmlIn);
        return parser.getDocument();
    }

    /**
     * Load an XML stylesheet.
     *
     * @param fileName The name of the file to load.
     * @returns an XSLT Stylesheet.
     */
    public static Stylesheet loadXSL(String fileName)
           throws FileNotFoundException, SAXException
    {
        XSLTProcessor processor = XSLTProcessorFactory.getProcessor();
        FileInputStream fileIn = new FileInputStream(fileName);
        XSLTInputSource xslIn = new XSLTInputSource(fileIn);
        return processor.processStylesheet(xslIn);
    }

    /**
     * Transform an XML document using XSLT.
     *
     * @param xml The XML node to transform.
     * @param xsl The XSLT Stylesheet used for the transform.
     * @returns The transformed XML document.
     */
    public Document xslt(Node xml, Stylesheet xsl)
           throws SAXException
    {
        // create the input
        XSLTInputSource xmlIn = new XSLTInputSource(xml);
        XSLTInputSource xslIn = new XSLTInputSource(xsl);

        // create the output
        Document outDoc = new DocumentImpl();
        XSLTResultTarget xmlOut = new XSLTResultTarget(outDoc);

        // get the processor
        XSLTProcessor processor = XSLTProcessorFactory.getProcessor();

        Vector params = xsl.getTopLevelVariables();
        for(int i = 0; i < params.size(); i++)
        {
            ElemVariable param = (ElemVariable)params.elementAt(i);

            // extract value from context
            String name = param.m_qname.m_localpart; //### change for xalan 2.0
            Object value = context.get(name);

            if (value != null)
              processor.setStylesheetParam(name, "'" + value.toString() + "'");
//##        processor.setStylesheetParam(name, processor.createXString(value));
        }

        // do the actual transformation
        processor.process(xmlIn, xslIn, xmlOut);

        return outDoc;
    }

    /**
     * Transform an XML files using XSLT.
     * @param xmlFileName The name of the XML file to transform
     * @param xslFileName The name of the XSLT file used for the transformation.
     * @param indentOffset The initial indentation.
     */
    public String xslt(String xmlFileName,
                       String xslFileName,
                       String indentOffset)
           throws FileNotFoundException, IOException, SAXException
    {
        Document   xml = loadXML(xmlFileName);
        Stylesheet xsl = loadXSL(xslFileName);
        return format( xslt(xml, xsl), indent, indentOffset );
    }

} // end of XmlTool.java
-------------------------- cut here -----------------------------

:)
Christoph

[ann] anakia (A Velocity/JDOM version of XSL)

Posted by Jon Stevens <jo...@latchkey.com>.
Hey all,

The code is checked in with a bare bones example to show it off.

cd jakarta-velocity/build
./build-velocity jar

cd jakarta-velocity/examples/anakia/build
./build.sh

You should then get a index.html file in the anakia/docs directory.

Please note that last modified checking is turned on, so after you build a
file once, you will need to either:

#1. remove the index.html file
#2. touch the index.xml file
#3. touch the site.vsl file

...In order to get the index.html file to build again. This is way cool as
it only needs to rebuild the files which have been modified! :-)

I'm not actually using the project.xml file yet in the example, but hope to
soon. This will provide the ability to easily have a "global site nav".

thanks,

-jon

-- 
twice of not very much is still a lot more than not very much