package client.net.sf.saxon.ce.style;
import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.LogController;
import client.net.sf.saxon.ce.PreparedStylesheet;
import client.net.sf.saxon.ce.expr.Expression;
import client.net.sf.saxon.ce.expr.instruct.Executable;
import client.net.sf.saxon.ce.functions.ConstructorFunctionLibrary;
import client.net.sf.saxon.ce.functions.FunctionLibraryList;
import client.net.sf.saxon.ce.functions.StandardFunction;
import client.net.sf.saxon.ce.functions.SystemFunctionLibrary;
import client.net.sf.saxon.ce.js.IXSLFunctionLibrary;
import client.net.sf.saxon.ce.om.DocumentURI;
import client.net.sf.saxon.ce.om.NamespaceBinding;
import client.net.sf.saxon.ce.om.StructuredQName;
import client.net.sf.saxon.ce.trans.RuleManager;
import client.net.sf.saxon.ce.trans.StripSpaceRules;
import client.net.sf.saxon.ce.trans.XPathException;
import java.util.*;
import com.google.gwt.logging.client.LogConfiguration;
/**
* Represents the stylesheet module at the root of the import tree, that is, the module
* that includes or imports all the others. Note that this object is present at compile time only,
* unlike the Executable, which also exists at run-time.
*/
public class PrincipalStylesheetModule extends StylesheetModule {
private PreparedStylesheet preparedStylesheet;
// library of functions that are in-scope for XPath expressions in this stylesheet
private FunctionLibraryList functionLibrary;
// version attribute on xsl:stylesheet element of principal stylesheet module
private String version;
// index of global variables and parameters, by StructuredQName
// (overridden variables are excluded).
// Used at compile-time only, except for debugging
private HashMap<StructuredQName, Declaration> globalVariableIndex =
new HashMap<StructuredQName, Declaration>(20);
// table of named templates. Key is the integer fingerprint of the template name;
// value is the XSLTemplate object in the source stylesheet.
private HashMap<StructuredQName, Declaration> templateIndex =
new HashMap<StructuredQName, Declaration>(20);
// Table of named stylesheet functions. A two level lookup, using first the arity and then
// the expanded name of the function.
private HashMap<Integer, HashMap<StructuredQName, Declaration>> functionIndex =
new HashMap(8);
// map for allocating unique numbers to local parameter names. Key is a
// StructuredQName; value is a boxed int.
private HashMap<StructuredQName, Integer> localParameterNumbers = null;
// namespace aliases. This information is needed at compile-time only
private int numberOfAliases = 0;
private List<Declaration> namespaceAliasList = new ArrayList<Declaration>(5);
private HashMap<String, NamespaceBinding> namespaceAliasMap;
private Set<String> aliasResultUriSet;
// count of the maximum number of local variables in xsl:template match patterns
private int largestPatternStackFrame = 0;
// cache of stylesheet documents. Note that multiple imports of the same URI
// lead to the stylesheet tree being reused
private HashMap<DocumentURI, XSLStylesheet> moduleCache = new HashMap<DocumentURI, XSLStylesheet>(4);
public PrincipalStylesheetModule(XSLStylesheet sourceElement, int precedence) {
super(sourceElement, precedence);
}
public void setPreparedStylesheet(PreparedStylesheet preparedStylesheet) {
this.preparedStylesheet = preparedStylesheet;
}
public PreparedStylesheet getPreparedStylesheet() {
return preparedStylesheet;
}
public PrincipalStylesheetModule getPrincipalStylesheetModule() {
return this;
}
/**
* Create the function library
*/
public FunctionLibraryList createFunctionLibrary() {
Configuration config = getPreparedStylesheet().getConfiguration();
functionLibrary = new FunctionLibraryList();
int functionSet = StandardFunction.CORE | StandardFunction.XSLT;
functionLibrary.addFunctionLibrary(
SystemFunctionLibrary.getSystemFunctionLibrary(functionSet));
functionLibrary.addFunctionLibrary(
new StylesheetFunctionLibrary(this, true));
functionLibrary.addFunctionLibrary(
ConstructorFunctionLibrary.getInstance());
functionLibrary.addFunctionLibrary(
new IXSLFunctionLibrary());
functionLibrary.addFunctionLibrary(
new StylesheetFunctionLibrary(this, false));
return functionLibrary;
}
/**
* Get the function library. Available only on the principal stylesheet module
* @return the function library
*/
public FunctionLibraryList getFunctionLibrary() {
return functionLibrary;
}
/**
* Add a module to the cache
* @param key the key to be used (based on the absolute URI)
* @param module the stylesheet document tree corresponding to this absolute URI
*/
public void putStylesheetDocument(DocumentURI key, XSLStylesheet module) {
moduleCache.put(key, module);
}
/**
* Get a module from the cache
* @param key the key to be used (based on the absolute URI)
* @return the stylesheet document tree corresponding to this absolute URI
*/
public XSLStylesheet getStylesheetDocument(DocumentURI key) {
XSLStylesheet sheet = moduleCache.get(key);
if (sheet != null) {
XPathException warning = new XPathException(
"Stylesheet module " + key + " is included or imported more than once. " +
"This is permitted, but may lead to errors or unexpected behavior");
getPreparedStylesheet().reportWarning(warning);
}
return sheet;
}
/**
* Preprocess does all the processing possible before the source document is available.
* It is done once per stylesheet, so the stylesheet can be reused for multiple source
* documents. The method is called only on the XSLStylesheet element representing the
* principal stylesheet module
*/
public void preprocess() throws XPathException {
// process any xsl:include and xsl:import elements
spliceIncludes();
// build indexes for selected top-level elements
buildIndexes();
// check for use of schema-aware constructs
checkForSchemaAwareness();
// process the attributes of every node in the tree
processAllAttributes();
// collect any namespace aliases
collectNamespaceAliases();
// fix up references from XPath expressions to variables and functions, for static typing
for (int i = 0; i < topLevel.size(); i++) {
Declaration decl = topLevel.get(i);
StyleElement inst = decl.getSourceElement();
if (!inst.isActionCompleted(StyleElement.ACTION_FIXUP)) {
inst.setActionCompleted(StyleElement.ACTION_FIXUP);
// if (inst instanceof XSLVariableDeclaration) {
// System.err.println("Fixup global variable " + ((XSLVariableDeclaration)inst).getVariableQName());
// }
inst.fixupReferences();
}
}
// Validate the whole logical style sheet (i.e. with included and imported sheets)
XSLStylesheet top = getSourceElement();
setInputTypeAnnotations(top.getInputTypeAnnotationsAttribute());
Declaration decl = new Declaration(this, top);
if (!top.isActionCompleted(StyleElement.ACTION_VALIDATE)) {
top.setActionCompleted(StyleElement.ACTION_VALIDATE);
top.validate(decl);
for (int i = 0; i < topLevel.size(); i++) {
decl = topLevel.get(i);
decl.getSourceElement().validateSubtree(decl);
}
}
}
/**
* Build indexes for selected top-level declarations
*/
private void buildIndexes() throws XPathException {
// Scan the declarations in reverse order, that is, highest precedence first
for (int i = topLevel.size() - 1; i >= 0; i--) {
Declaration decl = topLevel.get(i);
decl.getSourceElement().index(decl, this);
}
}
/**
* Process the attributes of every node in the stylesheet
*/
public void processAllAttributes() throws XPathException {
getSourceElement().processDefaultCollationAttribute("");
getSourceElement().prepareAttributes();
for (Declaration decl : topLevel) {
StyleElement inst = decl.getSourceElement();
if (!inst.isActionCompleted(StyleElement.ACTION_PROCESS_ATTRIBUTES)) {
inst.setActionCompleted(StyleElement.ACTION_PROCESS_ATTRIBUTES);
try {
inst.processAllAttributes();
} catch (XPathException err) {
decl.getSourceElement().compileError(err);
}
}
}
}
/**
* Add a stylesheet function to the index
* @param decl The declaration wrapping an XSLFunction object
* @throws XPathException
*/
protected void indexFunction(Declaration decl) throws XPathException {
XSLFunction function = (XSLFunction)decl.getSourceElement();
StructuredQName qName = function.getObjectName();
int arity = function.getNumberOfArguments();
// see if there is already a named function with this precedence
Declaration other = getFunctionDeclaration(qName, arity);
if (other == null) {
// this is the first
putFunction(decl);
} else {
// check the precedences
int thisPrecedence = decl.getPrecedence();
int otherPrecedence = other.getPrecedence();
if (thisPrecedence == otherPrecedence) {
StyleElement f2 = other.getSourceElement();
if (decl.getSourceElement() == f2) {
function.compileError(
"Function " + qName.getDisplayName() + " is declared more than once " +
"(caused by including the containing module more than once)",
"XTSE0770");
} else {
function.compileError("Duplicate function declaration (see line " +
f2.getLineNumber() + " of " + f2.getSystemId() + ')', "XTSE0770");
}
} else if (thisPrecedence < otherPrecedence) {
//
} else {
// can't happen, but we'll play safe
putFunction(decl);
}
}
}
protected Declaration getFunctionDeclaration(StructuredQName name, int arity) {
HashMap<StructuredQName, Declaration> m = functionIndex.get(arity);
return (m == null ? null : m.get(name));
}
/**
* Get the function with a given name and arity
* @param name the name of the function
* @param arity the arity of the function, or -1 if any arity will do
* @return the requested function, or null if none can be found
*/
protected XSLFunction getFunction(StructuredQName name, int arity) {
if (arity == -1) {
// supports the single-argument function-available() function
for (Iterator<Integer> arities = functionIndex.keySet().iterator(); arities.hasNext();) {
int a = arities.next();
Declaration decl = getFunctionDeclaration(name, a);
if (decl != null) {
return (XSLFunction)decl.getSourceElement();
}
}
return null;
} else {
Declaration decl = getFunctionDeclaration(name, arity);
return (decl == null ? null : (XSLFunction)decl.getSourceElement());
}
}
protected void putFunction(Declaration decl) {
XSLFunction function = (XSLFunction)decl.getSourceElement();
StructuredQName qName = function.getObjectName();
int arity = function.getNumberOfArguments();
HashMap<StructuredQName, Declaration> m = functionIndex.get(arity);
if (m == null) {
m = new HashMap<StructuredQName, Declaration>();
functionIndex.put(arity, m);
}
m.put(qName, decl);
}
/**
* Index a global xsl:variable or xsl:param element
* @param decl The Declaration referencing the XSLVariable or XSLParam element
* @throws XPathException
*/
protected void indexVariableDeclaration(Declaration decl) throws XPathException {
XSLVariableDeclaration var = (XSLVariableDeclaration)decl.getSourceElement();
StructuredQName qName = var.getVariableQName();
if (qName != null) {
// see if there is already a global variable with this precedence
Declaration other = globalVariableIndex.get(qName);
if (other == null) {
// this is the first
globalVariableIndex.put(qName, decl);
} else {
// check the precedences
int thisPrecedence = decl.getPrecedence();
int otherPrecedence = other.getPrecedence();
if (thisPrecedence == otherPrecedence) {
StyleElement v2 = other.getSourceElement();
if (v2 == var) {
var.compileError(
"Global variable " + qName.getDisplayName() + " is declared more than once " +
"(caused by including the containing module more than once)",
"XTSE0630");
} else {
var.compileError("Duplicate global variable declaration (see line " +
v2.getLineNumber() + " of " + v2.getSystemId() + ')', "XTSE0630");
}
} else if (thisPrecedence < otherPrecedence && var != other.getSourceElement()) {
var.setRedundant();
} else if (var != other.getSourceElement()) {
((XSLVariableDeclaration)other.getSourceElement()).setRedundant();
globalVariableIndex.put(qName, decl);
}
}
}
}
/**
* Get the global variable or parameter with a given name (taking
* precedence rules into account)
* @param qName name of the global variable or parameter
* @return the variable declaration, or null if it does not exist
*/
public XSLVariableDeclaration getGlobalVariable(StructuredQName qName) {
Declaration decl = globalVariableIndex.get(qName);
return (decl == null ? null : (XSLVariableDeclaration)decl.getSourceElement());
}
/**
* Allocate a unique number to a local parameter name. This should only be called on the principal
* stylesheet module.
* @param qName the local parameter name
* @return an integer that uniquely identifies this parameter name within the stylesheet
*/
public int allocateUniqueParameterNumber(StructuredQName qName) {
if (localParameterNumbers == null) {
localParameterNumbers = new HashMap<StructuredQName, Integer>(50);
}
Integer x = localParameterNumbers.get(qName);
if (x == null) {
x = Integer.valueOf(localParameterNumbers.size());
localParameterNumbers.put(qName, x);
}
return x.intValue();
}
/**
* Add a named template to the index
* @param decl the declaration of the Template object
* @throws XPathException
*/
protected void indexNamedTemplate(Declaration decl) throws XPathException {
XSLTemplate template = (XSLTemplate)decl.getSourceElement();
StructuredQName qName = template.getTemplateName();
if (qName != null) {
// see if there is already a named template with this precedence
Declaration other = templateIndex.get(qName);
if (other == null) {
// this is the first
templateIndex.put(qName, decl);
getPreparedStylesheet().putNamedTemplate(qName, template.getCompiledTemplate());
} else {
// check the precedences
int thisPrecedence = decl.getPrecedence();
int otherPrecedence = other.getPrecedence();
if (thisPrecedence == otherPrecedence) {
StyleElement t2 = other.getSourceElement();
template.compileError("Duplicate named template (see line " +
t2.getLineNumber() + " of " + t2.getSystemId() + ')', "XTSE0660");
} else if (thisPrecedence < otherPrecedence) {
//template.setRedundantNamedTemplate();
} else {
// can't happen, but we'll play safe
//other.setRedundantNamedTemplate();
templateIndex.put(qName, decl);
getPreparedStylesheet().putNamedTemplate(qName, template.getCompiledTemplate());
}
}
}
}
/**
* Get the named template with a given name
* @param name the name of the required template
* @return the template with the given name, if there is one, or null otherwise. If there
* are several templates with the same name, the one with highest import precedence
* is returned.
*/
public XSLTemplate getNamedTemplate(StructuredQName name) {
Declaration decl = templateIndex.get(name);
return (decl == null ? null : (XSLTemplate)decl.getSourceElement());
}
/**
* Check for schema-awareness.
* Typed input nodes are recognized if and only if the stylesheet contains an import-schema declaration.
*/
private void checkForSchemaAwareness() {
// no-op
}
protected void addNamespaceAlias(Declaration node) {
namespaceAliasList.add(node);
numberOfAliases++;
}
/**
* Get the declared namespace alias for a given namespace URI code if there is one.
* If there is more than one, we get the last.
* @param uri The code of the uri used in the stylesheet.
* @return The namespace code to be used (prefix in top half, uri in bottom half): return -1
* if no alias is defined
*/
protected NamespaceBinding getNamespaceAlias(String uri) {
return namespaceAliasMap.get(uri);
}
/**
* Determine if a namespace is included in the result-prefix of a namespace-alias
* @param uri the URI
* @return true if an xsl:namespace-alias has been defined for this namespace URI
*/
protected boolean isAliasResultNamespace(String uri) {
return aliasResultUriSet.contains(uri);
}
/**
* Collect any namespace aliases
*/
private void collectNamespaceAliases() throws XPathException {
namespaceAliasMap = new HashMap(numberOfAliases);
aliasResultUriSet = new HashSet(numberOfAliases);
HashSet<String> aliasesAtThisPrecedence = new HashSet<String>();
//aliasSCodes = new short[numberOfAliases];
//aliasNCodes = new int[numberOfAliases];
//int precedenceBoundary = 0;
int currentPrecedence = -1;
// Note that we are processing the list in reverse stylesheet order,
// that is, highest precedence first.
for (int i = 0; i < numberOfAliases; i++) {
Declaration decl = namespaceAliasList.get(i);
XSLNamespaceAlias xna = (XSLNamespaceAlias)decl.getSourceElement();
String scode = xna.getStylesheetURI();
NamespaceBinding ncode = xna.getResultNamespaceBinding();
int prec = decl.getPrecedence();
// check that there isn't a conflict with another xsl:namespace-alias
// at the same precedence
if (currentPrecedence != prec) {
currentPrecedence = prec;
aliasesAtThisPrecedence.clear();
//precedenceBoundary = i;
}
if (aliasesAtThisPrecedence.contains(scode)) {
if (!namespaceAliasMap.get(scode).equals(ncode.getURI())) {
xna.compileError("More than one alias is defined for the same namespace", "XTSE0810");
}
}
if (namespaceAliasMap.get(scode) == null) {
namespaceAliasMap.put(scode, ncode);
aliasResultUriSet.add(ncode.getURI());
}
aliasesAtThisPrecedence.add(scode);
}
namespaceAliasList = null; // throw it in the garbage
}
protected boolean hasNamespaceAliases() {
return numberOfAliases > 0;
}
/**
* Compile the stylesheet to create an executable.
*/
public void compileStylesheet() throws XPathException {
try {
PreparedStylesheet pss = getPreparedStylesheet();
//Configuration config = pss.getConfiguration();
Executable exec = pss.getExecutable();
// Register template rules with the rule manager
for (int i = 0; i < topLevel.size(); i++) {
Declaration decl = topLevel.get(i);
StyleElement snode = decl.getSourceElement();
if (snode instanceof XSLTemplate) {
((XSLTemplate)snode).register(decl);
}
}
// Call compile method for each top-level object in the stylesheet
// Note, some declarations (templates) need to be compiled repeatedly if the module
// is imported repeatedly; others (variables, functions) do not
for (int i = 0; i < topLevel.size(); i++) {
Declaration decl = topLevel.get(i);
StyleElement snode = decl.getSourceElement();
if (!snode.isActionCompleted(StyleElement.ACTION_COMPILE)) {
snode.setActionCompleted(StyleElement.ACTION_COMPILE);
Expression inst = snode.compile(exec, decl);
if (inst != null) {
inst.setSourceLocator(snode);
}
}
}
// Call type-check method for each user-defined function in the stylesheet. This is no longer
// done during the optimize step, to avoid functions being inlined before they are type-checked.
// for (int i = 0; i < topLevel.size(); i++) {
// NodeInfo node = (NodeInfo) topLevel.get(i);
// if (node instanceof XSLFunction) {
// ((XSLFunction) node).typeCheckBody();
// }
// }
for (Iterator<Integer> arities = functionIndex.keySet().iterator(); arities.hasNext();) {
for (Iterator<Declaration> fi = functionIndex.get(arities.next()).values().iterator(); fi.hasNext();) {
Declaration decl = fi.next();
StyleElement node = decl.getSourceElement();
if (!node.isActionCompleted(StyleElement.ACTION_TYPECHECK)) {
node.setActionCompleted(StyleElement.ACTION_TYPECHECK);
((XSLFunction)node).typeCheckBody();
}
}
}
if (getPreparedStylesheet().getErrorCount() > 0) {
// not much point carrying on
return;
}
// Call optimize method for each top-level object in the stylesheet
// But for functions, do it only for those of highest precedence.
for (int i = 0; i < topLevel.size(); i++) {
Declaration decl = topLevel.get(i);
StyleElement node = decl.getSourceElement();
if (node instanceof StylesheetProcedure && !(node instanceof XSLFunction) &&
!node.isActionCompleted(StyleElement.ACTION_OPTIMIZE)) {
node.setActionCompleted(StyleElement.ACTION_OPTIMIZE);
((StylesheetProcedure) node).optimize(decl);
}
}
for (Iterator<Integer> arities = functionIndex.keySet().iterator(); arities.hasNext();) {
for (Iterator<Declaration> fi = functionIndex.get(arities.next()).values().iterator(); fi.hasNext();) {
Declaration decl = fi.next();
StyleElement node = decl.getSourceElement();
if (!node.isActionCompleted(StyleElement.ACTION_OPTIMIZE)) {
node.setActionCompleted(StyleElement.ACTION_OPTIMIZE);
((StylesheetProcedure) node).optimize(decl);
}
}
}
// Fix up references to the default default decimal format
if (pss.getDecimalFormatManager() != null) {
try {
pss.getDecimalFormatManager().fixupDefaultDefault();
} catch (XPathException err) {
compileError(err.getMessage(), err.getErrorCodeLocalPart());
}
}
// Finish off the lists of template rules
RuleManager ruleManager = getPreparedStylesheet().getRuleManager();
ruleManager.computeRankings();
} catch (RuntimeException err) {
// if syntax errors were reported earlier, then exceptions may occur during this phase
// due to inconsistency of data structures. We can ignore these exceptions as they
// will go away when the user corrects the stylesheet
if (getPreparedStylesheet().getErrorCount() == 0) {
// rethrow the exception
throw err;
}
}
}
/**
* Get the list of attribute-set declarations associated with a given QName.
* This is used for xsl:element, xsl:copy, xsl:attribute-set, and on literal
* result elements
*
* @param name the name of the required attribute set
* @param list a list to hold the list of XSLAttributeSet elements in the stylesheet tree.
* @return true if any declarations were found and added to the list; false if none were found
*/
protected boolean getAttributeSets(StructuredQName name, List<Declaration> list)
throws XPathException {
boolean found = false;
// search for the named attribute set, using all of them if there are several with the
// same name
for (int i = 0; i < topLevel.size(); i++) {
Declaration decl = topLevel.get(i);
if (decl.getSourceElement() instanceof XSLAttributeSet) {
XSLAttributeSet t = (XSLAttributeSet)decl.getSourceElement();
if (t.getAttributeSetName().equals(name)) {
t.incrementReferenceCount();
list.add(decl);
found = true;
}
}
}
return found;
}
/**
* Get the rules determining which nodes are to be stripped from the tree
* @return the Mode object holding the whitespace stripping rules. The stripping
* rules defined in xsl:strip-space are managed in the same way as template rules,
* hence the use of a special Mode object
*/
protected StripSpaceRules getStripperRules() {
Executable exec = getPreparedStylesheet().getExecutable();
if (exec.getStripperRules() == null) {
exec.setStripperRules(new StripSpaceRules());
}
return exec.getStripperRules();
}
/**
* Set the value of the version attribute on the xsl:stylesheet element of the
* principal stylesheet module
* @param version the value of the version attribute
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Get the value of the version attribute on the xsl:stylesheet element of the
* principal stylesheet module
* @return the value of the version attribute
*/
public String getVersion() {
return version;
}
/**
* Ensure there is enough space for local variables or parameters when evaluating the match pattern of
* template rules
* @param n the number of slots to be allocated
*/
public void allocatePatternSlots(int n) {
if (n > largestPatternStackFrame) {
largestPatternStackFrame = n;
}
}
/**
* Compile time error, specifying an error code
* @param message the error message
* @param errorCode the error code. May be null if not known or not defined
* @throws XPathException
*/
protected void compileError(String message, String errorCode) throws XPathException {
XPathException tce = new XPathException(message);
tce.setErrorCode(errorCode);
compileError(tce);
}
/**
* Report an error with diagnostic information
* @param error contains information about the error
* @throws XPathException always, after reporting the error to the ErrorListener
*/
protected void compileError(XPathException error)
throws XPathException {
error.setIsStaticError(true);
PreparedStylesheet pss = getPreparedStylesheet();
if (pss == null) {
// it is null before the stylesheet has been fully built
throw error;
} else {
pss.reportError(error);
}
}
}
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.