As various people will have noticed, I've been having a lot of trouble with
XSL lately. Brief history: I wrote myself an XML toolkit back in 2000, and
it worked well enough for me, so it's been little changed since. However,
it works only with an obsolete version of Saxon (6.2.2 I think), and it
has a number of small bugs; and I've at last got to the point where I need
to fix it. And I'm finding it, frankly, absurdly frustrating and
difficult.
But what's frustrating me at this moment is something absurd and weird.
Some weeks ago I did a test with a dummy XSL file called 'dummy.xsl'. Now,
whenever I test with modern Xalan2, I'm persistently getting an error
message:
/home/simon/tmp/test/dummy.xsl; Line #0; Column #0; stylesheet requires
attribute: version
I am really, really not referring to a file called 'dummy.xsl'. I have
grepped through every single file in my working set, and nothing - really
nothing at all - contains the text 'dummy.xsl'. Also, the stylesheet I am
using, really does have a version attribute - see below.
I do not get this message when I'm using my obsolete version of Saxon (and,
indeed, all my transforms work with my obsolete Saxon). So I guessed that
Xalan might use a cache somewhere on my hard disk that I don't know about.
I copied my working files - all three of them, stripping my test down as
far as I could - to my laptop, which should be virgin.
When I run the test, on the laptop, using Xalan2, using GCJ as the Java
implementation I get the 'stylesheet requires attribute: version' error,
but no reference to dummy.xsl. But if I use IBM's Java, I get exactly the
same error message as I get on my development machine - *INCLUDING* the
reference to dummy.xsl!
So I now completely fail to understand where my problem is coming from. I
include my three files below, so that anyone who has the time and patience
can verify my problem and suggest solutions (hopefully not including
either pistols at dawn or a vial of cyanide)
,----[ /home/simon/tmp/xsltest/testharness.sh ]
#!/bin/bash
# XSLPROC=/usr/share/java/xalan2.jar
XSLPROC=/usr/local/lib/java/saxon.jar
export CLASSPATH=".:$X SLPROC"
javac TransformerTest Harness.java
java TransformerTest Harness \
-d org.apache.xerc es.dom.DOMImple mentationImpl -s test.xsl
`----
,----[ /home/simon/tmp/xsltest/test.xsl ]
<?xml version="1.0" encoding="UTF-8"?>
<xsl:styleshe et version="1.0"
xmlns:xsl="http ://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<test>
<xsl:apply-templates/>
</test>
</xsl:template>
<xsl:template match="@* node()">
<xsl:copy>
<xsl:apply-templates select="@* node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
`----
,----[ /home/simon/tmp/xsltest/TransformerTest Harness.java ]
import org.apache.xml. serialize.Outpu tFormat;
import org.apache.xml. serialize.XMLSe rializer;
import org.w3c.dom.DOM Implementation;
import org.w3c.dom.Doc ument;
import org.w3c.dom.Ele ment;
import org.w3c.dom.Nod e;
import org.xml.sax.Inp utSource;
import java.io.File;
import java.io.FileInp utStream;
import java.io.FileOut putStream;
import java.io.OutputS tream;
import java.io.StringR eader;
import java.text.DateF ormat;
import java.util.Date;
import javax.xml.parse rs.DocumentBuil der;
import javax.xml.parse rs.DocumentBuil derFactory;
import javax.xml.trans form.Transforme rFactory;
import javax.xml.trans form.dom.DOMRes ult;
import javax.xml.trans form.dom.DOMSou rce;
import javax.xml.trans form.stream.Str eamResult;
/**
* Test XSL transformations
*/
public class TransformerTest Harness
{
//~ Static fields/initializers ------------------------------------------
/** A string with parsable content */
public static final String PARSETHIS =
"<parsable><p>P arse me</p></parsable>";
//~ Instance fields -----------------------------------------------------
/** the DOM Implementation I will use */
public DOMImplementati on rebus = null;
/** the XSL stylesheet (transform) I will use */
public File stylesheet;
/** DOCUMENT ME! */
public String label = "Transforme r test harness";
/**
* A directory into which to write output files - if null, use standard
* out
*/
protected File baseDir = null;
/** A factory class for XSL transformers; */
protected TransformerFact ory transformerFact ory = null;
//~ Constructors --------------------------------------------------------
/**
* Construct a new test harness
*/
public TransformerTest Harness( )
{
super( );
try
{
transformerFact ory = TransformerFact ory.newInstance ( );
}
catch ( Exception d )
{
System.err.prin tln( "Could not instantiate transformer: " +
d.getMessage( ) );
System.exit( 4 );
}
}
//~ Methods -------------------------------------------------------------
/**
* Generate a document
*
* @return the document
*/
public Document generate( )
{
Document doc = rebus.createDoc ument( "", "root", null );
Element root = doc.getDocument Element( );
Element generated = doc.createEleme nt( "generated" );
root.appendChil d( generated );
DateFormat df = DateFormat.getD ateTimeInstance ( );
generated.appen dChild( doc.createTextN ode( df.format( new
Date( ) ) ) );
try
{
root.appendChil d( maybeParse( doc, PARSETHIS ) );
}
catch ( Exception any )
{
System.err.prin tln( "Failed to parse: " + any.getMessage( ) );
}
return doc;
}
/**
* @param args
*/
public static void main( String[] args ) throws Exception
{
TransformerTest Harness tth = new TransformerTest Harness( );
for ( int i = 0; i < args.length; i++ )
{
if ( args[i].startsWith( "-" ) && ( args[i].length( ) 1 ) )
{
switch ( args[i].charAt( 1 ) )
{
case 'b':
tth.baseDir = new File( args[++i] );
if ( tth.baseDir.exi sts( ) &&
tth.baseDir.can Write( ) &&
tth.baseDir.isD irectory( ) )
{
System.err.prin tln( "Using base directory " +
tth.baseDir.get CanonicalPath( ) );
}
else
{
System.err.prin tln( "Bad base directory: " +
args[i] );
}
break;
case 'd':
try
{
tth.rebus =
(DOMImplementat ion) Class.forName( args[++i] )
.newInstance( );
System.err.prin tln(
"Using DOM implementation of class " +
tth.rebus.getCl ass( ).getName( ) );
}
catch ( Exception any )
{
System.err.prin tln( "Could not find DOM " +
"implementa tion " + args[i] );
System.err.prin tln( any.getMessage( ) );
System.exit( 2 );
}
break;
case 's':
tth.stylesheet = new File( args[++i] );
if ( tth.stylesheet. exists( ) &&
tth.stylesheet. canRead( ) )
{
System.err.prin tln( "Using XSL stylesheet " +
tth.stylesheet. getCanonicalPat h( ) );
}
else
{
System.err.prin tln( "Can't read stylesheet " +
args[i] );
System.exit( 1 );
}
break;
default:
System.err.prin tln( "Unrecognis ed arg " + args[i] );
System.exit( 3 );
break;
}
}
else
{
System.err.prin tln( "Unrecognis ed arg " + args[i] );
System.exit( 3 );
break;
}
}
tth.test( );
}
/**
* Get a suitable output stream - if I have a base directory, a file in
* that directory with this name; else the standard out
*
* @param name a name ofr my file if any
*
* @return an output stream
*
* @throws Exception unlikely
*/
public OutputStream getOutputStream ( String name )
throws Exception
{
OutputStream out = System.out;
if ( ( baseDir != null ) && baseDir.exists( ) &&
baseDir.canWrit e( ) && baseDir.isDirec tory( ) )
{
out = new FileOutputStrea m( new File( baseDir, name ) );
}
return out;
}
/**
* Run the tests
*/
public void test( )
{
Document generated = generate( );
System.out.prin tln( label );
try
{
System.out.prin tln( "Printer test -justprint" );
justPrint( generated, "justprint" );
if ( stylesheet != null )
{
DocumentBuilder p =
DocumentBuilder Factory.newInst ance( ).newDocumentBu ilder( );
Document xslDocument =
p.parse( new InputSource( new FileInputStream ( stylesheet ) ) );
System.out.prin tln( "Verify stylesheet -transform.xsl") ;
justPrint( xslDocument, "transform.xsl" );
System.out.prin tln( "DOM to DOM test -dom2dom" );
domToDom( generated, xslDocument, "dom2dom" );
System.out.prin tln( "DOM to Stream test -dom2stream" );
domToDom( generated, xslDocument, "dom2stream " );
}
else
{
System.err.prin tln( "No stylesheet?");
}
}
catch ( Exception e )
{
System.err.prin tln( "Whilst running print test:" );
e.printStackTra ce( );
}
}
/**
* Transform from DOM object to DOM object; seralize after
*
* @param doc the document transformed
* @param name the name for printing
*/
protected void domToDom( Document doc, Document xslDocument, String
name )
{
try
{
if ( stylesheet != null )
{
DOMSource domSource = new DOMSource( doc );
DOMResult domResult = new DOMResult( );
transformerFact ory.newTransfor mer( new DOMSource( xslDocument ) )
.transform( domSource, domResult );
Node root = domResult.getNo de( );
if ( root instanceof Document )
{
justPrint( (Document) root, name );
}
else
{
System.err.prin tln( "DOM to DOM transform returned " +
root );
}
}
}
catch ( Exception e )
{
System.err.prin tln( "DOM to DOM failed: " + e.getMessage( ) );
e.printStackTra ce( );
}
}
/**
* Transform from DOM object to output stream directly
*
* @param doc the document transformed
* @param transform the xsl transform to apply
* @param name the name for printing
*/
protected void domToStream( Document doc, Document transform, String
name )
{
try
{
if ( stylesheet != null )
{
DOMSource domSource = new DOMSource( doc );
StreamResult result =
new StreamResult( getOutputStream ( name ) );
transformerFact ory.newTransfor mer( new DOMSource( transform ) )
.transform( domSource, result );
}
}
catch ( Exception e )
{
System.err.prin tln( "DOM to DOM failed: " + e.getMessage( ) );
e.printStackTra ce( );
}
}
/**
* Just print the document
*
* @param doc the document to print
* @param name the name of the file to print to
*
* @throws Exception DOCUMENT ME!
*/
protected void justPrint( Document doc, String name )
throws Exception
{
XMLSerializer dickens =
new XMLSerializer( getOutputStream ( name ),
new OutputFormat( doc, null, true ) );
dickens.seriali ze( doc );
}
/**
* Construct a node representing this value. It's perfectly possible (and
* possibly legitimate) that the value of a child should contain embedded
* markup. If so, try to parse a node out of it.
*
* @param doc the document in which the node is to be created
* @param unparsed the string, possibly with embedded markup, to parse
*
* @exception GenerationExcep tion if parsing fails
*/
protected Node maybeParse( Document doc, String unparsed )
throws Exception
{
Node val = doc.createTextN ode( unparsed ); // safe default
if ( unparsed != null ) // defensive
{
if ( unparsed.indexO f( "<" ) -1 ) // it looks like markup
{
if ( !unparsed.trim( ).startsWith( "<" ) )
{
// nasty: if it contains markup, but
// isn't contained in markup, the
// parser will barf.
unparsed = "<parsed>" + unparsed + "</parsed>";
}
try
{
DocumentBuilder parser =
DocumentBuilder Factory.newInst ance( )
.newDocumentBui lder( );
if ( parser == null )
{
System.err.prin tln( "Could not initialise XML parser" );
}
InputSource i =
new InputSource( new StringReader( unparsed ) );
Document parsed = parser.parse( i );
if ( parsed != null )
{
Node root = parsed.getDocum entElement( );
val = doc.importNode( root, true );
}
}
catch ( Exception e )
{
System.err.prin tln( "Could not parse '" + unparsed +
"'as XML" );
e.printStackTra ce( System.err );
}
}
}
return val;
}
}
`----
,----[ Sample output with Saxon (i.e. what I think you should get) ]
Using DOM implementation of class
org.apache.xerc es.dom.DOMImple mentationImpl
Using XSL stylesheet /home/simon/tmp/xsltest/test.xsl
Transformer test harness
Printer test -justprint
<?xml version="1.0"?>
<root>
<generated>18-Mar-2007 14:33:26</generated>
<parsable>
<p>Parse me</p>
</parsable>
</root>
Verify stylesheet -transform.xsl
<?xml version="1.0"?>
<xsl:styleshe et version="1.0"
xmlns:xsl="http ://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<test>
<xsl:apply-templates/>
</test>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
DOM to DOM test -dom2dom
<?xml version="1.0"?>
<test>
<root>
<generated>18-Mar-2007 14:33:26</generated>
<parsable>
<p>Parse me</p>
</parsable>
</root>
</test>
DOM to Stream test -dom2stream
<?xml version="1.0"?>
<test>
<root>
<generated>18-Mar-2007 14:33:26</generated>
<parsable>
<p>Parse me</p>
</parsable>
</root>
</test>
,----[ Sample output with Xalan2 ]
Using DOM implementation of class
org.apache.xerc es.dom.DOMImple mentationImpl
Using XSL stylesheet /home/simon/tmp/xsltest/test.xsl
Transformer test harness
Printer test -justprint
<?xml version="1.0"?>
<root>
<generated>18-Mar-2007 14:34:50</generated>
<parsable>
<p>Parse me</p>
</parsable>
</root>
Verify stylesheet -transform.xsl
<?xml version="1.0"?>
<xsl:styleshe et version="1.0"
xmlns:xsl="http ://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<test>
<xsl:apply-templates/>
</test>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
DOM to DOM test -dom2dom
/home/simon/tmp/xsltest/dummy.xsl; Line #0; Column #0; stylesheet requires
attribute: version
file:///home/simon/tmp/xsltest/dummy.xsl; Line #0; Column #0;
java.util.Empty StackException
DOM to Stream test -dom2stream
/home/simon/tmp/xsltest/dummy.xsl; Line #0; Column #0; stylesheet requires
attribute: version
file:///home/simon/tmp/xsltest/dummy.xsl; Line #0; Column #0;
java.util.Empty StackException
--
si***@jasmine.o rg.uk (Simon Brooke) http://www.jasmine.org.uk/~simon/
Do not sail on uphill water.
- Bill Lee