blob: 4a18dd4bbf264b8099208e6e886a94fabff5dd15 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xalan" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, Lotus
* Development Corporation., http://www.lotus.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xalan.processor;
import java.net.URL;
import java.util.Stack;
import java.util.Vector;
import java.io.File;
import java.util.StringTokenizer;
import org.apache.xalan.templates.Constants;
import org.apache.xalan.templates.ElemTemplateElement;
import org.apache.xalan.templates.ElemTemplate;
import org.apache.xalan.templates.ElemLiteralResult;
import org.apache.xalan.templates.ElemAttributeSet;
import org.apache.xalan.templates.ElemAttribute;
import org.apache.xalan.templates.StylesheetRoot;
import org.apache.xalan.templates.Stylesheet;
import org.apache.xalan.templates.XMLNSDecl;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerException;
import org.apache.xpath.XPath;
import org.apache.xpath.XPathFactory;
import org.apache.xpath.compiler.XPathParser;
import org.apache.xpath.compiler.FunctionTable;
import org.apache.xpath.functions.Function;
import org.apache.xalan.res.XSLMessages;
import org.apache.xalan.res.XSLTErrorResources;
import org.apache.xml.utils.PrefixResolver;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.helpers.NamespaceSupport;
import org.xml.sax.InputSource;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.TransformerException;
// Java Compiler support.
import org.apache.xml.utils.synthetic.JavaUtils;
/**
* <meta name="usage" content="advanced"/>
* Initializes and processes a stylesheet via SAX events.
* This differs from StylesheetHandler in adding a post-
* processing stage which attempts to replace part or all
* of the recursively-interpreted Templates collection with
* Java code, which is generated, compiled, and patched
* back into the tempate trees.
* @see StylesheetHandler
*/
public class CompilingStylesheetHandler
extends StylesheetHandler
{
/** Constants for the "maythrow" field (gates whether catch
* statements are generated):
*/
final int MAY_THROW_SAX_EXCEPTION=0x01;
/**
* Create a StylesheetHandler object, creating a root stylesheet
* as the target.
* @throws May throw TransformerConfigurationException if a StylesheetRoot
* can not be constructed for some reason.
*/
public CompilingStylesheetHandler(TransformerFactoryImpl processor)
throws TransformerConfigurationException
{
super(processor);
}
/**
* Receive notification of the end of the document.
* Run standard cleanup of the internal representation,
* then start trying to replace that rep with custom code.
*
* @throws org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.ContentHandler#endDocument
*/
public void endDocument ()
throws org.xml.sax.SAXException
{
// try
// {
// Perform normal end-of-parse operations -- in particular,
// the "composition" process which performs data gathering
// and optimization of the interpretable tree.
super.endDocument();
if(isStylesheetParsingComplete())
{
Stylesheet current=getStylesheet();
// Record the Templates as we compile them; this list gets
// passed into the "bundling" code to copy their excutable code
// into the .xsb file.
// TODO: Can we just use the StylesheetRoot's list?
Vector compiledTemplates=new Vector();
// cache the typecast
StylesheetRoot root=(StylesheetRoot)getStylesheetRoot();
// For all templates used in this stylesheet (including
// those from imported and included stylesheets),
// compile to Java. (Takes advantage of the fact that
// ComposedStylesheet processing has already gathered
// these and reconciled conflicts.)
// New TemplateList being generated
org.apache.xalan.templates.TemplateList newTl
= new org.apache.xalan.templates.TemplateList();
// Iterate over contents of old TemplateList
org.apache.xalan.templates.TemplateList.TemplateWalker tw
=root.getTemplateListComposed().getWalker();
// Scan all templates in old list, compile, insert into new
ElemTemplate et;
while ( null != (et = tw.next()) )
{
ElemTemplate ct = compileTemplate(et);
// If compilation succeeds, use it; else fall back on interp
newTl.setTemplate( (ct!=null) ? ct : et);
}
// Postprocess/reconcile list
newTl.compose();
// And make it active
root.setTemplateListComposed(newTl);
// TODO: Theoretically, we can now discard imports/includes
// -- they're no longer pointing at the right ElemTemplates
// anyway. There's discussion of doing so in any case when
// they aren't needed for tooling support. Always do it here,
// leave it to be handled there, or other?
/****** OLD APPROACH
int nImports = root.getGlobalImportCount();
for(int imp = 0; imp < nImports; imp++)
{
org.apache.xalan.templates.StylesheetComposed
sheet = root.getGlobalImport(imp);
// loop from sheet.recomposeTemplates
// Scan both main and included stylesheets
int nIncludes = sheet.getIncludeCountComposed();
for(int k = nIncludes-1; k >= -1; k--)
{
Stylesheet included = sheet.getIncludeComposed(k);
int n = included.getTemplateCount();
for(int i = 0; i < n; i++)
{
ElemTemplate t=included.getTemplate(i);
if(!(t instanceof CompiledTemplate))
{
ElemTemplate newT=compileTemplate(t);
if(newT!=t)
{
included.replaceTemplate(newT,i);
compiledTemplates.addElement(newT);
}
}
}
}
// Need to rebuild each sheet's cache.
sheet.recomposeTemplates(true);
}
// After compiling I think we have to reconstruct the cached
// "composed templates" set.
// NOTE: RECOMPOSE WAS A ONE-TIME OP
// I've set up an alternate entry that allows me to flush
// before composing. That could take over from the standard
// entry point... Or flush might be made the new default
// behavior; I don't know whether that would be appropriate.
root.recomposeTemplates(true);
****** OLD APPROACH */
// TODO: Theoretically, we no longer need the Imported trees.
// Eliminating them would save some bytes in memory and in the
// .xsb bundle file. (They should be needed only for tooling
// support, which doesn't seem to apply to compiled operation.)
// Try that _AFTER_ the compile loop has been revived!
// TODO: Should bundling occur elsewhere or be optional?
CompiledStylesheetBundle.createBundle(root,compiledTemplates);
}
// }
// catch(TransformerException te)
// {
// throw new org.xml.sax.SAXException(te);
// }
}
// Minor optimization: Only need to look these up once
private static final org.apache.xml.utils.synthetic.Class sClassCompiledTemplate=org.apache.xml.utils.synthetic.Class.forClass(org.apache.xalan.processor.CompiledTemplate.class);
private static final org.apache.xml.utils.synthetic.Class sClassObjectArray=org.apache.xml.utils.synthetic.Class.forClass(Object[].class);
/**
Analyse an <xsl:template> tree, converting some (most?)
of its recursive evaluate() behavior into straight-line
Java code. That code is then compiled and instantiated
to produce a new "equivalent" object, which can be used
to replace the original Template.
<p>
Note that the compiled Template may have to reference
children that we don't yet know how to compile. This
is done by copying references to those kids into a vector,
and having the generated code invoke them via offsets
in that vector.
<p>
At this time, the compiler's rather simpleminded. No
common subexpression extraction is performed, and no
attempt is made to optimize across stylesheets. We're
just flattening some of the code and reorganizing it
into direct SAX invocations.
<p>
Literal result elements become SAX begin/endElement
Context-insensitive attributes become literal assignment
to the attribute trees.
xsl:choose and xsl:for-each may be flattened
Namespace declarations become SAX prefix operations.
<p>
Other nodes simply have their .evaluate() invoked
TODO: Their children really should be walked for further compilation opportunities.
*/
ElemTemplate compileTemplate(ElemTemplate source)
{
ElemTemplate instance=source;
String className=generateUniqueClassName("org.apache.xalan.processor.ACompiledTemplate");
try
{
// public class ACompiledTemplate000...
// extends CompiledTemplate (partly abstract superclass)
org.apache.xml.utils.synthetic.Class tClass=
org.apache.xml.utils.synthetic.Class.declareClass(className);
tClass.setModifiers(java.lang.reflect.Modifier.PUBLIC);
tClass.setSuperClass(sClassCompiledTemplate);
// public constructor: Copy values from original
// template object, pick up "uncompiled children"
// array from compilation/instantiation process.
org.apache.xml.utils.synthetic.reflection.Constructor ctor=
tClass.declareConstructor();
ctor.setModifiers(java.lang.reflect.Modifier.PUBLIC);
ctor.addParameter(tClass.forClass(ElemTemplate.class),"original");
ctor.addParameter(sClassObjectArray,"interpretArray");
ctor.getBody().append(
"super(original,\n"
+'\t'+source.getLineNumber()+','+source.getColumnNumber()+",\n"
+'\t'+makeQuotedString(source.getPublicId())+",\n"
+'\t'+makeQuotedString(source.getSystemId())+",\n"
+"\tinterpretArray);\n"
);
// vector built during compilation, winds up in m_interpretArray
Vector interpretVector=new Vector();
// Now for the big guns: the execute() method is where all the
// actual work of the template is performed, and is what we've
// really set out to compile.
// public void execute(TransformerImpl transformer,
// Node sourceNode, QName mode)
org.apache.xml.utils.synthetic.reflection.Method exec=
tClass.declareMethod("execute");
exec.setModifiers(java.lang.reflect.Modifier.PUBLIC);
exec.addParameter(
tClass.forClass(org.apache.xalan.transformer.TransformerImpl.class),
"transformer");
exec.addParameter(
tClass.forClass(org.w3c.dom.Node.class),"sourceNode");
exec.addParameter(
tClass.forClass(org.apache.xml.utils.QName.class),"mode");
exec.addExceptionType(
tClass.forClass(javax.xml.transform.TransformerException.class));
// If there are no kids, the body is a no-op.
ElemTemplateElement firstChild = source.getFirstChildElem();
if(null == firstChild)
{
exec.getBody().append("//empty template");
}
else
{
// Body startup
// **** FIRST DRAFT, I'm continuing to use ResultTreeHandler
// In future we might want to move toward direct SAX generation,
// (though that requires reordering data into normal SAX
// event order, generating trace events, and figuring
// out how to cooperate w/ template fragments not yet
// switched over to compiled mode)... or to raw text output,
// though I doubt that's significantly faster than SAX and it's
// definitely less convenient if further processing is desired.
StringBuffer body=exec.getBody().append(
"if(transformer.S_DEBUG)\n"
+" transformer.getTraceManager().fireTraceEvent(sourceNode, mode, this);\n"
+"org.apache.xalan.transformer.ResultTreeHandler rhandler = transformer.getResultTreeHandler();\n"
+"org.xml.sax.ContentHandler saxChandler = rhandler.getContentHandler();\n"
+"if(null == sourceNode) {\n"
// throws(javax.xml.transform.TransformerException
+" transformer.getMsgMgr().error(this, sourceNode,\n"
+" org.apache.xalan.res.XSLTErrorResources.ER_NULL_SOURCENODE_HANDLEAPPLYTEMPLATES);\n"
//sourceNode is null in handleApplyTemplatesInstruction!
+" return; }\n"
+"org.apache.xpath.XPathContext xctxt = transformer.getXPathContext();\n"
+"// Check for infinite loops if requested\n"
+"boolean check = (transformer.getRecursionLimit() > -1);\n"
+"if (check)\n"
+" transformer.getStackGuard().push(this, sourceNode);\n"
+"String avtStringedValue; // ***** Optimize away?\n\n"
// Establish dynamic namespace context for this invocation
+"org.xml.sax.helpers.NamespaceSupport nsSupport=new org.xml.sax.helpers.NamespaceSupport();\n"
+"org.xml.sax.helpers.NamespaceSupport savedNsSupport=(org.xml.sax.helpers.NamespaceSupport)m_nsThreadContexts.get(Thread.currentThread());\n"
+"m_nsThreadContexts.put(Thread.currentThread(),nsSupport);\n"
);
compileChildTemplates(source,body,interpretVector);
// Body Cleanup
body.append(
// Restore dynamic namespace context for this invocation
"if(null!=savedNsSupport) m_nsThreadContexts.put(Thread.currentThread(),savedNsSupport);\n"
+"else m_nsThreadContexts.remove(Thread.currentThread());\n\n"
+"// Decrement infinite-loop check\n"
+"if (check)\n"
+" transformer.getStackGuard().pop();\n"
);
}
// Compile the new java class. Note that any existing class
// by the same name will be walked upon... which is why I'm
// going to the trouble of generating unique classnames.
// TODO: ***** ISSUE: Where write out the class? Parameterize.
// Needs to be somewhere on the classpath, or we need a classloader.
Class realclass=compileSyntheticClass(tClass,".");
// Prepare the array of execute()ables
Object[] eteParms=new Object[interpretVector.size()];
interpretVector.copyInto(eteParms);
// Instantiate -- note that this will be a singleton,
// as each template is probably unique
org.apache.xml.utils.synthetic.reflection.Constructor c=
tClass.getConstructor(ctor.getParameterTypes());
Object[] parms={source,eteParms};
instance=(ElemTemplate)c.newInstance(parms);
}
catch(org.apache.xml.utils.synthetic.SynthesisException e)
{
System.out.println("CompilingStylesheetHandler class synthesis error");
e.printStackTrace();
}
catch(java.lang.IllegalAccessException e)
{
System.out.println("CompilingStylesheetHandler class comilation error");
e.printStackTrace();
}
catch(java.lang.NoSuchMethodException e)
{
System.out.println("CompilingStylesheetHandler constructor resolution error");
e.printStackTrace();
}
catch(java.lang.InstantiationException e)
{
System.out.println("CompilingStylesheetHandler constructor invocation error");
e.printStackTrace();
}
catch(java.lang.reflect.InvocationTargetException e)
{
System.out.println("CompilingStylesheetHandler constructor invocation error");
e.printStackTrace();
}
return instance;
}
int compileElemTemplateElement(ElemTemplateElement kid,StringBuffer body,Vector interpretVector)
{
int maythrow=0;
++uniqueVarSuffix; // Maintain unique variable naming
switch(kid.getXSLToken())
{
case Constants.ELEMNAME_LITERALRESULT:
maythrow=compileElemLiteralResult((ElemLiteralResult)kid,body,interpretVector);
break;
// TODO: ***** Redirection of attr value not working yet.
// TODO: ***** Attrs should be preprocessed (SAX-ordered)
//case Constants.ELEMNAME_ATTRIBUTE:
// compileElemAttribute((ElemAttribute)kid,body,interpretVector);
// break;
default:
// Safety net: We don't yet know how to compile this
// type of node, so instead we'll pass it into the
// compiled instance and invoke it interpretively.
int offset=interpretVector.size();
interpretVector.addElement(kid);
body.append(
"((org.apache.xalan.templates.ElemTemplateElement)m_interpretArray["+offset+"]).execute(transformer,sourceNode,mode);\n"
);
break;
}
return maythrow;
}
int compileElemLiteralResult(ElemLiteralResult ele,StringBuffer body,Vector interpretVector)
{
int maythrow=0;
++uniqueVarSuffix; // Maintain unique variable naming
body.append("rhandler.startElement(\""
+ele.getNamespace()+"\",\""
+ele.getLocalName()+"\",\""
+ele.getRawName()+"\");\n");
// Handle xsl:use-attribute-sets
// expand ElemUse.execute(transformer, sourceNode, mode);
compileUseAttrSet(ele,body,interpretVector);
// Add stylesheet namespace declarations -- unrolling of
// ElemTemplateElement.executeNSDecls, plus additional logic to
// track that information within the generated code.
// (I really wish I could avoid the latter replication of data.
// Unfortunately the possibility of <xsl:attribute name="prefix:foo"/>
// with no explicit namespace requires that we be able to resolve a
// prefix in terms of the stylesheet, _not_ in terms of the output
// document... which means we need to track the stylesheet's NS context.)
Vector prefixTable=ele.getPrefixes();
int n = prefixTable.size();
boolean newNSlevel=(n>0);
if(newNSlevel)
body.append("nsSupport.pushContext();\n");
for(int i = 0; i < n; i++)
{
XMLNSDecl decl = (XMLNSDecl)prefixTable.elementAt(i);
// Output document
if(!decl.getIsExcluded())
body.append(
"rhandler.startPrefixMapping(\""
+decl.getPrefix()+"\",\""
+decl.getURI()+"\");\n"
);
// CompiledTemplate state
body.append(
"nsSupport.declarePrefix(\""
+decl.getPrefix()+"\",\""
+decl.getURI()+"\");\n"
);
}
// Process AVTs.
// TODO: Should we be checking for excluded namespace prefixes?
// ***** That wasn't done in non-compiled version, but was an open issue.
java.util.Enumeration avts=ele.enumerateLiteralResultAttributes();
if (avts!=null)
{
while(avts.hasMoreElements())
{
org.apache.xalan.templates.AVT avt = (org.apache.xalan.templates.AVT)avts.nextElement();
String avtValueExpression=null;
boolean literal=avt.isContextInsensitive();
if(literal)
{
// Literal value, can fully resolve at compile time.
// Exception won't be thrown, but we've gotta catch
// try{
// %XTBD% ??
// avtValueExpression=makeQuotedString(
// avt.evaluate(null,null,null)
// );
// }catch(TransformerException e)
// {
// }
}
else
{
// Expression. Must resolve at runtime.
// TODO: ***** It might be possible to unwind this too.
int offset=interpretVector.size();
interpretVector.addElement(avt);
body.append(
"avtStringedValue=((org.apache.xalan.templates.AVT)"
+"(m_interpretArray["+offset+"])"
+").evaluate(xctxt,sourceNode,this,new StringBuffer());\n"
+"if(null!=avtStringedValue)\n{\n"
);
avtValueExpression="avtStringedValue";
}
body.append(
"rhandler.addAttribute(\""
+avt.getURI()+"\",\""
+avt.getName()+"\",\""
+avt.getRawName()
+"\",\"CDATA\","
+avtValueExpression
+");\n");
// Match the open brace, if one was issued
if(!literal)
body.append("} // endif\n");
} // end while more AVTs
} // end process AVTs
// Process children
// TODO:***** "Process m_extensionElementPrefixes && m_attributeSetsNames"
// TODO: Should maythrow be passed outward, unwinding try/catch?
// maythrow !=
compileChildTemplates(ele,body,interpretVector);
// Close the patient
body.append("rhandler.endElement(\""
+ele.getNamespace()+"\",\""
+ele.getLocalName()+"\",\""
+ele.getRawName()+"\");\n");
if(newNSlevel)
body.append("nsSupport.popContext();\n");
return maythrow | MAY_THROW_SAX_EXCEPTION;
}
// Detect and report AttributeSet loops.
Stack attrSetStack=new Stack();
void compileUseAttrSet(ElemTemplateElement ete,StringBuffer body,Vector interpretVector)
{
++uniqueVarSuffix; // Maintain unique variable naming
body.append(
"if(transformer.S_DEBUG)\n"
+" transformer.getTraceManager().fireTraceEvent(sourceNode, mode, this);\n"
);
// expand ElemUse.applyAttrSets(transformer, getStylesheetComposed(),
// ele.getUseAttributeSets(), sourceNode, mode);
// ***** DOES THIS CAST NEED TO BE CHECKED?
org.apache.xml.utils.QName[] attributeSetsNames=((org.apache.xalan.templates.ElemUse)ete).getUseAttributeSets();
if(null != attributeSetsNames)
{
org.apache.xalan.templates.StylesheetRoot stylesheet=ete.getStylesheetRoot();
int nNames = attributeSetsNames.length;
for(int i = 0; i < nNames; i++)
{
org.apache.xml.utils.QName qname = attributeSetsNames[i];
Vector attrSets = stylesheet.getAttributeSetComposed(qname);
int nSets = attrSets.size();
for(int k = 0; k < nSets; k++)
{
ElemAttributeSet attrSet =
(ElemAttributeSet)attrSets.elementAt(k);
// expand ElemAttributeSet.execute(transformer, sourceNode, mode);
if(attrSetStack.contains(attrSet))
{
// TODO: ***** WHAT'S THE RIGHT WAY TO REPORT THIS ERROR?
String errmsg="TEMPLATE COMPILATION ERROR: ATTRIBUTE SET RECURSION SUPPRESSED in "+attrSet.getName().getLocalPart();
/**/System.err.println(errmsg);
/**/body.append("// ***** "+errmsg+" *****/\n");
/**/return;
//throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_XSLATTRSET_USED_ITSELF, new Object[]{attrSet.getName().getLocalPart()})); //"xsl:attribute-set '"+m_qname.m_localpart+
}
attrSetStack.push(attrSet);
// Recurse, since attrsets can reference attrsets
compileUseAttrSet(attrSet,body,interpretVector);
// %XTBD%
// ElemAttribute attr = (ElemAttribute)attrSet.getFirstChild();
// while(null != attr)
// {
// compileElemTemplateElement(attr,body,interpretVector);
// attr = (ElemAttribute)attr.getNextSibling();
// }
attrSetStack.pop();
}
}
}
}
String compileAVTvalue(org.apache.xalan.templates.AVT avt,StringBuffer body,Vector interpretVector)
{
// Literal string is easy -- except for potential of " within "".
// %XTBD%
// if(avt.isContextInsensitive())
// try
// {
// return makeQuotedString(avt.evaluate(null,null,null));
// } catch(TransformerException e)
// {
// // Should never arise
// String s=">UNEXPECTED ERROR evaluating context-insensitive AVT<";
// System.err.println(s);
// e.printStackTrace();
// return "\""+s+'"';
// }
// Otherwise no compilation yet, just reference and return expression.
// Note that we do _not_ code-gen directly into the body, due to
// some concerns about where this might be used.
// YES, this is inconsistant.
int offset=interpretVector.size();
interpretVector.addElement(avt);
return
"( ((org.apache.xalan.templates.AVT)m_interpretArray["
+offset
+"]).evaluate(transformer.getXPathContext(),sourceNode,this,new StringBuffer()) )"
;
}
// Wrap quotes around a text string.
// Escapes any contained quotes, converts null to the string "null".
// Used to prepare literal string arguments.
String makeQuotedString(String in)
{
if(in==null)
return "null";
StringBuffer out=new StringBuffer("\""); // don't use '"', it's taken as int
int startpos=0,quotepos;
for(quotepos=in.indexOf('"',startpos);
quotepos!=-1;
startpos=quotepos+1,quotepos=in.indexOf('"',startpos))
{
out.append(in.substring(startpos,quotepos)).append('\\').append('\"');
}
out.append(in.substring(startpos)).append('"');
return out.toString();
}
// Get the children of the xsl:attribute element as the string value.
// String val = transformer.transformToString(this, sourceNode, mode);
// Returns string econtaiing result expression
String compileTransformToString(ElemTemplateElement ea,StringBuffer body,Vector interpretVector)
{
++uniqueVarSuffix; // Maintain unique variable naming
String savedResultTreeHandler="savedResultTreeHandler"+uniqueVarSuffix;
String shandler="shandler"+uniqueVarSuffix;
String sw="sw"+uniqueVarSuffix;
String sfactory="sfactory"+uniqueVarSuffix;
String format="format"+uniqueVarSuffix;
String serializer="serializer"+uniqueVarSuffix;
String ioe="ioe"+uniqueVarSuffix;
body.append("\n// Begin transformToString (probably of attribute contents)\n"
+"// by redirecting output into a StringWriter.\n"
+"org.apache.xalan.transformer.ResultTreeHandler "+savedResultTreeHandler+"=rhandler;\n"
+"org.xml.sax.ContentHandler "+shandler+";\n"
+"java.io.StringWriter "+sw+";\n"
+"try\n{\n"
+"org.apache.xml.org.apache.xalan.serialize.SerializerFactory "+sfactory+"=org.apache.xml.org.apache.xalan.serialize.SerializerFactory.getSerializerFactory(\"text\");\n"
+sw+"=new java.io.StringWriter();\n"
+"org.apache.xalan.templates.OutputProperties "+format+"=new org.apache.xalan.templates.OutputProperties();\n"
+format+".setPreserveSpace(true);\n"
+"org.apache.xml.org.apache.xalan.serialize.Serializer "+serializer+"="+sfactory+".makeSerializer("+sw+","+format+");\n"
+shandler+"="+serializer+".asContentHandler();\n"
+"}\ncatch (java.io.IOException "+ioe+")\n{\n"
+"throw new javax.xml.transform.TransformerException("+ioe+");\n}\n"
// TODO: ***** DO WE NEED transformer.setResultTreeHandler()?
+"rhandler=new org.apache.xalan.transformer.ResultTreeHandler(transformer,"+shandler+");\n\n"
+"rhandler.startDocument();\n"
+"\n//unwind executeChildTemplates\n"
);
compileChildTemplates(ea,body,interpretVector);
body.append(
"\nrhandler.flushPending();\n"
+"rhandler.endDocument();\n"
+"rhandler="+savedResultTreeHandler+";\n"
+"//End transformToString unwind; result in "+sw+"\n\n"
);
return "("+sw+".toString())";
}
void compileElemAttribute(ElemAttribute ea,StringBuffer body,Vector interpretVector)
{
++uniqueVarSuffix; // Maintain unique variable naming
String attrName="attrName"+uniqueVarSuffix;
String val="val"+uniqueVarSuffix;
String attrNameSpace="attrNameSpace"+uniqueVarSuffix;
String prefix="prefix"+uniqueVarSuffix;
String ns="ns"+uniqueVarSuffix;
String attributeHandled="attributeHandled"+uniqueVarSuffix;
String nsprefix="nsprefix"+uniqueVarSuffix;
String ex="ex"+uniqueVarSuffix;
String localName="localName"+uniqueVarSuffix;
// The attribute name has to be evaluated as an AVT.
// We may add/alter the prefix later.
//String origAttrName="origAttrName"+uniqueVarSuffix;
String origAttrName=compileAVTvalue(ea.getName(),body,interpretVector);
// If they are trying to add an attribute when there isn't an
// element pending, it is an error.
// TODO: ****** Could these tests occur _before_ we transform? Factor?
body.append(
"if(null == rhandler.getPendingElementName())\n"
+"{\n"
+"transformer.getMsgMgr().warn(org.apache.xalan.res.XSLTErrorResources.WG_ILLEGAL_ATTRIBUTE_NAME, new Object[]{"+origAttrName+"}); \n"
+"// warn(templateChild, sourceNode, \"Trying to add attribute after element child has been added, ignoring...\");\n"
+"}\n"
);
// This check was done in the interpretive code... is it Really Needed?
if(null==origAttrName)
return;
body.append("boolean "+attributeHandled+"=false;\n");
// The attribute name has to be evaluated as an AVT,
// and gets stashed because we may add a prefix later
body.append("String "+attrName+"="+origAttrName+";\n");
// Get the children of the xsl:attribute element as the string value.
// String val = transformer.transformToString(this, sourceNode, mode);
String strval=compileTransformToString(ea,body,interpretVector);
body.append("String "+val+"="+strval+";\n");
// If they are trying to add an attribute when there isn't an
// element pending, it is an error.
// TODO: ****** Could these tests occur _before_ we transform?
body.append(
"if(null == rhandler.getPendingElementName())\n"
+"{\n"
+"transformer.getMsgMgr().warn(org.apache.xalan.res.XSLTErrorResources.WG_ILLEGAL_ATTRIBUTE_NAME, new Object[]{"+origAttrName+"}); \n"
+"// warn(templateChild, sourceNode, \"Trying to add attribute after element child has been added, ignoring...\");\n"
+"}\n"
+"if(null=="+attrName+")\n return;\n\n"
);
// Namespace is also an AVT, which means we can't count on it having been
// fully evaluated at stylesheet load time.
body.append(
"String "+attrNameSpace+"=null; // by default\n"
);
if(null!=ea.getNamespace()) // Can/must decide at compile time!
{
String avtValueExpression=compileAVTvalue(ea.getNamespace(),body,interpretVector);
body.append(
attrNameSpace+"="+avtValueExpression+";\n"
+"if(null!="+attrNameSpace+" && "+attrNameSpace+".length()>0)\n"
+"{\n"
+" String "+prefix+"=rhandler.getPrefix("+attrNameSpace+");\n"
+" if(null=="+prefix+")\n"
+" {\n"
+" "+prefix+"=rhandler.getNewUniqueNSPrefix();\n"
+" rhandler.startPrefixMapping("+prefix+","+attrNameSpace+");\n"
+" }\n"
+" "+attrName+"=("+prefix+"+\':'+org.apache.xml.utils.QName.getLocalPart("+attrName+"));\n"
+"}\n"
);
}
// Else: Is attribute xmlns type?
// TODO: ***** Must this retest for non-null namespace, due to
// compile-time decision above? Shouldn't really need to since the
// qname test ought to cover it, but....
body.append(
"if(org.apache.xml.utils.QName.isXMLNSDecl("+origAttrName+"))\n"
+"{ // Just declare namespace prefix \n"
+" String "+prefix+"=org.apache.xml.utils.QName.getPrefixFromXMLNSDecl("+origAttrName+");\n"
+" String "+ns+"=rhandler.getURI("+prefix+");\n"
+" if(null=="+ns+")\n"
+" rhandler.startPrefixMapping("+prefix+","+val+");\n"
);
// Here the original code returned from ea.execute. Since
// we're expanding inline, we can't return, so instead we gate
// the later code via a boolean flag. (Could generate a jump to a
// label instead, but I think this is clearer.)
body.append(" "+attributeHandled+"=true;\n");
body.append("}\n"
+"else\n{\n"
+" String "+nsprefix+"=org.apache.xml.utils.QName.getPrefixPart("+origAttrName+");\n"
+" if(null=="+nsprefix+") "+nsprefix+"=\"\";\n"
// attrNameSpace = getNamespaceForPrefix(nsprefix);
// Handles the case where name was specified, namespace wasn't
// Resolves prefix in terms of STYLESHEET context, not input
// or output doc context... which requires that we track
// that context in our compiled code. Grrr.
+attrNameSpace+"=nsSupport.getURI("+nsprefix+");\n"
// The if here substitutes for early returns in original code
+"if(!"+attributeHandled+")\n{\n"
+"String "+localName+"=org.apache.xml.utils.QName.getLocalPart("+attrName+");\n"
+"rhandler.addAttribute("+attrNameSpace+","+localName+","+attrName+",\"CDATA\","+val+");\n"
+"} //end attributeHandled\n"
+"} //end else\n"
);
}
// Issue: When unrolling code, variable name resuse becomes an issue.
// Java doesn't permit local name scoping such as
// int i; { int i; }
// so we have to generate unique names. We don't really need a stack,
// since we don't care whether the suffix matches level of unrolling.
int uniqueVarSuffix=0;
void compileChildTemplates(ElemTemplateElement source,StringBuffer body,Vector interpretVector)
{
int maythrow=0;
++uniqueVarSuffix; // Maintain unique variable naming
// If no kids, no code gen.
if(source.getFirstChildElem()!=null)
{
// ***** TransformerImpl.executeChildTemplates does a
// bunch of additional setup/shutdown work. Since I
// don't know otherwise, I'm assuming I have to
// emulate that work as part of unwinding this.
//
// Set up the TransformerImpl context:
String savedLocatorName="savedLocator"+uniqueVarSuffix;
String varstackName="varstack"+uniqueVarSuffix;
body.append(
"\n// Unwound Transformer.executeChildTemplates: //\n\n"
+"// We need to push an element frame in the variables stack,\n"
+"// so all the variables can be popped at once when we're done.\n"
+"org.apache.xpath.VariableStack "+varstackName+" = transformer.getXPathContext().getVarStack();\n"
+varstackName+".pushElemFrame();\n"
+"javax.xml.transform.SourceLocator "+savedLocatorName+" = xctxt.getSAXLocator();\n"
);
body.append("try {\n\n");
// Process the kids
for(ElemTemplateElement kid=source.getFirstChildElem();
kid!=null;
kid=kid.getNextSiblingElem())
{
//TODO: NEED EQUIVALENT? This Node is Going Away...
// body.append("transformer.pushElemTemplateElement(kid);\n");
maythrow|=compileElemTemplateElement(kid,body,interpretVector);
//TODO: NEED EQUIVALENT? This Node is Going Away...
// body.append("transformer.popElemTemplateElement(kid);\n");
}
// End the class wrapper.
// TODO: Should "maythrow" be returned and processed @ outermost compile?
body.append("\n\n}\n");
if(0!=(maythrow & MAY_THROW_SAX_EXCEPTION))
body.append("catch(org.xml.sax.SAXException se) {\n"
+" throw new javax.xml.transform.TransformerException(se);\n"
+"}\n"
);
body.append(
"finally {\n"
+" xctxt.setSAXLocator("+savedLocatorName+");\n"
+" // Pop all the variables in this element frame.\n"
+" "+varstackName+".popElemFrame();\n"
+"}\n"
);
}
}
/** Run this class description through the Java compiler,
* and patch the result back into the Synthetic system.
* Note that classLocation is treated as a directory iff
* it ends in FileLocator; if not, it's treated as a file
* name and output is written to the directory that file
* would be found in (possibly relative). However, "."
* is treated as being found in itself rather than in "..".
* TODO: ***** Move polished version into org.apache.xml.utils.synthetic.Class
* TODO: Should we use a classloader rather than std. classpath?
*/
Class compileSyntheticClass(org.apache.xml.utils.synthetic.Class tClass, String classLocation)
{
Class resolved=null;
// Write class relative to specified starting location
// (which should be on the classpath, so we can load
// the resulting class!).
String filename=classLocation;
int fnstart=filename.lastIndexOf(File.separator);
StringBuffer subdir=new StringBuffer(
(fnstart>=0)
? filename.substring(0,fnstart)
: ".");
StringTokenizer parts=
new StringTokenizer(tClass.getPackageName(),".");
while(parts.hasMoreTokens())
{
subdir.append(File.separator).append(parts.nextToken());
}
if(fnstart<0)
subdir.append(File.separator);
File jfile=new File(subdir.toString());
jfile.mkdirs();
subdir.append(tClass.getShortName()).append(".java");
filename=subdir.toString();
jfile=new File(filename);
// Write Java source
try {
java.io.PrintWriter out=
new java.io.PrintWriter(new java.io.FileWriter(filename));
tClass.toSource(out,0);
out.close();
}
catch(java.io.IOException e)
{
System.err.println("ERR: File open failed for "+
filename);
e.printStackTrace();
return null;
}
// Try to pick up the same classpath we're executing under. That
// ought to include everything in Xalan and the standard libraries.
String classpath=System.getProperty ("java.class.path");
// If compiling with the -g switch (Java debugging), we should retain
// the Java source code to support debugging into the synthesized class.
// Some additional diagnostics are also turned on as a side effect.
// TODO: Find a better place to put the debugging control. Property? Parm?
String javac_options=
System.getProperty("org.apache.xalan.processor.CompilingStylesheetHandler.options","");
boolean debug=(javac_options.indexOf("-g")>=0);
// Run the compilation. Encapsulates the fallbacks and
// workarounds needed to achieve this in various environments.
JavaUtils.setDebug(debug);
boolean compileOK=
JavaUtils.JDKcompile(filename,classpath);
if (compileOK)
{
if(debug)
System.err.println("\tCompilation successful. Debug specified, .java file retained.");
else if(jfile.exists())
jfile.delete();
// Now try to load it!
try {
resolved=Class.forName(tClass.getName());
tClass.setRealClass(resolved);
}
catch(ClassNotFoundException e)
{
System.err.println("ERR: synthesized Template class load failed for "+
tClass.getName());
e.printStackTrace();
}
catch(org.apache.xml.utils.synthetic.SynthesisException e)
{
System.err.println("ERR: synthesized Template class realization failed for "+
tClass.getName());
e.printStackTrace();
}
}
else
{
if(debug)
System.err.println("\tTemplate compilation failed; retaining .java file");
// This should probably be an exception instead
System.err.println("ERR: Java compilation failed for "+
filename);
System.exit(1);
}
return resolved;
}
/** Generate a mostly-unique class name.
* The longterm answer here is File.createTempFile... but that
* requires JDK 1.2 or above.
*/
static int templateCounter=0;
String generateUniqueClassName(String basename)
{
//TODO: ***** ISSUE: CLASS NAMING. This is kluged
//as a temporary measure; we need to think about a
//more formal solution. Each compilation of each
//stylesheet winds up with its own set of classes,
//which need to be made globally unique if they aren't
//to collide with other compilations loaded into the same
//JVM. We really need classnames based on something like
//UUID or GID, combining the source stylesheet's URI and locator,
//where it was processed, exactly when it was processed
//... and then going the extra step to guard against
//multitasking causing two stylesheets to start within
//the same clock tick.
// TODO: ***** Subissue: Package name components will correspond
// to directories when we compile. We shouldn't spawn more
// directories than we must. That may mean we'd rather
// flatten the source address.
long intAddr=0;
try
{
byte[] ipAddr=java.net.InetAddress.getLocalHost().getAddress();
for(int i=0;i<ipAddr.length;++i)
intAddr=(intAddr<<8)+ipAddr[i];
}
catch(java.net.UnknownHostException e)
{
// Should never occur
e.printStackTrace();
}
long templateNumber;
synchronized(this)
{ // Atomic get-next-count -- last-ditch protection against
// the risk of multiple threads trying to process the same
// stylesheet at the same instant on the same machine.
templateNumber = ++templateCounter;
}
// Generate a mostly-unique name.
// TODO: ***** NOT GUARANTEED TO BE COMPLETELY UNIQUE, since
// there could, theoretically, be two seperate PROCESSES
// which are running this code simultaneously. The officious
// answer would be to tap into a system-level unique ID service,
// like UUID or GID.
// ***** Unclear these names will be supportable on all
// systems. Some platforms may have limits on filename length.
// Could move more of the fields into directory names if
// that helps.
String className=
basename
+"On"
+intAddr
+"at"
+new java.util.Date().getTime() // msec since 1970 epoch
+'n'
+templateNumber // minor multithread protection
;
return className;
}
}