001: package net.sf.saxon.instruct;
002:
003: import net.sf.saxon.expr.*;
004: import net.sf.saxon.functions.NumberFn;
005: import net.sf.saxon.number.NumberFormatter;
006: import net.sf.saxon.number.Numberer;
007: import net.sf.saxon.number.Numberer_en;
008: import net.sf.saxon.om.*;
009: import net.sf.saxon.pattern.Pattern;
010: import net.sf.saxon.pattern.PatternSponsor;
011: import net.sf.saxon.trans.DynamicError;
012: import net.sf.saxon.trans.StaticError;
013: import net.sf.saxon.trans.XPathException;
014: import net.sf.saxon.type.AtomicType;
015: import net.sf.saxon.type.ItemType;
016: import net.sf.saxon.type.Type;
017: import net.sf.saxon.type.TypeHierarchy;
018: import net.sf.saxon.value.*;
019: import net.sf.saxon.Configuration;
020:
021: import java.io.PrintStream;
022: import java.util.*;
023:
024: /**
025: * An xsl:number element in the stylesheet. Although this is an XSLT instruction, it is compiled
026: * into an expression, evaluated using xsl:value-of to create the resulting text node.<br>
027: */
028:
029: public class NumberInstruction extends ComputedExpression {
030:
031: private static final int SINGLE = 0;
032: private static final int MULTI = 1;
033: private static final int ANY = 2;
034: private static final int SIMPLE = 3;
035:
036: private int level;
037: private Pattern count = null;
038: private Pattern from = null;
039: private Expression select = null;
040: private Expression value = null;
041: private Expression format = null;
042: private Expression groupSize = null;
043: private Expression groupSeparator = null;
044: private Expression letterValue = null;
045: private Expression ordinal = null;
046: private Expression lang = null;
047: private NumberFormatter formatter = null;
048: private Numberer numberer = null;
049: private HashMap nationalNumberers = null;
050: private boolean hasVariablesInPatterns;
051: private boolean backwardsCompatible;
052:
053: private static Numberer defaultNumberer = new Numberer_en();
054:
055: public NumberInstruction(Configuration config, Expression select,
056: int level, Pattern count, Pattern from, Expression value,
057: Expression format, Expression groupSize,
058: Expression groupSeparator, Expression letterValue,
059: Expression ordinal, Expression lang,
060: NumberFormatter formatter, Numberer numberer,
061: boolean hasVariablesInPatterns, boolean backwardsCompatible) {
062: this .select = select;
063: this .level = level;
064: this .count = count;
065: this .from = from;
066: this .value = value;
067: this .format = format;
068: this .groupSize = groupSize;
069: this .groupSeparator = groupSeparator;
070: this .letterValue = letterValue;
071: this .ordinal = ordinal;
072: this .lang = lang;
073: this .formatter = formatter;
074: this .numberer = numberer;
075: this .hasVariablesInPatterns = hasVariablesInPatterns;
076: this .backwardsCompatible = backwardsCompatible;
077:
078: final TypeHierarchy th = config.getNamePool()
079: .getTypeHierarchy();
080: if (this .value != null
081: && !this .value.getItemType(th).isAtomicType()) {
082: this .value = new Atomizer(this .value, config);
083: }
084:
085: Iterator kids = iterateSubExpressions();
086: while (kids.hasNext()) {
087: Expression child = (Expression) kids.next();
088: adoptChildExpression(child);
089: }
090:
091: // if (select != null) {
092: // adoptChildExpression(select);
093: // }
094: // if (value != null) {
095: // adoptChildExpression(value);
096: // }
097: // if (format != null) {
098: // adoptChildExpression(format);
099: // }
100: // if (groupSize != null) {
101: // adoptChildExpression(groupSize);
102: // }
103: // if (groupSeparator != null) {
104: // adoptChildExpression(groupSeparator);
105: // }
106: // if (letterValue != null) {
107: // adoptChildExpression(letterValue);
108: // }
109: // if (ordinal != null) {
110: // adoptChildExpression(ordinal);
111: // }
112: // if (lang != null) {
113: // adoptChildExpression(lang);
114: // }
115: }
116:
117: public Expression simplify(StaticContext env) throws XPathException {
118: if (select != null) {
119: select = select.simplify(env);
120: }
121: if (value != null) {
122: value = value.simplify(env);
123: }
124: if (format != null) {
125: format = format.simplify(env);
126: }
127: if (groupSize != null) {
128: groupSize = groupSize.simplify(env);
129: }
130: if (groupSeparator != null) {
131: groupSeparator = groupSeparator.simplify(env);
132: }
133: if (letterValue != null) {
134: letterValue = letterValue.simplify(env);
135: }
136: if (ordinal != null) {
137: ordinal = ordinal.simplify(env);
138: }
139: if (lang != null) {
140: lang = lang.simplify(env);
141: }
142: if (count != null) {
143: count = count.simplify(env);
144: }
145: if (from != null) {
146: from = from.simplify(env);
147: }
148: return this ;
149: }
150:
151: /**
152: * Perform static analysis of an expression and its subexpressions.
153: *
154: * <p>This checks statically that the operands of the expression have
155: * the correct type; if necessary it generates code to do run-time type checking or type
156: * conversion. A static type error is reported only if execution cannot possibly succeed, that
157: * is, if a run-time type error is inevitable. The call may return a modified form of the expression.</p>
158: *
159: * <p>This method is called after all references to functions and variables have been resolved
160: * to the declaration of the function or variable. However, the types of such functions and
161: * variables will only be accurately known if they have been explicitly declared.</p>
162: *
163: * @param env the static context of the expression
164: * @exception net.sf.saxon.trans.StaticError if an error is discovered during this phase
165: * (typically a type error)
166: * @return the original expression, rewritten to perform necessary
167: * run-time type checks, and to perform other type-related
168: * optimizations
169: */
170:
171: public Expression typeCheck(StaticContext env,
172: ItemType contextItemType) throws XPathException {
173: if (select != null) {
174: select = select.typeCheck(env, contextItemType);
175: } else {
176: if (value == null) {
177: // we are numbering the context node
178: if (contextItemType instanceof AtomicType) {
179: StaticError err = new StaticError(
180: "xsl:number requires the context item to be a node, but it is an atomic value");
181: err.setIsTypeError(true);
182: err.setErrorCode("XTTE0990");
183: }
184: }
185: }
186: if (value != null) {
187: value = value.typeCheck(env, contextItemType);
188: }
189: if (format != null) {
190: format = format.typeCheck(env, contextItemType);
191: }
192: if (groupSize != null) {
193: groupSize = groupSize.typeCheck(env, contextItemType);
194: }
195: if (groupSeparator != null) {
196: groupSeparator = groupSeparator.typeCheck(env,
197: contextItemType);
198: }
199: if (letterValue != null) {
200: letterValue = letterValue.typeCheck(env, contextItemType);
201: }
202: if (ordinal != null) {
203: ordinal = ordinal.typeCheck(env, contextItemType);
204: }
205: if (lang != null) {
206: lang = lang.typeCheck(env, contextItemType);
207: }
208: if (count != null) {
209: count = count.analyze(env, contextItemType);
210: }
211: if (from != null) {
212: from = from.analyze(env, contextItemType);
213: }
214: return this ;
215: }
216:
217: /**
218: * Perform optimisation of an expression and its subexpressions.
219: * <p/>
220: * <p>This method is called after all references to functions and variables have been resolved
221: * to the declaration of the function or variable, and after all type checking has been done.</p>
222: *
223: * @param opt the optimizer in use. This provides access to supporting functions; it also allows
224: * different optimization strategies to be used in different circumstances.
225: * @param env the static context of the expression
226: * @param contextItemType the static type of "." at the point where this expression is invoked.
227: * The parameter is set to null if it is known statically that the context item will be undefined.
228: * If the type of the context item is not known statically, the argument is set to
229: * {@link net.sf.saxon.type.Type#ITEM_TYPE}
230: * @return the original expression, rewritten if appropriate to optimize execution
231: * @throws net.sf.saxon.trans.StaticError if an error is discovered during this phase
232: * (typically a type error)
233: */
234:
235: public Expression optimize(Optimizer opt, StaticContext env,
236: ItemType contextItemType) throws XPathException {
237: if (select != null) {
238: select = select.optimize(opt, env, contextItemType);
239: }
240: if (value != null) {
241: value = value.optimize(opt, env, contextItemType);
242: }
243: if (format != null) {
244: format = format.optimize(opt, env, contextItemType);
245: }
246: if (groupSize != null) {
247: groupSize = groupSize.optimize(opt, env, contextItemType);
248: }
249: if (groupSeparator != null) {
250: groupSeparator = groupSeparator.optimize(opt, env,
251: contextItemType);
252: }
253: if (letterValue != null) {
254: letterValue = letterValue.optimize(opt, env,
255: contextItemType);
256: }
257: if (ordinal != null) {
258: ordinal = ordinal.optimize(opt, env, contextItemType);
259: }
260: if (lang != null) {
261: lang = lang.optimize(opt, env, contextItemType);
262: }
263: // if (count != null) {
264: // count = count.analyze(env, contextItemType);
265: // }
266: // if (from != null) {
267: // from = from.analyze(env, contextItemType);
268: // }
269: return this ;
270: }
271:
272: /**
273: * Get the immediate sub-expressions of this expression. Default implementation
274: * returns a zero-length array, appropriate for an expression that has no
275: * sub-expressions.
276: * @return an iterator containing the sub-expressions of this expression
277: */
278:
279: public Iterator iterateSubExpressions() {
280: List sub = new ArrayList(9);
281: if (select != null) {
282: sub.add(select);
283: }
284: if (value != null) {
285: sub.add(value);
286: }
287: if (format != null) {
288: sub.add(format);
289: }
290: if (groupSize != null) {
291: sub.add(groupSize);
292: }
293: if (groupSeparator != null) {
294: sub.add(groupSeparator);
295: }
296: if (letterValue != null) {
297: sub.add(letterValue);
298: }
299: if (ordinal != null) {
300: sub.add(ordinal);
301: }
302: if (lang != null) {
303: sub.add(lang);
304: }
305: if (count != null) {
306: sub.add(new PatternSponsor(count));
307: }
308: if (from != null) {
309: sub.add(new PatternSponsor(from));
310: }
311: return sub.iterator();
312: }
313:
314: /**
315: * Determine the intrinsic dependencies of an expression, that is, those which are not derived
316: * from the dependencies of its subexpressions. For example, position() has an intrinsic dependency
317: * on the context position, while (position()+1) does not. The default implementation
318: * of the method returns 0, indicating "no dependencies".
319: *
320: * @return a set of bit-significant flags identifying the "intrinsic"
321: * dependencies. The flags are documented in class net.sf.saxon.value.StaticProperty
322: */
323:
324: public int getIntrinsicDependencies() {
325: return (select == null ? StaticProperty.DEPENDS_ON_CONTEXT_ITEM
326: : 0);
327: }
328:
329: public ItemType getItemType(TypeHierarchy th) {
330: return Type.STRING_TYPE;
331: }
332:
333: public int computeCardinality() {
334: return StaticProperty.EXACTLY_ONE;
335: }
336:
337: /**
338: * Offer promotion for this subexpression. The offer will be accepted if the subexpression
339: * is not dependent on the factors (e.g. the context item) identified in the PromotionOffer.
340: * By default the offer is not accepted - this is appropriate in the case of simple expressions
341: * such as constant values and variable references where promotion would give no performance
342: * advantage. This method is always called at compile time.
343: *
344: * @param offer details of the offer, for example the offer to move
345: * expressions that don't depend on the context to an outer level in
346: * the containing expression
347: * @return if the offer is not accepted, return this expression unchanged.
348: * Otherwise return the result of rewriting the expression to promote
349: * this subexpression
350: * @throws net.sf.saxon.trans.XPathException
351: * if any error is detected
352: */
353:
354: public Expression promote(PromotionOffer offer)
355: throws XPathException {
356: Expression exp = offer.accept(this );
357: if (exp != null) {
358: return exp;
359: } else {
360: if (select != null) {
361: select = doPromotion(select, offer);
362: }
363: if (value != null) {
364: value = doPromotion(value, offer);
365: }
366: if (format != null) {
367: format = doPromotion(format, offer);
368: }
369: if (groupSize != null) {
370: groupSize = doPromotion(groupSize, offer);
371: }
372: if (groupSeparator != null) {
373: groupSeparator = doPromotion(groupSeparator, offer);
374: }
375: if (letterValue != null) {
376: letterValue = doPromotion(letterValue, offer);
377: }
378: if (ordinal != null) {
379: ordinal = doPromotion(ordinal, offer);
380: }
381: if (lang != null) {
382: lang = doPromotion(lang, offer);
383: }
384: if (count != null) {
385: count.promote(offer);
386: }
387: if (from != null) {
388: from.promote(offer);
389: }
390: return this ;
391: }
392: }
393:
394: public Item evaluateItem(XPathContext context)
395: throws XPathException {
396: long value = -1;
397: List vec = null; // a list whose items may be of type either Long or
398: // BigInteger or the string to be output (e.g. "NaN")
399:
400: if (this .value != null) {
401:
402: SequenceIterator iter = this .value.iterate(context);
403: vec = new ArrayList(4);
404: while (true) {
405: AtomicValue val = (AtomicValue) iter.next();
406: if (val == null) {
407: break;
408: }
409: try {
410: NumericValue num;
411: if (val instanceof NumericValue) {
412: num = (NumericValue) val;
413: } else {
414: num = NumberFn.convert(val);
415: }
416: if (num.isNaN()) {
417: throw new DynamicError("NaN"); // thrown to be caught
418: }
419: num = num.round();
420: if (num.compareTo(IntegerValue.MAX_LONG) > 0) {
421: vec
422: .add(((BigIntegerValue) num.convert(
423: Type.INTEGER, context))
424: .getBigInteger());
425: // DynamicError e = new DynamicError("A number is too large to be formatted");
426: // e.setXPathContext(context);
427: // e.setErrorCode("SAXON:0000");
428: // throw e;
429: } else {
430: if (num.compareTo(IntegerValue.ZERO) < 0) {
431: DynamicError e = new DynamicError(
432: "The numbers to be formatted must not be negative");
433: // e.setXPathContext(context);
434: // e.setErrorCode("XT0980");
435: throw e;
436: }
437: long i = ((NumericValue) num.convert(
438: Type.INTEGER, context)).longValue();
439: // if (i < 0) {
440: // DynamicError e = new DynamicError("The numbers to be formatted must not be negative");
441: // e.setXPathContext(context);
442: // e.setErrorCode("XT0980");
443: // throw e;
444: // }
445: vec.add(new Long(i));
446: }
447: } catch (DynamicError err) {
448: if (backwardsCompatible) {
449: vec.add("NaN");
450: } else {
451: vec.add(val.getStringValue());
452: DynamicError e = new DynamicError(
453: "Cannot convert supplied value to an integer. "
454: + err.getMessage());
455: e.setErrorCode("XTDE0980");
456: e.setXPathContext(context);
457: throw e;
458: }
459: }
460: }
461: if (backwardsCompatible && vec.size() == 0) {
462: vec.add("NaN");
463: }
464: } else {
465: NodeInfo source;
466: if (select != null) {
467: source = (NodeInfo) select.evaluateItem(context);
468: } else {
469: Item item = context.getContextItem();
470: if (!(item instanceof NodeInfo)) {
471: DynamicError err = new DynamicError(
472: "context item for xsl:number must be a node");
473: err.setErrorCode("XTTE0990");
474: err.setIsTypeError(true);
475: err.setXPathContext(context);
476: throw err;
477: //recoverableError(err, context);
478: //return null; // error recovery action is to output nothing
479: }
480: source = (NodeInfo) item;
481: }
482:
483: if (level == SIMPLE) {
484: value = Navigator.getNumberSimple(source, context);
485: } else if (level == SINGLE) {
486: value = Navigator.getNumberSingle(source, count, from,
487: context);
488: if (value == 0) {
489: vec = Collections.EMPTY_LIST; // an empty list
490: }
491: } else if (level == ANY) {
492: value = Navigator.getNumberAny(this , source, count,
493: from, context, hasVariablesInPatterns);
494: if (value == 0) {
495: vec = Collections.EMPTY_LIST; // an empty list
496: }
497: } else if (level == MULTI) {
498: vec = Navigator.getNumberMulti(source, count, from,
499: context);
500: }
501: }
502:
503: int gpsize = 0;
504: String gpseparator = "";
505: String letterVal;
506: String ordinalVal = null;
507:
508: if (groupSize != null) {
509: String g = groupSize.evaluateAsString(context);
510: try {
511: gpsize = Integer.parseInt(g);
512: } catch (NumberFormatException err) {
513: DynamicError e = new DynamicError(
514: "grouping-size must be numeric");
515: e.setXPathContext(context);
516: e.setErrorCode("XTDE0030");
517: throw e;
518: }
519: }
520:
521: if (groupSeparator != null) {
522: gpseparator = groupSeparator.evaluateAsString(context);
523: }
524:
525: if (ordinal != null) {
526: ordinalVal = ordinal.evaluateAsString(context);
527: }
528:
529: // fast path for the simple case
530:
531: if (vec == null && format == null && gpsize == 0
532: && lang == null) {
533: return new StringValue("" + value);
534: }
535:
536: // Use the numberer decided at compile time if possible; otherwise try to get it from
537: // a table of numberers indexed by language; if not there, load the relevant class and
538: // add it to the table.
539: Numberer numb = numberer;
540: if (numb == null) {
541: String language = lang.evaluateAsString(context);
542: if (nationalNumberers == null) {
543: nationalNumberers = new HashMap(4);
544: }
545: numb = (Numberer) nationalNumberers.get(language);
546: if (numb == null) {
547: numb = makeNumberer(language, null, context);
548: nationalNumberers.put(language, numb);
549: }
550: }
551:
552: if (letterValue == null) {
553: letterVal = "";
554: } else {
555: letterVal = letterValue.evaluateAsString(context);
556: if (!("alphabetic".equals(letterVal) || "traditional"
557: .equals(letterVal))) {
558: DynamicError e = new DynamicError(
559: "letter-value must be \"traditional\" or \"alphabetic\"");
560: e.setXPathContext(context);
561: e.setErrorCode("XTDE0030");
562: throw e;
563: }
564: }
565:
566: if (vec == null) {
567: vec = new ArrayList(1);
568: vec.add(new Long(value));
569: }
570:
571: NumberFormatter nf;
572: if (formatter == null) { // format not known until run-time
573: nf = new NumberFormatter();
574: nf.prepare(format.evaluateAsString(context));
575: } else {
576: nf = formatter;
577: }
578:
579: CharSequence s = nf.format(vec, gpsize, gpseparator, letterVal,
580: ordinalVal, numb);
581: return new StringValue(s);
582: }
583:
584: /**
585: * Load a Numberer class for a given language and check it is OK.
586: * @param language the language for which a Numberer is required
587: * @return a suitable numberer. If no specific numberer is available
588: * for the language, the default (English) numberer is used.
589: */
590:
591: public static Numberer makeNumberer(String language,
592: String country, XPathContext context) {
593:
594: Numberer numberer;
595: if ("en".equals(language)) {
596: numberer = defaultNumberer;
597: } else {
598: String langClassName = "net.sf.saxon.number.Numberer_";
599: for (int i = 0; i < language.length(); i++) {
600: if (Character.isLetter(language.charAt(i))) {
601: langClassName += language.charAt(i);
602: }
603: }
604: try {
605: if (context == null) {
606: Object x = Class.forName(langClassName)
607: .newInstance();
608: numberer = (Numberer) x;
609: } else {
610: numberer = (Numberer) (context.getConfiguration()
611: .getInstance(langClassName, null));
612: }
613: } catch (XPathException err) {
614: numberer = defaultNumberer;
615: } catch (ClassNotFoundException err) {
616: numberer = defaultNumberer;
617: } catch (InstantiationException err) {
618: numberer = defaultNumberer;
619: } catch (IllegalAccessException err) {
620: numberer = defaultNumberer;
621: }
622: }
623: numberer.setCountry(country);
624:
625: return numberer;
626: }
627:
628: /**
629: * Diagnostic print of expression structure. The expression is written to the System.err
630: * output stream
631: *
632: * @param level indentation level for this expression
633: * @param out
634: */
635:
636: public void display(int level, NamePool pool, PrintStream out) {
637: out.println(ExpressionTool.indent(level) + "xsl:number");
638: }
639: }
640:
641: //
642: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
643: // you may not use this file except in compliance with the License. You may obtain a copy of the
644: // License at http://www.mozilla.org/MPL/
645: //
646: // Software distributed under the License is distributed on an "AS IS" basis,
647: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
648: // See the License for the specific language governing rights and limitations under the License.
649: //
650: // The Original Code is: all this file.
651: //
652: // The Initial Developer of the Original Code is Michael H. Kay.
653: //
654: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
655: //
656: // Contributor(s): none.
657: //
|