001: package net.sf.saxon.style;
002:
003: import net.sf.saxon.expr.*;
004: import net.sf.saxon.instruct.*;
005: import net.sf.saxon.om.AttributeCollection;
006: import net.sf.saxon.om.Axis;
007: import net.sf.saxon.om.NamespaceException;
008: import net.sf.saxon.pattern.NoNodeTest;
009: import net.sf.saxon.pattern.Pattern;
010: import net.sf.saxon.trans.Mode;
011: import net.sf.saxon.trans.RuleManager;
012: import net.sf.saxon.trans.XPathException;
013: import net.sf.saxon.type.ItemType;
014: import net.sf.saxon.type.Type;
015: import net.sf.saxon.value.EmptySequence;
016: import net.sf.saxon.value.SequenceType;
017:
018: import javax.xml.transform.TransformerException;
019: import java.math.BigDecimal;
020: import java.util.StringTokenizer;
021:
022: /**
023: * An xsl:template element in the style sheet.
024: */
025:
026: public final class XSLTemplate extends StyleElement implements
027: StylesheetProcedure {
028:
029: private String matchAtt = null;
030: private String modeAtt = null;
031: private String nameAtt = null;
032: private String priorityAtt = null;
033: private String asAtt = null;
034:
035: private int[] modeNameCodes;
036: private String diagnosticId;
037: private Pattern match;
038: private boolean prioritySpecified;
039: private double priority;
040: private SlotManager stackFrameMap;
041: private Template compiledTemplate = new Template();
042: private SequenceType requiredType = null;
043:
044: /**
045: * Determine whether this type of element is allowed to contain a template-body
046: * @return true: yes, it may contain a template-body
047: */
048:
049: public boolean mayContainSequenceConstructor() {
050: return true;
051: }
052:
053: /**
054: * Specify that xsl:param is a permitted child
055: */
056:
057: protected boolean isPermittedChild(StyleElement child) {
058: return (child instanceof XSLParam);
059: }
060:
061: /**
062: * Return the fingerprint for the name of this template. Note that this may
063: * be called before prepareAttributes has been called.
064: */
065:
066: public int getTemplateFingerprint() {
067:
068: //We use -1 to mean "not yet evaluated"
069:
070: try {
071: if (getObjectFingerprint() == -1) {
072: // allow for forwards references
073: String nameAtt = getAttributeValue(StandardNames.NAME);
074: if (nameAtt != null) {
075: setObjectNameCode(makeNameCode(nameAtt.trim()));
076: }
077: }
078: return getObjectFingerprint();
079: } catch (NamespaceException err) {
080: return -1; // the errors will be picked up later
081: } catch (XPathException err) {
082: return -1;
083: }
084: }
085:
086: /**
087: * Determine the type of item returned by this template
088: * @return the item type returned
089: */
090:
091: protected ItemType getReturnedItemType() {
092: if (requiredType == null) {
093: return getCommonChildItemType();
094: } else {
095: return requiredType.getPrimaryType();
096: }
097: }
098:
099: private int getMinImportPrecedence() {
100: return getContainingStylesheet().getMinImportPrecedence();
101: }
102:
103: public void prepareAttributes() throws XPathException {
104:
105: AttributeCollection atts = getAttributeList();
106:
107: for (int a = 0; a < atts.getLength(); a++) {
108: int nc = atts.getNameCode(a);
109: String f = getNamePool().getClarkName(nc);
110: if (f == StandardNames.MODE) {
111: modeAtt = atts.getValue(a).trim();
112: } else if (f == StandardNames.NAME) {
113: nameAtt = atts.getValue(a).trim();
114: } else if (f == StandardNames.MATCH) {
115: matchAtt = atts.getValue(a);
116: } else if (f == StandardNames.PRIORITY) {
117: priorityAtt = atts.getValue(a).trim();
118: } else if (f == StandardNames.AS) {
119: asAtt = atts.getValue(a);
120: } else {
121: checkUnknownAttribute(nc);
122: }
123: }
124: try {
125: if (modeAtt == null) {
126: modeNameCodes = new int[1];
127: modeNameCodes[0] = -1;
128: } else {
129: if (matchAtt == null) {
130: compileError(
131: "The mode attribute must be absent if the match attribute is absent",
132: "XTSE0500");
133: }
134: // mode is a space-separated list of mode names, or "#default", or "#all"
135:
136: int count = 0;
137: boolean allModes = false;
138: StringTokenizer st = new StringTokenizer(modeAtt);
139: while (st.hasMoreTokens()) {
140: st.nextToken();
141: count++;
142: }
143:
144: if (count == 0) {
145: compileError(
146: "The mode attribute must not be empty",
147: "XTSE0550");
148: }
149:
150: modeNameCodes = new int[count];
151: count = 0;
152: st = new StringTokenizer(modeAtt);
153: while (st.hasMoreTokens()) {
154: String s = st.nextToken();
155: int code;
156: if ("#default".equals(s)) {
157: code = Mode.DEFAULT_MODE;
158: } else if ("#all".equals(s)) {
159: allModes = true;
160: code = Mode.ALL_MODES;
161: } else {
162: code = makeNameCode(s);
163: }
164: for (int e = 0; e < count; e++) {
165: if (modeNameCodes[e] == code) {
166: compileError(
167: "In the list of modes, the value "
168: + s + " is duplicated",
169: "XTSE0550");
170: }
171: }
172: modeNameCodes[count++] = code;
173: }
174: if (allModes && (count > 1)) {
175: compileError(
176: "mode='#all' cannot be combined with other modes",
177: "XTSE0550");
178: }
179: }
180:
181: if (nameAtt != null) {
182: setObjectNameCode(makeNameCode(nameAtt.trim()));
183: diagnosticId = nameAtt;
184: }
185: } catch (NamespaceException err) {
186: compileError(err.getMessage(), "XTSE0280");
187: } catch (XPathException err) {
188: compileError(err.getMessage(), "XTSE0280");
189: }
190:
191: prioritySpecified = (priorityAtt != null);
192: if (prioritySpecified) {
193: if (matchAtt == null) {
194: compileError(
195: "The priority attribute must be absent if the match attribute is absent",
196: "XTSE0500");
197: }
198: try {
199: // it's got to be a valid decimal, but we want it as a double, so parse it twice
200: new BigDecimal(priorityAtt.trim());
201: priority = Double.parseDouble(priorityAtt.trim());
202: } catch (NumberFormatException err) {
203: compileError("Invalid numeric value for priority ("
204: + priority + ')', "XTSE0530");
205: }
206: }
207:
208: if (matchAtt != null) {
209: match = makePattern(matchAtt);
210: if (diagnosticId == null) {
211: diagnosticId = "match=\"" + matchAtt + '\"';
212: }
213: }
214:
215: if (match == null && nameAtt == null)
216: compileError(
217: "xsl:template must have a name or match attribute (or both)",
218: "XTSE0010");
219:
220: if (asAtt != null) {
221: requiredType = makeSequenceType(asAtt);
222: }
223:
224: }
225:
226: public void validate() throws XPathException {
227: stackFrameMap = getConfiguration().makeSlotManager();
228: checkTopLevel(null);
229:
230: // the check for duplicates is now done in the buildIndexes() method of XSLStylesheet
231: if (match != null) {
232: typeCheck("match", match);
233: if (match.getNodeTest() instanceof NoNodeTest) {
234: try {
235: getConfiguration()
236: .getErrorListener()
237: .warning(
238: new TransformerException(
239: "Match pattern cannot match any nodes",
240: this ));
241: } catch (TransformerException e) {
242: compileError(e);
243: }
244: }
245: }
246: markTailCalls();
247: }
248:
249: /**
250: * Mark tail-recursive calls on templates and functions.
251: */
252:
253: public void markTailCalls() {
254: if (requiredType == null) {
255: // don't attempt tail call optimization if the return type needs checking
256: StyleElement last = getLastChildInstruction();
257: if (last != null) {
258: last.markTailCalls();
259: }
260: }
261: }
262:
263: /**
264: * Compile: this registers the template with the rule manager, and ensures
265: * space is available for local variables
266: */
267:
268: public Expression compile(Executable exec) throws XPathException {
269:
270: Expression block = compileSequenceConstructor(exec,
271: iterateAxis(Axis.CHILD), true);
272: if (block == null) {
273: block = EmptySequence.getInstance();
274: }
275: compiledTemplate.setBody(block);
276: compiledTemplate.setStackFrameMap(stackFrameMap);
277: compiledTemplate.setExecutable(getExecutable());
278: compiledTemplate.setSystemId(getSystemId());
279: compiledTemplate.setLineNumber(getLineNumber());
280:
281: Expression exp = null;
282: try {
283: exp = block.simplify(getStaticContext());
284: } catch (XPathException e) {
285: compileError(e);
286: }
287:
288: try {
289: if (requiredType != null) {
290: RoleLocator role = new RoleLocator(
291: RoleLocator.TEMPLATE_RESULT, diagnosticId, 0,
292: null);
293: role.setSourceLocator(new ExpressionLocation(this ));
294: exp = TypeChecker.staticTypeCheck(exp, requiredType,
295: false, role, getStaticContext());
296: }
297: } catch (XPathException err) {
298: compileError(err);
299: }
300:
301: compiledTemplate.setBody(exp);
302: compiledTemplate.init(getObjectFingerprint(), getPrecedence(),
303: getMinImportPrecedence());
304:
305: if (getConfiguration().getTraceListener() != null) {
306: TraceWrapper trace = new TraceInstruction(exp, this );
307: trace.setLocationId(allocateLocationId(getSystemId(),
308: getLineNumber()));
309: trace.setParentExpression(compiledTemplate);
310: exp = trace;
311: compiledTemplate.setBody(exp);
312: }
313:
314: ItemType contextItemType = Type.ITEM_TYPE;
315: if (getObjectFingerprint() == -1) {
316: // the template can't be called by name, so the context item must match the match pattern
317: contextItemType = match.getNodeTest();
318: }
319:
320: try {
321: // We've already done the typecheck of each XPath expression, but it's worth doing again at this
322: // level because we have more information now.
323: Expression exp2 = exp.typeCheck(staticContext,
324: contextItemType);
325: exp2 = exp2.optimize(getConfiguration().getOptimizer(),
326: staticContext, contextItemType);
327: if (exp != exp2) {
328: compiledTemplate.setBody(exp2);
329: exp = exp2;
330: }
331: } catch (XPathException e) {
332: compileError(e);
333: }
334: super .allocateSlots(exp);
335: if (match != null) {
336: RuleManager mgr = getPrincipalStylesheet().getRuleManager();
337: for (int i = 0; i < modeNameCodes.length; i++) {
338: int nc = modeNameCodes[i];
339: Mode mode = mgr.getMode(nc);
340: if (prioritySpecified) {
341: mgr.setHandler(match, compiledTemplate, mode,
342: getPrecedence(), priority);
343: } else {
344: mgr.setHandler(match, compiledTemplate, mode,
345: getPrecedence());
346: }
347: }
348: }
349:
350: if (isExplaining()) {
351: System.err
352: .println("Optimized expression tree for template at line "
353: + getLineNumber()
354: + " in "
355: + getSystemId()
356: + ":");
357: exp.display(10, getNamePool(), System.err);
358: }
359:
360: return null;
361: }
362:
363: /**
364: * Get associated Procedure (for details of stack frame)
365: */
366:
367: public SlotManager getSlotManager() {
368: return stackFrameMap;
369: }
370:
371: /**
372: * Allocate space for range variables within predicates in the match pattern. The xsl:template
373: * element has no XPath expressions among its attributes, so if this method is called on this
374: * object it can only be because there are variables used in the match pattern. We work out
375: * how many slots are needed for the match pattern in each template rule, and then apply-templates
376: * can allocate a stack frame that is large enough for the most demanding match pattern in the
377: * entire stylesheet.
378: * @param exp The expression containing range variables. This will be a predicate within a match pattern,
379: * or possibly an argument to id() or key() used in a match pattern.
380: */
381:
382: public void allocateSlots(Expression exp) {
383: int highWater = ExpressionTool.allocateSlots(exp, 0, null);
384: getContainingStylesheet().allocatePatternSlots(highWater);
385: }
386:
387: /**
388: * Get the compiled template
389: */
390:
391: public Template getCompiledTemplate() {
392: return compiledTemplate;
393: }
394:
395: /**
396: * Get the type of construct. This will be a constant in
397: * class {@link net.sf.saxon.trace.Location}. This method is part of the {@link net.sf.saxon.trace.InstructionInfo} interface
398: */
399:
400: public int getConstructType() {
401: return StandardNames.XSL_TEMPLATE;
402: }
403:
404: }
405:
406: //
407: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
408: // you may not use this file except in compliance with the License. You may obtain a copy of the
409: // License at http://www.mozilla.org/MPL/
410: //
411: // Software distributed under the License is distributed on an "AS IS" basis,
412: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
413: // See the License for the specific language governing rights and limitations under the License.
414: //
415: // The Original Code is: all this file.
416: //
417: // The Initial Developer of the Original Code is Michael H. Kay.
418: //
419: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
420: //
421: // Contributor(s):
422: // Portions marked "e.g." are from Edwin Glaser (edwin@pannenleiter.de)
423: //
|