• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: ElemNumber.java 468643 2006-10-28 06:56:03Z minchau $
20  */
21 package org.apache.xalan.templates;
22 
23 import java.text.DecimalFormat;
24 import java.text.DecimalFormatSymbols;
25 import java.text.NumberFormat;
26 import java.util.Locale;
27 import java.util.NoSuchElementException;
28 
29 import javax.xml.transform.TransformerException;
30 
31 import org.apache.xalan.res.XSLTErrorResources;
32 import org.apache.xalan.transformer.CountersTable;
33 import org.apache.xalan.transformer.DecimalToRoman;
34 import org.apache.xalan.transformer.TransformerImpl;
35 import org.apache.xml.dtm.DTM;
36 import org.apache.xml.utils.FastStringBuffer;
37 import org.apache.xml.utils.NodeVector;
38 import org.apache.xml.utils.PrefixResolver;
39 import org.apache.xml.utils.StringBufferPool;
40 import org.apache.xml.utils.res.XResourceBundle;
41 import org.apache.xml.utils.res.CharArrayWrapper;
42 import org.apache.xml.utils.res.IntArrayWrapper;
43 import org.apache.xml.utils.res.LongArrayWrapper;
44 import org.apache.xml.utils.res.StringArrayWrapper;
45 import org.apache.xpath.NodeSetDTM;
46 import org.apache.xpath.XPath;
47 import org.apache.xpath.XPathContext;
48 import org.apache.xpath.objects.XObject;
49 
50 import org.w3c.dom.Node;
51 
52 import org.xml.sax.SAXException;
53 
54 // import org.apache.xalan.dtm.*;
55 
56 /**
57  * Implement xsl:number.
58  * <pre>
59  * <!ELEMENT xsl:number EMPTY>
60  * <!ATTLIST xsl:number
61  *    level (single|multiple|any) "single"
62  *    count %pattern; #IMPLIED
63  *    from %pattern; #IMPLIED
64  *    value %expr; #IMPLIED
65  *    format %avt; '1'
66  *    lang %avt; #IMPLIED
67  *    letter-value %avt; #IMPLIED
68  *    grouping-separator %avt; #IMPLIED
69  *    grouping-size %avt; #IMPLIED
70  * >
71  * </pre>
72  * @see <a href="http://www.w3.org/TR/xslt#number">number in XSLT Specification</a>
73  * @xsl.usage advanced
74  */
75 public class ElemNumber extends ElemTemplateElement
76 {
77     static final long serialVersionUID = 8118472298274407610L;
78 
79     /**
80      * Chars for converting integers into alpha counts.
81      * @see TransformerImpl#int2alphaCount
82      */
83     private CharArrayWrapper m_alphaCountTable = null;
84 
85     private class MyPrefixResolver implements PrefixResolver {
86 
87         DTM dtm;
88         int handle;
89         boolean handleNullPrefix;
90 
91 		/**
92 		 * Constructor for MyPrefixResolver.
93 		 * @param xpathExpressionContext
94 		 */
MyPrefixResolver(Node xpathExpressionContext, DTM dtm, int handle, boolean handleNullPrefix)95 		public MyPrefixResolver(Node xpathExpressionContext, DTM dtm, int handle, boolean handleNullPrefix) {
96             this.dtm = dtm;
97             this.handle = handle;
98             this.handleNullPrefix = handleNullPrefix;
99 		}
100 
101     	/**
102 		 * @see PrefixResolver#getNamespaceForPrefix(String, Node)
103 		 */
getNamespaceForPrefix(String prefix)104 		public String getNamespaceForPrefix(String prefix) {
105             return dtm.getNamespaceURI(handle);
106 		}
107 
108         /**
109          * @see PrefixResolver#getNamespaceForPrefix(String, Node)
110          * this shouldn't get called.
111          */
getNamespaceForPrefix(String prefix, Node context)112         public String getNamespaceForPrefix(String prefix, Node context) {
113             return getNamespaceForPrefix(prefix);
114         }
115 
116 		/**
117 		 * @see PrefixResolver#getBaseIdentifier()
118 		 */
getBaseIdentifier()119 		public String getBaseIdentifier() {
120 			return ElemNumber.this.getBaseIdentifier();
121 		}
122 
123 		/**
124 		 * @see PrefixResolver#handlesNullPrefixes()
125 		 */
handlesNullPrefixes()126 		public boolean handlesNullPrefixes() {
127 			return handleNullPrefix;
128 		}
129 
130 }
131 
132   /**
133    * Only nodes are counted that match this pattern.
134    * @serial
135    */
136   private XPath m_countMatchPattern = null;
137 
138   /**
139    * Set the "count" attribute.
140    * The count attribute is a pattern that specifies what nodes
141    * should be counted at those levels. If count attribute is not
142    * specified, then it defaults to the pattern that matches any
143    * node with the same node type as the current node and, if the
144    * current node has an expanded-name, with the same expanded-name
145    * as the current node.
146    *
147    * @param v Value to set for "count" attribute.
148    */
setCount(XPath v)149   public void setCount(XPath v)
150   {
151     m_countMatchPattern = v;
152   }
153 
154   /**
155    * Get the "count" attribute.
156    * The count attribute is a pattern that specifies what nodes
157    * should be counted at those levels. If count attribute is not
158    * specified, then it defaults to the pattern that matches any
159    * node with the same node type as the current node and, if the
160    * current node has an expanded-name, with the same expanded-name
161    * as the current node.
162    *
163    * @return Value of "count" attribute.
164    */
getCount()165   public XPath getCount()
166   {
167     return m_countMatchPattern;
168   }
169 
170   /**
171    * Specifies where to count from.
172    * For level="single" or level="multiple":
173    * Only ancestors that are searched are
174    * those that are descendants of the nearest ancestor that matches
175    * the from pattern.
176    * For level="any:
177    * Only nodes after the first node before the
178    * current node that match the from pattern are considered.
179    * @serial
180    */
181   private XPath m_fromMatchPattern = null;
182 
183   /**
184    * Set the "from" attribute. Specifies where to count from.
185    * For level="single" or level="multiple":
186    * Only ancestors that are searched are
187    * those that are descendants of the nearest ancestor that matches
188    * the from pattern.
189    * For level="any:
190    * Only nodes after the first node before the
191    * current node that match the from pattern are considered.
192    *
193    * @param v Value to set for "from" attribute.
194    */
setFrom(XPath v)195   public void setFrom(XPath v)
196   {
197     m_fromMatchPattern = v;
198   }
199 
200   /**
201    * Get the "from" attribute.
202    * For level="single" or level="multiple":
203    * Only ancestors that are searched are
204    * those that are descendants of the nearest ancestor that matches
205    * the from pattern.
206    * For level="any:
207    * Only nodes after the first node before the
208    * current node that match the from pattern are considered.
209    *
210    * @return Value of "from" attribute.
211    */
getFrom()212   public XPath getFrom()
213   {
214     return m_fromMatchPattern;
215   }
216 
217   /**
218    * When level="single", it goes up to the first node in the ancestor-or-self axis
219    * that matches the count pattern, and constructs a list of length one containing
220    * one plus the number of preceding siblings of that ancestor that match the count
221    * pattern. If there is no such ancestor, it constructs an empty list. If the from
222    * attribute is specified, then the only ancestors that are searched are those
223    * that are descendants of the nearest ancestor that matches the from pattern.
224    * Preceding siblings has the same meaning here as with the preceding-sibling axis.
225    *
226    * When level="multiple", it constructs a list of all ancestors of the current node
227    * in document order followed by the element itself; it then selects from the list
228    * those nodes that match the count pattern; it then maps each node in the list to
229    * one plus the number of preceding siblings of that node that match the count pattern.
230    * If the from attribute is specified, then the only ancestors that are searched are
231    * those that are descendants of the nearest ancestor that matches the from pattern.
232    * Preceding siblings has the same meaning here as with the preceding-sibling axis.
233    *
234    * When level="any", it constructs a list of length one containing the number of
235    * nodes that match the count pattern and belong to the set containing the current
236    * node and all nodes at any level of the document that are before the current node
237    * in document order, excluding any namespace and attribute nodes (in other words
238    * the union of the members of the preceding and ancestor-or-self axes). If the
239    * from attribute is specified, then only nodes after the first node before the
240    * current node that match the from pattern are considered.
241    * @serial
242    */
243   private int m_level = Constants.NUMBERLEVEL_SINGLE;
244 
245   /**
246    * Set the "level" attribute.
247    * The level attribute specifies what levels of the source tree should
248    * be considered; it has the values single, multiple or any. The default
249    * is single.
250    *
251    * @param v Value to set for "level" attribute.
252    */
setLevel(int v)253   public void setLevel(int v)
254   {
255     m_level = v;
256   }
257 
258   /**
259    * Get the "level" attribute.
260    * The level attribute specifies what levels of the source tree should
261    * be considered; it has the values single, multiple or any. The default
262    * is single.
263    *
264    * @return Value of "level" attribute.
265    */
getLevel()266   public int getLevel()
267   {
268     return m_level;
269   }
270 
271   /**
272    * The value attribute contains an expression. The expression is evaluated
273    * and the resulting object is converted to a number as if by a call to the
274    * number function.
275    * @serial
276    */
277   private XPath m_valueExpr = null;
278 
279   /**
280    * Set the "value" attribute.
281    * The value attribute contains an expression. The expression is evaluated
282    * and the resulting object is converted to a number as if by a call to the
283    * number function.
284    *
285    * @param v Value to set for "value" attribute.
286    */
setValue(XPath v)287   public void setValue(XPath v)
288   {
289     m_valueExpr = v;
290   }
291 
292   /**
293    * Get the "value" attribute.
294    * The value attribute contains an expression. The expression is evaluated
295    * and the resulting object is converted to a number as if by a call to the
296    * number function.
297    *
298    * @return Value of "value" attribute.
299    */
getValue()300   public XPath getValue()
301   {
302     return m_valueExpr;
303   }
304 
305   /**
306    * The "format" attribute is used to control conversion of a list of
307    * numbers into a string.
308    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
309    * @serial
310    */
311   private AVT m_format_avt = null;
312 
313   /**
314    * Set the "format" attribute.
315    * The "format" attribute is used to control conversion of a list of
316    * numbers into a string.
317    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
318    *
319    * @param v Value to set for "format" attribute.
320    */
setFormat(AVT v)321   public void setFormat(AVT v)
322   {
323     m_format_avt = v;
324   }
325 
326   /**
327    * Get the "format" attribute.
328    * The "format" attribute is used to control conversion of a list of
329    * numbers into a string.
330    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
331    *
332    * @return Value of "format" attribute.
333    */
getFormat()334   public AVT getFormat()
335   {
336     return m_format_avt;
337   }
338 
339   /**
340    * When numbering with an alphabetic sequence, the lang attribute
341    * specifies which language's alphabet is to be used.
342    * @serial
343    */
344   private AVT m_lang_avt = null;
345 
346   /**
347    * Set the "lang" attribute.
348    * When numbering with an alphabetic sequence, the lang attribute
349    * specifies which language's alphabet is to be used; it has the same
350    * range of values as xml:lang [XML]; if no lang value is specified,
351    * the language should be determined from the system environment.
352    * Implementers should document for which languages they support numbering.
353    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
354    *
355    * @param v Value to set for "lang" attribute.
356    */
setLang(AVT v)357   public void setLang(AVT v)
358   {
359     m_lang_avt = v;
360   }
361 
362   /**
363    * Get the "lang" attribute.
364    * When numbering with an alphabetic sequence, the lang attribute
365    * specifies which language's alphabet is to be used; it has the same
366    * range of values as xml:lang [XML]; if no lang value is specified,
367    * the language should be determined from the system environment.
368    * Implementers should document for which languages they support numbering.
369    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
370    *
371    * @return Value ofr "lang" attribute.
372    */
getLang()373   public AVT getLang()
374   {
375     return m_lang_avt;
376   }
377 
378   /**
379    * The letter-value attribute disambiguates between numbering
380    * sequences that use letters.
381    * @serial
382    */
383   private AVT m_lettervalue_avt = null;
384 
385   /**
386    * Set the "letter-value" attribute.
387    * The letter-value attribute disambiguates between numbering sequences
388    * that use letters.
389    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
390    *
391    * @param v Value to set for "letter-value" attribute.
392    */
setLetterValue(AVT v)393   public void setLetterValue(AVT v)
394   {
395     m_lettervalue_avt = v;
396   }
397 
398   /**
399    * Get the "letter-value" attribute.
400    * The letter-value attribute disambiguates between numbering sequences
401    * that use letters.
402    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
403    *
404    * @return Value to set for "letter-value" attribute.
405    */
getLetterValue()406   public AVT getLetterValue()
407   {
408     return m_lettervalue_avt;
409   }
410 
411   /**
412    * The grouping-separator attribute gives the separator
413    * used as a grouping (e.g. thousands) separator in decimal
414    * numbering sequences.
415    * @serial
416    */
417   private AVT m_groupingSeparator_avt = null;
418 
419   /**
420    * Set the "grouping-separator" attribute.
421    * The grouping-separator attribute gives the separator
422    * used as a grouping (e.g. thousands) separator in decimal
423    * numbering sequences.
424    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
425    *
426    * @param v Value to set for "grouping-separator" attribute.
427    */
setGroupingSeparator(AVT v)428   public void setGroupingSeparator(AVT v)
429   {
430     m_groupingSeparator_avt = v;
431   }
432 
433   /**
434    * Get the "grouping-separator" attribute.
435    * The grouping-separator attribute gives the separator
436    * used as a grouping (e.g. thousands) separator in decimal
437    * numbering sequences.
438    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
439    *
440    * @return Value of "grouping-separator" attribute.
441    */
getGroupingSeparator()442   public AVT getGroupingSeparator()
443   {
444     return m_groupingSeparator_avt;
445   }
446 
447   /**
448    * The optional grouping-size specifies the size (normally 3) of the grouping.
449    * @serial
450    */
451   private AVT m_groupingSize_avt = null;
452 
453   /**
454    * Set the "grouping-size" attribute.
455    * The optional grouping-size specifies the size (normally 3) of the grouping.
456    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
457    *
458    * @param v Value to set for "grouping-size" attribute.
459    */
setGroupingSize(AVT v)460   public void setGroupingSize(AVT v)
461   {
462     m_groupingSize_avt = v;
463   }
464 
465   /**
466    * Get the "grouping-size" attribute.
467    * The optional grouping-size specifies the size (normally 3) of the grouping.
468    * @see <a href="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
469    *
470    * @return Value of "grouping-size" attribute.
471    */
getGroupingSize()472   public AVT getGroupingSize()
473   {
474     return m_groupingSize_avt;
475   }
476 
477   /**
478    * Shouldn't this be in the transformer?  Big worries about threads...
479    */
480 
481   // private XResourceBundle thisBundle;
482 
483   /**
484    * Table to help in converting decimals to roman numerals.
485    * @see org.apache.xalan.transformer.DecimalToRoman
486    */
487   private final static DecimalToRoman m_romanConvertTable[] = {
488     new DecimalToRoman(1000, "M", 900, "CM"),
489     new DecimalToRoman(500, "D", 400, "CD"),
490     new DecimalToRoman(100L, "C", 90L, "XC"),
491     new DecimalToRoman(50L, "L", 40L, "XL"),
492     new DecimalToRoman(10L, "X", 9L, "IX"),
493     new DecimalToRoman(5L, "V", 4L, "IV"),
494     new DecimalToRoman(1L, "I", 1L, "I") };
495 
496   /**
497    * This function is called after everything else has been
498    * recomposed, and allows the template to set remaining
499    * values that may be based on some other property that
500    * depends on recomposition.
501    */
compose(StylesheetRoot sroot)502   public void compose(StylesheetRoot sroot) throws TransformerException
503   {
504     super.compose(sroot);
505     StylesheetRoot.ComposeState cstate = sroot.getComposeState();
506     java.util.Vector vnames = cstate.getVariableNames();
507     if(null != m_countMatchPattern)
508       m_countMatchPattern.fixupVariables(vnames, cstate.getGlobalsSize());
509     if(null != m_format_avt)
510       m_format_avt.fixupVariables(vnames, cstate.getGlobalsSize());
511     if(null != m_fromMatchPattern)
512       m_fromMatchPattern.fixupVariables(vnames, cstate.getGlobalsSize());
513     if(null != m_groupingSeparator_avt)
514       m_groupingSeparator_avt.fixupVariables(vnames, cstate.getGlobalsSize());
515     if(null != m_groupingSize_avt)
516       m_groupingSize_avt.fixupVariables(vnames, cstate.getGlobalsSize());
517     if(null != m_lang_avt)
518       m_lang_avt.fixupVariables(vnames, cstate.getGlobalsSize());
519     if(null != m_lettervalue_avt)
520       m_lettervalue_avt.fixupVariables(vnames, cstate.getGlobalsSize());
521     if(null != m_valueExpr)
522       m_valueExpr.fixupVariables(vnames, cstate.getGlobalsSize());
523   }
524 
525 
526   /**
527    * Get an int constant identifying the type of element.
528    * @see org.apache.xalan.templates.Constants
529    *
530    * @return The token ID for this element
531    */
getXSLToken()532   public int getXSLToken()
533   {
534     return Constants.ELEMNAME_NUMBER;
535   }
536 
537   /**
538    * Return the node name.
539    *
540    * @return The element's name
541    */
getNodeName()542   public String getNodeName()
543   {
544     return Constants.ELEMNAME_NUMBER_STRING;
545   }
546 
547   /**
548    * Execute an xsl:number instruction. The xsl:number element is
549    * used to insert a formatted number into the result tree.
550    *
551    * @param transformer non-null reference to the the current transform-time state.
552    *
553    * @throws TransformerException
554    */
execute( TransformerImpl transformer)555   public void execute(
556           TransformerImpl transformer)
557             throws TransformerException
558   {
559 
560     int sourceNode = transformer.getXPathContext().getCurrentNode();
561     String countString = getCountString(transformer, sourceNode);
562 
563     try
564     {
565       transformer.getResultTreeHandler().characters(countString.toCharArray(),
566                                                     0, countString.length());
567     }
568     catch(SAXException se)
569     {
570       throw new TransformerException(se);
571     }
572   }
573 
574   /**
575    * Add a child to the child list.
576    *
577    * @param newChild Child to add to child list
578    *
579    * @return Child just added to child list
580    *
581    * @throws DOMException
582    */
appendChild(ElemTemplateElement newChild)583   public ElemTemplateElement appendChild(ElemTemplateElement newChild)
584   {
585 
586     error(XSLTErrorResources.ER_CANNOT_ADD,
587           new Object[]{ newChild.getNodeName(),
588                         this.getNodeName() });  //"Can not add " +((ElemTemplateElement)newChild).m_elemName +
589 
590     //" to " + this.m_elemName);
591     return null;
592   }
593 
594   /**
595    * Given a 'from' pattern (ala xsl:number), a match pattern
596    * and a context, find the first ancestor that matches the
597    * pattern (including the context handed in).
598    *
599    * @param xctxt The XPath runtime state for this.
600    * @param fromMatchPattern The ancestor must match this pattern.
601    * @param countMatchPattern The ancestor must also match this pattern.
602    * @param context The node that "." expresses.
603    * @param namespaceContext The context in which namespaces in the
604    * queries are supposed to be expanded.
605    *
606    * @return the first ancestor that matches the given pattern
607    *
608    * @throws javax.xml.transform.TransformerException
609    */
findAncestor( XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern, int context, ElemNumber namespaceContext)610   int findAncestor(
611           XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern,
612           int context, ElemNumber namespaceContext)
613             throws javax.xml.transform.TransformerException
614   {
615     DTM dtm = xctxt.getDTM(context);
616     while (DTM.NULL != context)
617     {
618       if (null != fromMatchPattern)
619       {
620         if (fromMatchPattern.getMatchScore(xctxt, context)
621                 != XPath.MATCH_SCORE_NONE)
622         {
623 
624           //context = null;
625           break;
626         }
627       }
628 
629       if (null != countMatchPattern)
630       {
631         if (countMatchPattern.getMatchScore(xctxt, context)
632                 != XPath.MATCH_SCORE_NONE)
633         {
634           break;
635         }
636       }
637 
638       context = dtm.getParent(context);
639     }
640 
641     return context;
642   }
643 
644   /**
645    * Given a 'from' pattern (ala xsl:number), a match pattern
646    * and a context, find the first ancestor that matches the
647    * pattern (including the context handed in).
648    * @param xctxt The XPath runtime state for this.
649    * @param fromMatchPattern The ancestor must match this pattern.
650    * @param countMatchPattern The ancestor must also match this pattern.
651    * @param context The node that "." expresses.
652    * @param namespaceContext The context in which namespaces in the
653    * queries are supposed to be expanded.
654    *
655    * @return the first preceding, ancestor or self node that
656    * matches the given pattern
657    *
658    * @throws javax.xml.transform.TransformerException
659    */
findPrecedingOrAncestorOrSelf( XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern, int context, ElemNumber namespaceContext)660   private int findPrecedingOrAncestorOrSelf(
661           XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern,
662           int context, ElemNumber namespaceContext)
663             throws javax.xml.transform.TransformerException
664   {
665     DTM dtm = xctxt.getDTM(context);
666     while (DTM.NULL != context)
667     {
668       if (null != fromMatchPattern)
669       {
670         if (fromMatchPattern.getMatchScore(xctxt, context)
671                 != XPath.MATCH_SCORE_NONE)
672         {
673           context = DTM.NULL;
674 
675           break;
676         }
677       }
678 
679       if (null != countMatchPattern)
680       {
681         if (countMatchPattern.getMatchScore(xctxt, context)
682                 != XPath.MATCH_SCORE_NONE)
683         {
684           break;
685         }
686       }
687 
688       int prevSibling = dtm.getPreviousSibling(context);
689 
690       if (DTM.NULL == prevSibling)
691       {
692         context = dtm.getParent(context);
693       }
694       else
695       {
696 
697         // Now go down the chain of children of this sibling
698         context = dtm.getLastChild(prevSibling);
699 
700         if (context == DTM.NULL)
701           context = prevSibling;
702       }
703     }
704 
705     return context;
706   }
707 
708   /**
709    * Get the count match pattern, or a default value.
710    *
711    * @param support The XPath runtime state for this.
712    * @param contextNode The node that "." expresses.
713    *
714    * @return the count match pattern, or a default value.
715    *
716    * @throws javax.xml.transform.TransformerException
717    */
getCountMatchPattern(XPathContext support, int contextNode)718   XPath getCountMatchPattern(XPathContext support, int contextNode)
719           throws javax.xml.transform.TransformerException
720   {
721 
722     XPath countMatchPattern = m_countMatchPattern;
723     DTM dtm = support.getDTM(contextNode);
724     if (null == countMatchPattern)
725     {
726       switch (dtm.getNodeType(contextNode))
727       {
728       case DTM.ELEMENT_NODE :
729         MyPrefixResolver resolver;
730 
731         if (dtm.getNamespaceURI(contextNode) == null) {
732              resolver =  new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, false);
733         } else {
734             resolver = new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, true);
735         }
736 
737         countMatchPattern = new XPath(dtm.getNodeName(contextNode), this, resolver,
738                                       XPath.MATCH, support.getErrorListener());
739         break;
740 
741       case DTM.ATTRIBUTE_NODE :
742 
743         // countMatchPattern = m_stylesheet.createMatchPattern("@"+contextNode.getNodeName(), this);
744         countMatchPattern = new XPath("@" + dtm.getNodeName(contextNode), this,
745                                       this, XPath.MATCH, support.getErrorListener());
746         break;
747       case DTM.CDATA_SECTION_NODE :
748       case DTM.TEXT_NODE :
749 
750         // countMatchPattern = m_stylesheet.createMatchPattern("text()", this);
751         countMatchPattern = new XPath("text()", this, this, XPath.MATCH, support.getErrorListener());
752         break;
753       case DTM.COMMENT_NODE :
754 
755         // countMatchPattern = m_stylesheet.createMatchPattern("comment()", this);
756         countMatchPattern = new XPath("comment()", this, this, XPath.MATCH, support.getErrorListener());
757         break;
758       case DTM.DOCUMENT_NODE :
759 
760         // countMatchPattern = m_stylesheet.createMatchPattern("/", this);
761         countMatchPattern = new XPath("/", this, this, XPath.MATCH, support.getErrorListener());
762         break;
763       case DTM.PROCESSING_INSTRUCTION_NODE :
764 
765         // countMatchPattern = m_stylesheet.createMatchPattern("pi("+contextNode.getNodeName()+")", this);
766         countMatchPattern = new XPath("pi(" + dtm.getNodeName(contextNode)
767                                       + ")", this, this, XPath.MATCH, support.getErrorListener());
768         break;
769       default :
770         countMatchPattern = null;
771       }
772     }
773 
774     return countMatchPattern;
775   }
776 
777   /**
778    * Given an XML source node, get the count according to the
779    * parameters set up by the xsl:number attributes.
780    * @param transformer non-null reference to the the current transform-time state.
781    * @param sourceNode The source node being counted.
782    *
783    * @return The count of nodes
784    *
785    * @throws TransformerException
786    */
getCountString(TransformerImpl transformer, int sourceNode)787   String getCountString(TransformerImpl transformer, int sourceNode)
788           throws TransformerException
789   {
790 
791     long[] list = null;
792     XPathContext xctxt = transformer.getXPathContext();
793     CountersTable ctable = transformer.getCountersTable();
794 
795     if (null != m_valueExpr)
796     {
797       XObject countObj = m_valueExpr.execute(xctxt, sourceNode, this);
798       //According to Errata E24
799       double d_count = java.lang.Math.floor(countObj.num()+ 0.5);
800       if (Double.isNaN(d_count)) return "NaN";
801       else if (d_count < 0 && Double.isInfinite(d_count)) return "-Infinity";
802       else if (Double.isInfinite(d_count)) return "Infinity";
803       else if (d_count == 0) return "0";
804       else{
805               long count = (long)d_count;
806               list = new long[1];
807               list[0] = count;
808       }
809     }
810     else
811     {
812       if (Constants.NUMBERLEVEL_ANY == m_level)
813       {
814         list = new long[1];
815         list[0] = ctable.countNode(xctxt, this, sourceNode);
816       }
817       else
818       {
819         NodeVector ancestors =
820           getMatchingAncestors(xctxt, sourceNode,
821                                Constants.NUMBERLEVEL_SINGLE == m_level);
822         int lastIndex = ancestors.size() - 1;
823 
824         if (lastIndex >= 0)
825         {
826           list = new long[lastIndex + 1];
827 
828           for (int i = lastIndex; i >= 0; i--)
829           {
830             int target = ancestors.elementAt(i);
831 
832             list[lastIndex - i] = ctable.countNode(xctxt, this, target);
833           }
834         }
835       }
836     }
837 
838     return (null != list)
839            ? formatNumberList(transformer, list, sourceNode) : "";
840   }
841 
842   /**
843    * Get the previous node to be counted.
844    *
845    * @param xctxt The XPath runtime state for this.
846    * @param pos The current node
847    *
848    * @return the previous node to be counted.
849    *
850    * @throws TransformerException
851    */
getPreviousNode(XPathContext xctxt, int pos)852   public int getPreviousNode(XPathContext xctxt, int pos)
853           throws TransformerException
854   {
855 
856     XPath countMatchPattern = getCountMatchPattern(xctxt, pos);
857     DTM dtm = xctxt.getDTM(pos);
858 
859     if (Constants.NUMBERLEVEL_ANY == m_level)
860     {
861       XPath fromMatchPattern = m_fromMatchPattern;
862 
863       // Do a backwards document-order walk 'till a node is found that matches
864       // the 'from' pattern, or a node is found that matches the 'count' pattern,
865       // or the top of the tree is found.
866       while (DTM.NULL != pos)
867       {
868 
869         // Get the previous sibling, if there is no previous sibling,
870         // then count the parent, but if there is a previous sibling,
871         // dive down to the lowest right-hand (last) child of that sibling.
872         int next = dtm.getPreviousSibling(pos);
873 
874         if (DTM.NULL == next)
875         {
876           next = dtm.getParent(pos);
877 
878           if ((DTM.NULL != next) && ((((null != fromMatchPattern) && (fromMatchPattern.getMatchScore(
879                   xctxt, next) != XPath.MATCH_SCORE_NONE)))
880               || (dtm.getNodeType(next) == DTM.DOCUMENT_NODE)))
881           {
882             pos = DTM.NULL;  // return null from function.
883 
884             break;  // from while loop
885           }
886         }
887         else
888         {
889 
890           // dive down to the lowest right child.
891           int child = next;
892 
893           while (DTM.NULL != child)
894           {
895             child = dtm.getLastChild(next);
896 
897             if (DTM.NULL != child)
898               next = child;
899           }
900         }
901 
902         pos = next;
903 
904         if ((DTM.NULL != pos)
905                 && ((null == countMatchPattern)
906                     || (countMatchPattern.getMatchScore(xctxt, pos)
907                         != XPath.MATCH_SCORE_NONE)))
908         {
909           break;
910         }
911       }
912     }
913     else  // NUMBERLEVEL_MULTI or NUMBERLEVEL_SINGLE
914     {
915       while (DTM.NULL != pos)
916       {
917         pos = dtm.getPreviousSibling(pos);
918 
919         if ((DTM.NULL != pos)
920                 && ((null == countMatchPattern)
921                     || (countMatchPattern.getMatchScore(xctxt, pos)
922                         != XPath.MATCH_SCORE_NONE)))
923         {
924           break;
925         }
926       }
927     }
928 
929     return pos;
930   }
931 
932   /**
933    * Get the target node that will be counted..
934    *
935    * @param xctxt The XPath runtime state for this.
936    * @param sourceNode non-null reference to the <a href="http://www.w3.org/TR/xslt#dt-current-node">current source node</a>.
937    *
938    * @return the target node that will be counted
939    *
940    * @throws TransformerException
941    */
getTargetNode(XPathContext xctxt, int sourceNode)942   public int getTargetNode(XPathContext xctxt, int sourceNode)
943           throws TransformerException
944   {
945 
946     int target = DTM.NULL;
947     XPath countMatchPattern = getCountMatchPattern(xctxt, sourceNode);
948 
949     if (Constants.NUMBERLEVEL_ANY == m_level)
950     {
951       target = findPrecedingOrAncestorOrSelf(xctxt, m_fromMatchPattern,
952                                              countMatchPattern, sourceNode,
953                                              this);
954     }
955     else
956     {
957       target = findAncestor(xctxt, m_fromMatchPattern, countMatchPattern,
958                             sourceNode, this);
959     }
960 
961     return target;
962   }
963 
964   /**
965    * Get the ancestors, up to the root, that match the
966    * pattern.
967    *
968    * @param xctxt The XPath runtime state for this.
969    * @param node Count this node and it's ancestors.
970    * @param stopAtFirstFound Flag indicating to stop after the
971    * first node is found (difference between level = single
972    * or multiple)
973    * @return The number of ancestors that match the pattern.
974    *
975    * @throws javax.xml.transform.TransformerException
976    */
getMatchingAncestors( XPathContext xctxt, int node, boolean stopAtFirstFound)977   NodeVector getMatchingAncestors(
978           XPathContext xctxt, int node, boolean stopAtFirstFound)
979             throws javax.xml.transform.TransformerException
980   {
981 
982     NodeSetDTM ancestors = new NodeSetDTM(xctxt.getDTMManager());
983     XPath countMatchPattern = getCountMatchPattern(xctxt, node);
984     DTM dtm = xctxt.getDTM(node);
985 
986     while (DTM.NULL != node)
987     {
988       if ((null != m_fromMatchPattern)
989               && (m_fromMatchPattern.getMatchScore(xctxt, node)
990                   != XPath.MATCH_SCORE_NONE))
991       {
992 
993         // The following if statement gives level="single" different
994         // behavior from level="multiple", which seems incorrect according
995         // to the XSLT spec.  For now we are leaving this in to replicate
996         // the same behavior in XT, but, for all intents and purposes we
997         // think this is a bug, or there is something about level="single"
998         // that we still don't understand.
999         if (!stopAtFirstFound)
1000           break;
1001       }
1002 
1003       if (null == countMatchPattern)
1004         System.out.println(
1005           "Programmers error! countMatchPattern should never be null!");
1006 
1007       if (countMatchPattern.getMatchScore(xctxt, node)
1008               != XPath.MATCH_SCORE_NONE)
1009       {
1010         ancestors.addElement(node);
1011 
1012         if (stopAtFirstFound)
1013           break;
1014       }
1015 
1016       node = dtm.getParent(node);
1017     }
1018 
1019     return ancestors;
1020   }  // end getMatchingAncestors method
1021 
1022   /**
1023    * Get the locale we should be using.
1024    *
1025    * @param transformer non-null reference to the the current transform-time state.
1026    * @param contextNode The node that "." expresses.
1027    *
1028    * @return The locale to use. May be specified by "lang" attribute,
1029    * but if not, use default locale on the system.
1030    *
1031    * @throws TransformerException
1032    */
getLocale(TransformerImpl transformer, int contextNode)1033   Locale getLocale(TransformerImpl transformer, int contextNode)
1034           throws TransformerException
1035   {
1036 
1037     Locale locale = null;
1038 
1039     if (null != m_lang_avt)
1040     {
1041       XPathContext xctxt = transformer.getXPathContext();
1042       String langValue = m_lang_avt.evaluate(xctxt, contextNode, this);
1043 
1044       if (null != langValue)
1045       {
1046 
1047         // Not really sure what to do about the country code, so I use the
1048         // default from the system.
1049         // TODO: fix xml:lang handling.
1050         locale = new Locale(langValue.toUpperCase(), "");
1051 
1052         //Locale.getDefault().getDisplayCountry());
1053         if (null == locale)
1054         {
1055           transformer.getMsgMgr().warn(this, null, xctxt.getDTM(contextNode).getNode(contextNode),
1056                                        XSLTErrorResources.WG_LOCALE_NOT_FOUND,
1057                                        new Object[]{ langValue });  //"Warning: Could not find locale for xml:lang="+langValue);
1058 
1059           locale = Locale.getDefault();
1060         }
1061       }
1062     }
1063     else
1064     {
1065       locale = Locale.getDefault();
1066     }
1067 
1068     return locale;
1069   }
1070 
1071   /**
1072    * Get the number formatter to be used the format the numbers
1073    *
1074    * @param transformer non-null reference to the the current transform-time state.
1075    * @param contextNode The node that "." expresses.
1076    *
1077    * ($objectName$) @return The number formatter to be used
1078    *
1079    * @throws TransformerException
1080    */
getNumberFormatter( TransformerImpl transformer, int contextNode)1081   private DecimalFormat getNumberFormatter(
1082           TransformerImpl transformer, int contextNode) throws TransformerException
1083   {
1084     // Patch from Steven Serocki
1085     // Maybe we really want to do the clone in getLocale() and return
1086     // a clone of the default Locale??
1087     Locale locale = (Locale)getLocale(transformer, contextNode).clone();
1088 
1089     // Helper to format local specific numbers to strings.
1090     DecimalFormat formatter = null;
1091 
1092     //synchronized (locale)
1093     //{
1094     //     formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale);
1095     //}
1096 
1097     String digitGroupSepValue =
1098       (null != m_groupingSeparator_avt)
1099       ? m_groupingSeparator_avt.evaluate(
1100       transformer.getXPathContext(), contextNode, this) : null;
1101 
1102 
1103     // Validate grouping separator if an AVT was used; otherwise this was
1104     // validated statically in XSLTAttributeDef.java.
1105     if ((digitGroupSepValue != null) && (!m_groupingSeparator_avt.isSimple()) &&
1106         (digitGroupSepValue.length() != 1))
1107     {
1108             transformer.getMsgMgr().warn(
1109                this, XSLTErrorResources.WG_ILLEGAL_ATTRIBUTE_VALUE,
1110                new Object[]{ Constants.ATTRNAME_NAME, m_groupingSeparator_avt.getName()});
1111     }
1112 
1113 
1114     String nDigitsPerGroupValue =
1115       (null != m_groupingSize_avt)
1116       ? m_groupingSize_avt.evaluate(
1117       transformer.getXPathContext(), contextNode, this) : null;
1118 
1119     // TODO: Handle digit-group attributes
1120     if ((null != digitGroupSepValue) && (null != nDigitsPerGroupValue) &&
1121         // Ignore if separation value is empty string
1122         (digitGroupSepValue.length() > 0))
1123     {
1124       try
1125       {
1126         formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale);
1127         formatter.setGroupingSize(
1128           Integer.valueOf(nDigitsPerGroupValue).intValue());
1129 
1130         DecimalFormatSymbols symbols = formatter.getDecimalFormatSymbols();
1131         symbols.setGroupingSeparator(digitGroupSepValue.charAt(0));
1132         formatter.setDecimalFormatSymbols(symbols);
1133         formatter.setGroupingUsed(true);
1134       }
1135       catch (NumberFormatException ex)
1136       {
1137         formatter.setGroupingUsed(false);
1138       }
1139     }
1140 
1141     return formatter;
1142   }
1143 
1144   /**
1145    * Format a vector of numbers into a formatted string.
1146    *
1147    * @param transformer non-null reference to the the current transform-time state.
1148    * @param list Array of one or more long integer numbers.
1149    * @param contextNode The node that "." expresses.
1150    * @return String that represents list according to
1151    * %conversion-atts; attributes.
1152    * TODO: Optimize formatNumberList so that it caches the last count and
1153    * reuses that info for the next count.
1154    *
1155    * @throws TransformerException
1156    */
formatNumberList( TransformerImpl transformer, long[] list, int contextNode)1157   String formatNumberList(
1158           TransformerImpl transformer, long[] list, int contextNode)
1159             throws TransformerException
1160   {
1161 
1162     String numStr;
1163     FastStringBuffer formattedNumber = StringBufferPool.get();
1164 
1165     try
1166     {
1167       int nNumbers = list.length, numberWidth = 1;
1168       char numberType = '1';
1169       String formatToken, lastSepString = null, formatTokenString = null;
1170 
1171       // If a seperator hasn't been specified, then use "."
1172       // as a default separator.
1173       // For instance: [2][1][5] with a format value of "1 "
1174       // should format to "2.1.5 " (I think).
1175       // Otherwise, use the seperator specified in the format string.
1176       // For instance: [2][1][5] with a format value of "01-001. "
1177       // should format to "02-001-005 ".
1178       String lastSep = ".";
1179       boolean isFirstToken = true;  // true if first token
1180       String formatValue =
1181         (null != m_format_avt)
1182         ? m_format_avt.evaluate(
1183         transformer.getXPathContext(), contextNode, this) : null;
1184 
1185       if (null == formatValue)
1186         formatValue = "1";
1187 
1188       NumberFormatStringTokenizer formatTokenizer =
1189         new NumberFormatStringTokenizer(formatValue);
1190 
1191       // int sepCount = 0;                  // keep track of seperators
1192       // Loop through all the numbers in the list.
1193       for (int i = 0; i < nNumbers; i++)
1194       {
1195 
1196         // Loop to the next digit, letter, or separator.
1197         if (formatTokenizer.hasMoreTokens())
1198         {
1199           formatToken = formatTokenizer.nextToken();
1200 
1201           // If the first character of this token is a character or digit, then
1202           // it is a number format directive.
1203           if (Character.isLetterOrDigit(
1204                   formatToken.charAt(formatToken.length() - 1)))
1205           {
1206             numberWidth = formatToken.length();
1207             numberType = formatToken.charAt(numberWidth - 1);
1208           }
1209 
1210           // If there is a number format directive ahead,
1211           // then append the formatToken.
1212           else if (formatTokenizer.isLetterOrDigitAhead())
1213           {
1214             formatTokenString = formatToken;
1215 
1216             // Append the formatToken string...
1217             // For instance [2][1][5] with a format value of "1--1. "
1218             // should format to "2--1--5. " (I guess).
1219             while (formatTokenizer.nextIsSep())
1220             {
1221               formatToken = formatTokenizer.nextToken();
1222               formatTokenString += formatToken;
1223             }
1224 
1225             // Record this separator, so it can be used as the
1226             // next separator, if the next is the last.
1227             // For instance: [2][1][5] with a format value of "1-1 "
1228             // should format to "2-1-5 ".
1229             if (!isFirstToken)
1230               lastSep = formatTokenString;
1231 
1232             // Since we know the next is a number or digit, we get it now.
1233             formatToken = formatTokenizer.nextToken();
1234             numberWidth = formatToken.length();
1235             numberType = formatToken.charAt(numberWidth - 1);
1236           }
1237           else  // only separators left
1238           {
1239 
1240             // Set up the string for the trailing characters after
1241             // the last number is formatted (i.e. after the loop).
1242             lastSepString = formatToken;
1243 
1244             // And append any remaining characters to the lastSepString.
1245             while (formatTokenizer.hasMoreTokens())
1246             {
1247               formatToken = formatTokenizer.nextToken();
1248               lastSepString += formatToken;
1249             }
1250           }  // else
1251         }  // end if(formatTokenizer.hasMoreTokens())
1252 
1253         // if this is the first token and there was a prefix
1254         // append the prefix else, append the separator
1255         // For instance, [2][1][5] with a format value of "(1-1.) "
1256         // should format to "(2-1-5.) " (I guess).
1257         if (null != formatTokenString && isFirstToken)
1258         {
1259           formattedNumber.append(formatTokenString);
1260         }
1261         else if (null != lastSep &&!isFirstToken)
1262           formattedNumber.append(lastSep);
1263 
1264         getFormattedNumber(transformer, contextNode, numberType, numberWidth,
1265                            list[i], formattedNumber);
1266 
1267         isFirstToken = false;  // After the first pass, this should be false
1268       }  // end for loop
1269 
1270       // Check to see if we finished up the format string...
1271       // Skip past all remaining letters or digits
1272       while (formatTokenizer.isLetterOrDigitAhead())
1273       {
1274         formatTokenizer.nextToken();
1275       }
1276 
1277       if (lastSepString != null)
1278         formattedNumber.append(lastSepString);
1279 
1280       while (formatTokenizer.hasMoreTokens())
1281       {
1282         formatToken = formatTokenizer.nextToken();
1283 
1284         formattedNumber.append(formatToken);
1285       }
1286 
1287       numStr = formattedNumber.toString();
1288     }
1289     finally
1290     {
1291       StringBufferPool.free(formattedNumber);
1292     }
1293 
1294     return numStr;
1295   }  // end formatNumberList method
1296 
1297   /*
1298   * Get Formatted number
1299   */
1300 
1301   /**
1302    * Format the given number and store it in the given buffer
1303    *
1304    *
1305    * @param transformer non-null reference to the the current transform-time state.
1306    * @param contextNode The node that "." expresses.
1307    * @param numberType Type to format to
1308    * @param numberWidth Maximum length of formatted number
1309    * @param listElement Number to format
1310    * @param formattedNumber Buffer to store formatted number
1311    *
1312    * @throws javax.xml.transform.TransformerException
1313    */
getFormattedNumber( TransformerImpl transformer, int contextNode, char numberType, int numberWidth, long listElement, FastStringBuffer formattedNumber)1314   private void getFormattedNumber(
1315           TransformerImpl transformer, int contextNode,
1316           char numberType, int numberWidth, long listElement,
1317           FastStringBuffer formattedNumber)
1318             throws javax.xml.transform.TransformerException
1319   {
1320 
1321 
1322     String letterVal =
1323       (m_lettervalue_avt != null)
1324       ? m_lettervalue_avt.evaluate(
1325       transformer.getXPathContext(), contextNode, this) : null;
1326 
1327     /**
1328      * Wrapper of Chars for converting integers into alpha counts.
1329      */
1330     CharArrayWrapper alphaCountTable = null;
1331 
1332     XResourceBundle thisBundle = null;
1333 
1334     switch (numberType)
1335     {
1336     case 'A' :
1337         if (null == m_alphaCountTable){
1338                 thisBundle =
1339                   (XResourceBundle) XResourceBundle.loadResourceBundle(
1340                     org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, getLocale(transformer, contextNode));
1341                 m_alphaCountTable = (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET);
1342         }
1343       int2alphaCount(listElement, m_alphaCountTable, formattedNumber);
1344       break;
1345     case 'a' :
1346         if (null == m_alphaCountTable){
1347                 thisBundle =
1348                   (XResourceBundle) XResourceBundle.loadResourceBundle(
1349                     org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, getLocale(transformer, contextNode));
1350                 m_alphaCountTable = (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET);
1351         }
1352       FastStringBuffer stringBuf = StringBufferPool.get();
1353 
1354       try
1355       {
1356         int2alphaCount(listElement, m_alphaCountTable, stringBuf);
1357         formattedNumber.append(
1358           stringBuf.toString().toLowerCase(
1359             getLocale(transformer, contextNode)));
1360       }
1361       finally
1362       {
1363         StringBufferPool.free(stringBuf);
1364       }
1365       break;
1366     case 'I' :
1367       formattedNumber.append(long2roman(listElement, true));
1368       break;
1369     case 'i' :
1370       formattedNumber.append(
1371         long2roman(listElement, true).toLowerCase(
1372           getLocale(transformer, contextNode)));
1373       break;
1374     case 0x3042 :
1375     {
1376 
1377       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1378         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "HA"));
1379 
1380       if (letterVal != null
1381               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1382         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1383       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1384         formattedNumber.append(
1385           int2singlealphaCount(
1386             listElement,
1387             (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1388 
1389       break;
1390     }
1391     case 0x3044 :
1392     {
1393 
1394       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1395         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "HI"));
1396 
1397       if ((letterVal != null)
1398               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1399         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1400       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1401         formattedNumber.append(
1402           int2singlealphaCount(
1403             listElement,
1404             (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1405 
1406       break;
1407     }
1408     case 0x30A2 :
1409     {
1410 
1411       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1412         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "A"));
1413 
1414       if (letterVal != null
1415               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1416         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1417       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1418         formattedNumber.append(
1419           int2singlealphaCount(
1420             listElement,
1421             (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1422 
1423       break;
1424     }
1425     case 0x30A4 :
1426     {
1427 
1428       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1429         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "I"));
1430 
1431       if (letterVal != null
1432               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1433         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1434       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1435         formattedNumber.append(
1436           int2singlealphaCount(
1437             listElement,
1438             (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1439 
1440       break;
1441     }
1442     case 0x4E00 :
1443     {
1444 
1445       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1446         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("zh", "CN"));
1447 
1448       if (letterVal != null
1449               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1450       {
1451         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1452       }
1453       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1454         int2alphaCount(listElement,
1455                        (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1456                        formattedNumber);
1457 
1458       break;
1459     }
1460     case 0x58F9 :
1461     {
1462 
1463       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1464         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("zh", "TW"));
1465 
1466       if (letterVal != null
1467               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1468         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1469       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1470         int2alphaCount(listElement,
1471                        (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1472                        formattedNumber);
1473 
1474       break;
1475     }
1476     case 0x0E51 :
1477     {
1478 
1479       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1480         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("th", ""));
1481 
1482       if (letterVal != null
1483               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1484         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1485       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1486         int2alphaCount(listElement,
1487                        (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1488                        formattedNumber);
1489 
1490       break;
1491     }
1492     case 0x05D0 :
1493     {
1494 
1495       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1496         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("he", ""));
1497 
1498       if (letterVal != null
1499               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1500         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1501       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1502         int2alphaCount(listElement,
1503                        (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1504                        formattedNumber);
1505 
1506       break;
1507     }
1508     case 0x10D0 :
1509     {
1510 
1511       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1512         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ka", ""));
1513 
1514       if (letterVal != null
1515               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1516         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1517       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1518         int2alphaCount(listElement,
1519                        (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1520                        formattedNumber);
1521 
1522       break;
1523     }
1524     case 0x03B1 :
1525     {
1526 
1527       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1528         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("el", ""));
1529 
1530       if (letterVal != null
1531               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1532         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1533       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1534         int2alphaCount(listElement,
1535                        (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1536                        formattedNumber);
1537 
1538       break;
1539     }
1540     case 0x0430 :
1541     {
1542 
1543       thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1544         org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("cy", ""));
1545 
1546       if (letterVal != null
1547               && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1548         formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1549       else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1550         int2alphaCount(listElement,
1551                        (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1552                        formattedNumber);
1553 
1554       break;
1555     }
1556     default :  // "1"
1557       DecimalFormat formatter = getNumberFormatter(transformer, contextNode);
1558       String padString = formatter == null ? String.valueOf(0) : formatter.format(0);
1559       String numString = formatter == null ? String.valueOf(listElement) : formatter.format(listElement);
1560       int nPadding = numberWidth - numString.length();
1561 
1562       for (int k = 0; k < nPadding; k++)
1563       {
1564         formattedNumber.append(padString);
1565       }
1566 
1567       formattedNumber.append(numString);
1568     }
1569   }
1570 
1571   /**
1572    * Get a string value for zero, which is not really defined by the 1.0 spec,
1573    * thought I think it might be cleared up by the erreta.
1574    */
getZeroString()1575    String getZeroString()
1576    {
1577      return ""+0;
1578    }
1579 
1580   /**
1581    * Convert a long integer into alphabetic counting, in other words
1582    * count using the sequence A B C ... Z.
1583    *
1584    * @param val Value to convert -- must be greater than zero.
1585    * @param table a table containing one character for each digit in the radix
1586    * @return String representing alpha count of number.
1587    * @see TransformerImpl#DecimalToRoman
1588    *
1589    * Note that the radix of the conversion is inferred from the size
1590    * of the table.
1591    */
int2singlealphaCount(long val, CharArrayWrapper table)1592   protected String int2singlealphaCount(long val, CharArrayWrapper table)
1593   {
1594 
1595     int radix = table.getLength();
1596 
1597     // TODO:  throw error on out of range input
1598     if (val > radix)
1599     {
1600       return getZeroString();
1601     }
1602     else
1603       return (new Character(table.getChar((int)val - 1))).toString();  // index into table is off one, starts at 0
1604   }
1605 
1606   /**
1607    * Convert a long integer into alphabetic counting, in other words
1608    * count using the sequence A B C ... Z AA AB AC.... etc.
1609    *
1610    * @param val Value to convert -- must be greater than zero.
1611    * @param table a table containing one character for each digit in the radix
1612    * @param aTable Array of alpha characters representing numbers
1613    * @param stringBuf Buffer where to save the string representing alpha count of number.
1614    *
1615    * @see TransformerImpl#DecimalToRoman
1616    *
1617    * Note that the radix of the conversion is inferred from the size
1618    * of the table.
1619    */
int2alphaCount(long val, CharArrayWrapper aTable, FastStringBuffer stringBuf)1620   protected void int2alphaCount(long val, CharArrayWrapper aTable,
1621                                 FastStringBuffer stringBuf)
1622   {
1623 
1624     int radix = aTable.getLength();
1625     char[] table = new char[radix];
1626 
1627     // start table at 1, add last char at index 0. Reason explained above and below.
1628     int i;
1629 
1630     for (i = 0; i < radix - 1; i++)
1631     {
1632       table[i + 1] = aTable.getChar(i);
1633     }
1634 
1635     table[0] = aTable.getChar(i);
1636 
1637     // Create a buffer to hold the result
1638     // TODO:  size of the table can be detereined by computing
1639     // logs of the radix.  For now, we fake it.
1640     char buf[] = new char[100];
1641 
1642     //some languages go left to right(ie. english), right to left (ie. Hebrew),
1643     //top to bottom (ie.Japanese), etc... Handle them differently
1644     //String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION);
1645     // next character to set in the buffer
1646     int charPos;
1647 
1648     charPos = buf.length - 1;  // work backward through buf[]
1649 
1650     // index in table of the last character that we stored
1651     int lookupIndex = 1;  // start off with anything other than zero to make correction work
1652 
1653     //                                          Correction number
1654     //
1655     //  Correction can take on exactly two values:
1656     //
1657     //          0       if the next character is to be emitted is usual
1658     //
1659     //      radix - 1
1660     //                  if the next char to be emitted should be one less than
1661     //                  you would expect
1662     //
1663     // For example, consider radix 10, where 1="A" and 10="J"
1664     //
1665     // In this scheme, we count: A, B, C ...   H, I, J (not A0 and certainly
1666     // not AJ), A1
1667     //
1668     // So, how do we keep from emitting AJ for 10?  After correctly emitting the
1669     // J, lookupIndex is zero.  We now compute a correction number of 9 (radix-1).
1670     // In the following line, we'll compute (val+correction) % radix, which is,
1671     // (val+9)/10.  By this time, val is 1, so we compute (1+9) % 10, which
1672     // is 10 % 10 or zero.  So, we'll prepare to emit "JJ", but then we'll
1673     // later suppress the leading J as representing zero (in the mod system,
1674     // it can represent either 10 or zero).  In summary, the correction value of
1675     // "radix-1" acts like "-1" when run through the mod operator, but with the
1676     // desireable characteristic that it never produces a negative number.
1677     long correction = 0;
1678 
1679     // TODO:  throw error on out of range input
1680     do
1681     {
1682 
1683       // most of the correction calculation is explained above,  the reason for the
1684       // term after the "|| " is that it correctly propagates carries across
1685       // multiple columns.
1686       correction =
1687         ((lookupIndex == 0) || (correction != 0 && lookupIndex == radix - 1))
1688         ? (radix - 1) : 0;
1689 
1690       // index in "table" of the next char to emit
1691       lookupIndex = (int)(val + correction) % radix;
1692 
1693       // shift input by one "column"
1694       val = (val / radix);
1695 
1696       // if the next value we'd put out would be a leading zero, we're done.
1697       if (lookupIndex == 0 && val == 0)
1698         break;
1699 
1700       // put out the next character of output
1701       buf[charPos--] = table[lookupIndex];  // left to right or top to bottom
1702     }
1703     while (val > 0);
1704 
1705     stringBuf.append(buf, charPos + 1, (buf.length - charPos - 1));
1706   }
1707 
1708   /**
1709    * Convert a long integer into traditional alphabetic counting, in other words
1710    * count using the traditional numbering.
1711    *
1712    * @param val Value to convert -- must be greater than zero.
1713    * @param thisBundle Resource bundle to use
1714    *
1715    * @return String representing alpha count of number.
1716    * @see XSLProcessor#DecimalToRoman
1717    *
1718    * Note that the radix of the conversion is inferred from the size
1719    * of the table.
1720    */
tradAlphaCount(long val, XResourceBundle thisBundle)1721   protected String tradAlphaCount(long val, XResourceBundle thisBundle)
1722   {
1723 
1724     // if this number is larger than the largest number we can represent, error!
1725     if (val > Long.MAX_VALUE)
1726     {
1727       this.error(XSLTErrorResources.ER_NUMBER_TOO_BIG);
1728       return XSLTErrorResources.ERROR_STRING;
1729     }
1730     char[] table = null;
1731 
1732     // index in table of the last character that we stored
1733     int lookupIndex = 1;  // start off with anything other than zero to make correction work
1734 
1735     // Create a buffer to hold the result
1736     // TODO:  size of the table can be detereined by computing
1737     // logs of the radix.  For now, we fake it.
1738     char buf[] = new char[100];
1739 
1740     //some languages go left to right(ie. english), right to left (ie. Hebrew),
1741     //top to bottom (ie.Japanese), etc... Handle them differently
1742     //String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION);
1743     // next character to set in the buffer
1744     int charPos;
1745 
1746     charPos = 0;  //start at 0
1747 
1748     // array of number groups: ie.1000, 100, 10, 1
1749     IntArrayWrapper groups = (IntArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERGROUPS);
1750 
1751     // array of tables of hundreds, tens, digits...
1752     StringArrayWrapper tables =
1753       (StringArrayWrapper) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUM_TABLES));
1754 
1755     //some languages have additive alphabetical notation,
1756     //some multiplicative-additive, etc... Handle them differently.
1757     String numbering = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERING);
1758 
1759     // do multiplicative part first
1760     if (numbering.equals(org.apache.xml.utils.res.XResourceBundle.LANG_MULT_ADD))
1761     {
1762       String mult_order = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.MULT_ORDER);
1763       LongArrayWrapper multiplier =
1764         (LongArrayWrapper) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER));
1765       CharArrayWrapper zeroChar = (CharArrayWrapper) thisBundle.getObject("zero");
1766       int i = 0;
1767 
1768       // skip to correct multiplier
1769       while (i < multiplier.getLength() && val < multiplier.getLong(i))
1770       {
1771         i++;
1772       }
1773 
1774       do
1775       {
1776         if (i >= multiplier.getLength())
1777           break;  //number is smaller than multipliers
1778 
1779         // some languages (ie chinese) put a zero character (and only one) when
1780         // the multiplier is multiplied by zero. (ie, 1001 is 1X1000 + 0X100 + 0X10 + 1)
1781         // 0X100 is replaced by the zero character, we don't need one for 0X10
1782         if (val < multiplier.getLong(i))
1783         {
1784           if (zeroChar.getLength() == 0)
1785           {
1786             i++;
1787           }
1788           else
1789           {
1790             if (buf[charPos - 1] != zeroChar.getChar(0))
1791               buf[charPos++] = zeroChar.getChar(0);
1792 
1793             i++;
1794           }
1795         }
1796         else if (val >= multiplier.getLong(i))
1797         {
1798           long mult = val / multiplier.getLong(i);
1799 
1800           val = val % multiplier.getLong(i);  // save this.
1801 
1802           int k = 0;
1803 
1804           while (k < groups.getLength())
1805           {
1806             lookupIndex = 1;  // initialize for each table
1807 
1808             if (mult / groups.getInt(k) <= 0)  // look for right table
1809               k++;
1810             else
1811             {
1812 
1813               // get the table
1814               CharArrayWrapper THEletters = (CharArrayWrapper) thisBundle.getObject(tables.getString(k));
1815 
1816               table = new char[THEletters.getLength() + 1];
1817 
1818               int j;
1819 
1820               for (j = 0; j < THEletters.getLength(); j++)
1821               {
1822                 table[j + 1] = THEletters.getChar(j);
1823               }
1824 
1825               table[0] = THEletters.getChar(j - 1);  // don't need this
1826 
1827               // index in "table" of the next char to emit
1828               lookupIndex = (int)mult / groups.getInt(k);
1829 
1830               //this should not happen
1831               if (lookupIndex == 0 && mult == 0)
1832                 break;
1833 
1834               char multiplierChar = ((CharArrayWrapper) (thisBundle.getObject(
1835                 org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER_CHAR))).getChar(i);
1836 
1837               // put out the next character of output
1838               if (lookupIndex < table.length)
1839               {
1840                 if (mult_order.equals(org.apache.xml.utils.res.XResourceBundle.MULT_PRECEDES))
1841                 {
1842                   buf[charPos++] = multiplierChar;
1843                   buf[charPos++] = table[lookupIndex];
1844                 }
1845                 else
1846                 {
1847 
1848                   // don't put out 1 (ie 1X10 is just 10)
1849                   if (lookupIndex == 1 && i == multiplier.getLength() - 1){}
1850                   else
1851                     buf[charPos++] = table[lookupIndex];
1852 
1853                   buf[charPos++] = multiplierChar;
1854                 }
1855 
1856                 break;  // all done!
1857               }
1858               else
1859                 return XSLTErrorResources.ERROR_STRING;
1860             }  //end else
1861           }  // end while
1862 
1863           i++;
1864         }  // end else if
1865       }  // end do while
1866       while (i < multiplier.getLength());
1867     }
1868 
1869     // Now do additive part...
1870     int count = 0;
1871     String tableName;
1872 
1873     // do this for each table of hundreds, tens, digits...
1874     while (count < groups.getLength())
1875     {
1876       if (val / groups.getInt(count) <= 0)  // look for correct table
1877         count++;
1878       else
1879       {
1880         CharArrayWrapper theletters = (CharArrayWrapper) thisBundle.getObject(tables.getString(count));
1881 
1882         table = new char[theletters.getLength() + 1];
1883 
1884         int j;
1885 
1886         // need to start filling the table up at index 1
1887         for (j = 0; j < theletters.getLength(); j++)
1888         {
1889           table[j + 1] = theletters.getChar(j);
1890         }
1891 
1892         table[0] = theletters.getChar(j - 1);  // don't need this
1893 
1894         // index in "table" of the next char to emit
1895         lookupIndex = (int)val / groups.getInt(count);
1896 
1897         // shift input by one "column"
1898         val = val % groups.getInt(count);
1899 
1900         // this should not happen
1901         if (lookupIndex == 0 && val == 0)
1902           break;
1903 
1904         if (lookupIndex < table.length)
1905         {
1906 
1907           // put out the next character of output
1908           buf[charPos++] = table[lookupIndex];  // left to right or top to bottom
1909         }
1910         else
1911           return XSLTErrorResources.ERROR_STRING;
1912 
1913         count++;
1914       }
1915     }  // end while
1916 
1917     // String s = new String(buf, 0, charPos);
1918     return new String(buf, 0, charPos);
1919   }
1920 
1921   /**
1922    * Convert a long integer into roman numerals.
1923    * @param val Value to convert.
1924    * @param prefixesAreOK true_ to enable prefix notation (e.g. 4 = "IV"),
1925    * false_ to disable prefix notation (e.g. 4 = "IIII").
1926    * @return Roman numeral string.
1927    * @see DecimalToRoman
1928    * @see m_romanConvertTable
1929    */
long2roman(long val, boolean prefixesAreOK)1930   protected String long2roman(long val, boolean prefixesAreOK)
1931   {
1932 
1933     if (val <= 0)
1934     {
1935       return getZeroString();
1936     }
1937 
1938     String roman = "";
1939     int place = 0;
1940 
1941     if (val <= 3999L)
1942     {
1943       do
1944       {
1945         while (val >= m_romanConvertTable[place].m_postValue)
1946         {
1947           roman += m_romanConvertTable[place].m_postLetter;
1948           val -= m_romanConvertTable[place].m_postValue;
1949         }
1950 
1951         if (prefixesAreOK)
1952         {
1953           if (val >= m_romanConvertTable[place].m_preValue)
1954           {
1955             roman += m_romanConvertTable[place].m_preLetter;
1956             val -= m_romanConvertTable[place].m_preValue;
1957           }
1958         }
1959 
1960         place++;
1961       }
1962       while (val > 0);
1963     }
1964     else
1965     {
1966       roman = XSLTErrorResources.ERROR_STRING;
1967     }
1968 
1969     return roman;
1970   }  // end long2roman
1971 
1972   /**
1973    * Call the children visitors.
1974    * @param visitor The visitor whose appropriate method will be called.
1975    */
callChildVisitors(XSLTVisitor visitor, boolean callAttrs)1976   public void callChildVisitors(XSLTVisitor visitor, boolean callAttrs)
1977   {
1978   	if(callAttrs)
1979   	{
1980 	  	if(null != m_countMatchPattern)
1981 	  		m_countMatchPattern.getExpression().callVisitors(m_countMatchPattern, visitor);
1982 	  	if(null != m_fromMatchPattern)
1983 	  		m_fromMatchPattern.getExpression().callVisitors(m_fromMatchPattern, visitor);
1984 	  	if(null != m_valueExpr)
1985 	  		m_valueExpr.getExpression().callVisitors(m_valueExpr, visitor);
1986 
1987 	  	if(null != m_format_avt)
1988 	  		m_format_avt.callVisitors(visitor);
1989 	  	if(null != m_groupingSeparator_avt)
1990 	  		m_groupingSeparator_avt.callVisitors(visitor);
1991 	  	if(null != m_groupingSize_avt)
1992 	  		m_groupingSize_avt.callVisitors(visitor);
1993 	  	if(null != m_lang_avt)
1994 	  		m_lang_avt.callVisitors(visitor);
1995 	  	if(null != m_lettervalue_avt)
1996 	  		m_lettervalue_avt.callVisitors(visitor);
1997   	}
1998 
1999     super.callChildVisitors(visitor, callAttrs);
2000   }
2001 
2002 
2003   /**
2004    * This class returns tokens using non-alphanumberic
2005    * characters as delimiters.
2006    */
2007   class NumberFormatStringTokenizer
2008   {
2009 
2010     /** Current position in the format string          */
2011     private int currentPosition;
2012 
2013     /** Index of last character in the format string      */
2014     private int maxPosition;
2015 
2016     /** Format string to be tokenized        */
2017     private String str;
2018 
2019     /**
2020      * Construct a NumberFormatStringTokenizer.
2021      *
2022      * @param str Format string to be tokenized
2023      */
NumberFormatStringTokenizer(String str)2024     public NumberFormatStringTokenizer(String str)
2025     {
2026       this.str = str;
2027       maxPosition = str.length();
2028     }
2029 
2030     /**
2031      * Reset tokenizer so that nextToken() starts from the beginning.
2032      */
reset()2033     public void reset()
2034     {
2035       currentPosition = 0;
2036     }
2037 
2038     /**
2039      * Returns the next token from this string tokenizer.
2040      *
2041      * @return     the next token from this string tokenizer.
2042      * @throws  NoSuchElementException  if there are no more tokens in this
2043      *               tokenizer's string.
2044      */
nextToken()2045     public String nextToken()
2046     {
2047 
2048       if (currentPosition >= maxPosition)
2049       {
2050         throw new NoSuchElementException();
2051       }
2052 
2053       int start = currentPosition;
2054 
2055       while ((currentPosition < maxPosition)
2056              && Character.isLetterOrDigit(str.charAt(currentPosition)))
2057       {
2058         currentPosition++;
2059       }
2060 
2061       if ((start == currentPosition)
2062               && (!Character.isLetterOrDigit(str.charAt(currentPosition))))
2063       {
2064         currentPosition++;
2065       }
2066 
2067       return str.substring(start, currentPosition);
2068     }
2069 
2070     /**
2071      * Tells if there is a digit or a letter character ahead.
2072      *
2073      * @return     true if there is a number or character ahead.
2074      */
isLetterOrDigitAhead()2075     public boolean isLetterOrDigitAhead()
2076     {
2077 
2078       int pos = currentPosition;
2079 
2080       while (pos < maxPosition)
2081       {
2082         if (Character.isLetterOrDigit(str.charAt(pos)))
2083           return true;
2084 
2085         pos++;
2086       }
2087 
2088       return false;
2089     }
2090 
2091     /**
2092      * Tells if there is a digit or a letter character ahead.
2093      *
2094      * @return     true if there is a number or character ahead.
2095      */
nextIsSep()2096     public boolean nextIsSep()
2097     {
2098 
2099       if (Character.isLetterOrDigit(str.charAt(currentPosition)))
2100         return false;
2101       else
2102         return true;
2103     }
2104 
2105     /**
2106      * Tells if <code>nextToken</code> will throw an exception
2107      * if it is called.
2108      *
2109      * @return true if <code>nextToken</code> can be called
2110      * without throwing an exception.
2111      */
hasMoreTokens()2112     public boolean hasMoreTokens()
2113     {
2114       return (currentPosition >= maxPosition) ? false : true;
2115     }
2116 
2117     /**
2118      * Calculates the number of times that this tokenizer's
2119      * <code>nextToken</code> method can be called before it generates an
2120      * exception.
2121      *
2122      * @return  the number of tokens remaining in the string using the current
2123      *          delimiter set.
2124      * @see     java.util.StringTokenizer#nextToken()
2125      */
countTokens()2126     public int countTokens()
2127     {
2128 
2129       int count = 0;
2130       int currpos = currentPosition;
2131 
2132       while (currpos < maxPosition)
2133       {
2134         int start = currpos;
2135 
2136         while ((currpos < maxPosition)
2137                && Character.isLetterOrDigit(str.charAt(currpos)))
2138         {
2139           currpos++;
2140         }
2141 
2142         if ((start == currpos)
2143                 && (Character.isLetterOrDigit(str.charAt(currpos)) == false))
2144         {
2145           currpos++;
2146         }
2147 
2148         count++;
2149       }
2150 
2151       return count;
2152     }
2153   }  // end NumberFormatStringTokenizer
2154 
2155 
2156 
2157 }
2158