package client.net.sf.saxon.ce.style; import com.google.gwt.logging.client.LogConfiguration; import client.net.sf.saxon.ce.LogController; import client.net.sf.saxon.ce.expr.*; import client.net.sf.saxon.ce.expr.instruct.*; import client.net.sf.saxon.ce.om.*; import client.net.sf.saxon.ce.tree.iter.AxisIterator; import client.net.sf.saxon.ce.pattern.NodeKindTest; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.type.ItemType; import client.net.sf.saxon.ce.type.Type; import client.net.sf.saxon.ce.value.Cardinality; import client.net.sf.saxon.ce.value.SequenceType; import client.net.sf.saxon.ce.value.StringValue; import client.net.sf.saxon.ce.value.Whitespace; import client.net.sf.saxon.ce.lib.NamespaceConstant; /** * This class defines common behaviour across xsl:variable, xsl:param, and xsl:with-param */ public abstract class XSLGeneralVariable extends StyleElement { protected Expression select = null; protected SequenceType requiredType = null; protected String constantText = null; protected boolean global; protected SlotManager slotManager = null; // used only for global variable declarations protected boolean redundant = false; protected boolean requiredParam = false; protected boolean implicitlyRequiredParam = false; protected boolean tunnel = false; protected GeneralVariable compiledVariable = null; private boolean textonly; /** * Determine the type of item returned by this instruction (only relevant if * it is an instruction). * @return the item type returned. This is null for a variable: we are not * interested in the type of the variable, but in what the xsl:variable constributes * to the result of the sequence constructor it is part of. */ protected ItemType getReturnedItemType() { return null; } /** * Determine whether this type of element is allowed to contain a template-body * @return true: yes, it may contain a template-body */ public boolean mayContainSequenceConstructor() { return true; } protected boolean allowsAsAttribute() { return true; } protected boolean allowsTunnelAttribute() { return false; } protected boolean allowsValue() { return true; } protected boolean allowsRequired() { return false; } /** * Test whether this is a tunnel parameter (tunnel="yes") * @return true if this is a tunnel parameter */ public boolean isTunnelParam() { return tunnel; } /** * Test whether this is a required parameter (required="yes") * @return true if this is a required parameter */ public boolean isRequiredParam() { return requiredParam; } /** * Test whether this is a global variable or parameter * @return true if this is global */ public boolean isGlobal() { return isTopLevel(); // might be called before the "global" field is initialized } /** * Get the display name of the variable. * @return the lexical QName */ public String getVariableDisplayName() { return getAttributeValue("", StandardNames.NAME); } /** * Mark this global variable as redundant. This is done before prepareAttributes is called. */ public void setRedundant() { redundant = true; } /** * Get the QName of the variable * @return the name as a structured QName, or a dummy name if the variable has no name attribute * or has an invalid name attribute */ public StructuredQName getVariableQName() { // if an expression has a forwards reference to this variable, getVariableQName() can be // called before prepareAttributes() is called. We need to allow for this. But we'll // deal with any errors when we come round to processing this attribute, to avoid // duplicate error messages if (getObjectName() == null) { String nameAttribute = getAttributeValue("", StandardNames.NAME); if (nameAttribute == null) { return new StructuredQName("saxon", NamespaceConstant.SAXON, "error-variable-name"); } try { setObjectName(makeQName(nameAttribute)); } catch (NamespaceException err) { setObjectName(new StructuredQName("saxon", NamespaceConstant.SAXON, "error-variable-name")); } catch (XPathException err) { setObjectName(new StructuredQName("saxon", NamespaceConstant.SAXON, "error-variable-name")); } } return getObjectName(); } public void prepareAttributes() throws XPathException { getVariableQName(); AttributeCollection atts = getAttributeList(); String selectAtt = null; String nameAtt = null; String asAtt = null; String requiredAtt = null; String tunnelAtt = null; for (int a=0; a<atts.getLength(); a++) { int nc = atts.getNameCode(a); String f = getNamePool().getClarkName(nc); if (f.equals(StandardNames.NAME)) { nameAtt = Whitespace.trim(atts.getValue(a)) ; } else if (f.equals(StandardNames.SELECT)) { selectAtt = atts.getValue(a); } else if (f.equals(StandardNames.AS) && allowsAsAttribute()) { asAtt = atts.getValue(a); } else if (f.equals(StandardNames.REQUIRED) && allowsRequired()) { requiredAtt = Whitespace.trim(atts.getValue(a)) ; } else if (f.equals(StandardNames.TUNNEL) && allowsTunnelAttribute()) { tunnelAtt = Whitespace.trim(atts.getValue(a)) ; } else { checkUnknownAttribute(nc); } } if (nameAtt==null) { reportAbsence("name"); } else { // the name might have already been read, but errors weren't reported try { setObjectName(makeQName(nameAtt)); } catch (NamespaceException e) { compileError("Prefix in variable name has not been declared: " + nameAtt, "XTSE0280"); } catch (XPathException e) { String expl = (nameAtt.startsWith("$") ? " (must not start with '$')" : ""); compileError("Variable name is not a valid QName: " + nameAtt + expl, "XTSE0020"); } } if (selectAtt!=null) { if (!allowsValue()) { compileError("Function parameters cannot have a default value", "XTSE0760"); } select = makeExpression(selectAtt); } if (requiredAtt!=null) { if (requiredAtt.equals("yes")) { requiredParam = true; } else if (requiredAtt.equals("no")) { requiredParam = false; } else { compileError("The attribute 'required' must be set to 'yes' or 'no'", "XTSE0020"); } } if (tunnelAtt!=null) { if (tunnelAtt.equals("yes")) { tunnel = true; if (this instanceof XSLParam && !(getParent() instanceof XSLTemplate)) { compileError("For attribute 'tunnel' within an " + getParent().getDisplayName() + " parameter, the only permitted value is 'no'", "XTSE0020"); } } else if (tunnelAtt.equals("no")) { tunnel = false; } else { compileError("The attribute 'tunnel' must be set to 'yes' or 'no'", "XTSE0020"); } } if (asAtt!=null) { requiredType = makeSequenceType(asAtt); } } public void validate(Declaration decl) throws XPathException { global = isTopLevel(); if (global) { slotManager = new SlotManager(); } if (select!=null && hasChildNodes()) { compileError("An " + getDisplayName() + " element with a select attribute must be empty", "XTSE0620"); } if (hasChildNodes() && !allowsValue()) { compileError("Function parameters cannot have a default value", "XTSE0760"); } } /** * Hook to allow additional validation of a parent element immediately after its * children have been validated. */ public void postValidate() throws XPathException { checkAgainstRequiredType(requiredType); if (select==null && allowsValue()) { textonly = true; AxisIterator kids = iterateAxis(Axis.CHILD); NodeInfo first = (NodeInfo)kids.next(); if (first == null) { if (requiredType == null) { select = new StringLiteral(StringValue.EMPTY_STRING); } else { if (this instanceof XSLParam) { if (!requiredParam) { if (Cardinality.allowsZero(requiredType.getCardinality())) { select = Literal.makeEmptySequence(); } else { // The implicit default value () is not valid for the required type, so // it is treated as if there is no default implicitlyRequiredParam = true; } } } else { if (Cardinality.allowsZero(requiredType.getCardinality())) { select = Literal.makeEmptySequence(); } else { compileError("The implicit value () is not valid for the declared type", "XTTE0570"); } } } } else { if (kids.next() == null) { // there is exactly one child node if (first.getNodeKind() == Type.TEXT) { // it is a text node: optimize for this case constantText = first.getStringValue(); } } // Determine if the temporary tree can only contain text nodes textonly = (getCommonChildItemType() == NodeKindTest.TEXT); } } select = typeCheck(select); } /** * Check the supplied select expression against the required type. * @param required The type required by the variable declaration, or in the case * of xsl:with-param, the signature of the called template */ public void checkAgainstRequiredType(SequenceType required) throws XPathException { try { if (required!=null) { // check that the expression is consistent with the required type if (select != null) { int category = RoleLocator.VARIABLE; String errorCode = "XTTE0570"; if (this instanceof XSLParam) { category = RoleLocator.PARAM; errorCode = "XTTE0600"; } else if (this instanceof XSLWithParam) { category = RoleLocator.PARAM; errorCode = "XTTE0590"; } RoleLocator role = new RoleLocator(category, getVariableDisplayName(), 0); //role.setSourceLocator(new ExpressionLocation(this)); role.setErrorCode(errorCode); select = TypeChecker.staticTypeCheck(select, required, false, role, makeExpressionVisitor()); } else { // do the check later } } } catch (XPathException err) { err.setLocator(this); // because the expression wasn't yet linked into the module compileError(err); select = new ErrorExpression(err); } } /** * Initialize - common code called from the compile() method of all subclasses * @param exec the executable * @param decl * @param var the representation of the variable declaration in the compiled executable */ protected void initializeInstruction(Executable exec, Declaration decl, GeneralVariable var) throws XPathException { var.init(select, getVariableQName()); var.setRequiredParam(requiredParam); var.setImplicitlyRequiredParam(implicitlyRequiredParam); var.setRequiredType(requiredType); var.setTunnel(tunnel); // handle the "temporary tree" case by creating a Document sub-instruction // to construct and return a document node. if (hasChildNodes()) { if (requiredType==null) { DocumentInstr doc = new DocumentInstr(textonly, constantText, getBaseURI()); var.adoptChildExpression(doc); Expression b = compileSequenceConstructor(exec, decl, iterateAxis(Axis.CHILD)); if (b == null) { b = Literal.makeEmptySequence(); } doc.setContentExpression(b); select = doc; var.setSelectExpression(doc); } else { select = compileSequenceConstructor(exec, decl, iterateAxis(Axis.CHILD)); var.adoptChildExpression(select); if (select == null) { select = Literal.makeEmptySequence(); } try { if (requiredType != null) { var.setContainer(this); //temporarily select.setContainer(this); RoleLocator role = new RoleLocator(RoleLocator.VARIABLE, getVariableDisplayName(), 0); role.setErrorCode("XTTE0570"); //role.setSourceLocator(new ExpressionLocation(this)); select = makeExpressionVisitor().simplify(select); select = TypeChecker.staticTypeCheck(select, requiredType, false, role, makeExpressionVisitor()); } } catch (XPathException err) { err.setLocator(this); compileError(err); select = new ErrorExpression(err); } var.setSelectExpression(select); } } if (global) { final GlobalVariable gvar = (GlobalVariable)var; var.setContainer(gvar); Expression exp2 = select; if (exp2 != null) { try { ExpressionVisitor visitor = makeExpressionVisitor(); exp2.setContainer(gvar); exp2 = visitor.typeCheck(visitor.simplify(select), Type.NODE_TYPE); //exp2 = exp2.optimize(visitor, Type.NODE_TYPE); } catch (XPathException err) { compileError(err); } if (LogConfiguration.loggingIsEnabled() && LogController.traceIsEnabled()) { exp2 = makeTraceInstruction(this, exp2); //exp2.AddTraceProperty("select", selectAtt); } } setReferenceCount(gvar); if (exp2 != select) { gvar.setSelectExpression(exp2); } } } protected void setReferenceCount(GeneralVariable var) { // overridden in subclass } /** * Get the type of construct. This will be a constant in * class {@link saxonce.trace.Location}. This method is part of the * {@link saxon.trace.InstructionInfo} interface */ public int getConstructType() { return StandardNames.XSL_VARIABLE; } } // 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.