KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > xalan > templates > ElemNumber


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 /*
17  * $Id: ElemNumber.java,v 1.32 2004/02/16 20:32:32 minchau Exp $
18  */

19 package org.apache.xalan.templates;
20
21 import java.text.DecimalFormat JavaDoc;
22 import java.text.DecimalFormatSymbols JavaDoc;
23 import java.text.NumberFormat JavaDoc;
24 import java.util.Locale JavaDoc;
25 import java.util.NoSuchElementException JavaDoc;
26
27 import javax.xml.transform.TransformerException JavaDoc;
28
29 import org.apache.xalan.res.XSLTErrorResources;
30 import org.apache.xalan.transformer.CountersTable;
31 import org.apache.xalan.transformer.DecimalToRoman;
32 import org.apache.xalan.transformer.TransformerImpl;
33 import org.apache.xml.dtm.DTM;
34 import org.apache.xml.utils.FastStringBuffer;
35 import org.apache.xml.utils.NodeVector;
36 import org.apache.xml.utils.PrefixResolver;
37 import org.apache.xml.utils.StringBufferPool;
38 import org.apache.xml.utils.res.XResourceBundle;
39 import org.apache.xpath.NodeSetDTM;
40 import org.apache.xpath.XPath;
41 import org.apache.xpath.XPathContext;
42 import org.apache.xpath.objects.XObject;
43
44 import org.w3c.dom.Node JavaDoc;
45
46 import org.xml.sax.SAXException JavaDoc;
47
48 // import org.apache.xalan.dtm.*;
49

50 /**
51  * Implement xsl:number.
52  * <pre>
53  * <!ELEMENT xsl:number EMPTY>
54  * <!ATTLIST xsl:number
55  * level (single|multiple|any) "single"
56  * count %pattern; #IMPLIED
57  * from %pattern; #IMPLIED
58  * value %expr; #IMPLIED
59  * format %avt; '1'
60  * lang %avt; #IMPLIED
61  * letter-value %avt; #IMPLIED
62  * grouping-separator %avt; #IMPLIED
63  * grouping-size %avt; #IMPLIED
64  * >
65  * </pre>
66  * @see <a HREF="http://www.w3.org/TR/xslt#number">number in XSLT Specification</a>
67  * @xsl.usage advanced
68  */

69 public class ElemNumber extends ElemTemplateElement
70 {
71
72     private class MyPrefixResolver implements PrefixResolver {
73         
74         DTM dtm;
75         int handle;
76         boolean handleNullPrefix;
77         
78         /**
79          * Constructor for MyPrefixResolver.
80          * @param xpathExpressionContext
81          */

82         public MyPrefixResolver(Node JavaDoc xpathExpressionContext, DTM dtm, int handle, boolean handleNullPrefix) {
83             this.dtm = dtm;
84             this.handle = handle;
85             this.handleNullPrefix = handleNullPrefix;
86         }
87
88         /**
89          * @see PrefixResolver#getNamespaceForPrefix(String, Node)
90          */

91         public String JavaDoc getNamespaceForPrefix(String JavaDoc prefix) {
92             return dtm.getNamespaceURI(handle);
93         }
94         
95         /**
96          * @see PrefixResolver#getNamespaceForPrefix(String, Node)
97          * this shouldn't get called.
98          */

99         public String JavaDoc getNamespaceForPrefix(String JavaDoc prefix, Node JavaDoc context) {
100             return getNamespaceForPrefix(prefix);
101         }
102
103         /**
104          * @see PrefixResolver#getBaseIdentifier()
105          */

106         public String JavaDoc getBaseIdentifier() {
107             return ElemNumber.this.getBaseIdentifier();
108         }
109
110         /**
111          * @see PrefixResolver#handlesNullPrefixes()
112          */

113         public boolean handlesNullPrefixes() {
114             return handleNullPrefix;
115         }
116
117 }
118     
119   /**
120    * Only nodes are counted that match this pattern.
121    * @serial
122    */

123   private XPath m_countMatchPattern = null;
124
125   /**
126    * Set the "count" attribute.
127    * The count attribute is a pattern that specifies what nodes
128    * should be counted at those levels. If count attribute is not
129    * specified, then it defaults to the pattern that matches any
130    * node with the same node type as the current node and, if the
131    * current node has an expanded-name, with the same expanded-name
132    * as the current node.
133    *
134    * @param v Value to set for "count" attribute.
135    */

136   public void setCount(XPath v)
137   {
138     m_countMatchPattern = v;
139   }
140
141   /**
142    * Get the "count" attribute.
143    * The count attribute is a pattern that specifies what nodes
144    * should be counted at those levels. If count attribute is not
145    * specified, then it defaults to the pattern that matches any
146    * node with the same node type as the current node and, if the
147    * current node has an expanded-name, with the same expanded-name
148    * as the current node.
149    *
150    * @return Value of "count" attribute.
151    */

152   public XPath getCount()
153   {
154     return m_countMatchPattern;
155   }
156
157   /**
158    * Specifies where to count from.
159    * For level="single" or level="multiple":
160    * Only ancestors that are searched are
161    * those that are descendants of the nearest ancestor that matches
162    * the from pattern.
163    * For level="any:
164    * Only nodes after the first node before the
165    * current node that match the from pattern are considered.
166    * @serial
167    */

168   private XPath m_fromMatchPattern = null;
169
170   /**
171    * Set the "from" attribute. 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    *
180    * @param v Value to set for "from" attribute.
181    */

182   public void setFrom(XPath v)
183   {
184     m_fromMatchPattern = v;
185   }
186
187   /**
188    * Get the "from" attribute.
189    * For level="single" or level="multiple":
190    * Only ancestors that are searched are
191    * those that are descendants of the nearest ancestor that matches
192    * the from pattern.
193    * For level="any:
194    * Only nodes after the first node before the
195    * current node that match the from pattern are considered.
196    *
197    * @return Value of "from" attribute.
198    */

199   public XPath getFrom()
200   {
201     return m_fromMatchPattern;
202   }
203
204   /**
205    * When level="single", it goes up to the first node in the ancestor-or-self axis
206    * that matches the count pattern, and constructs a list of length one containing
207    * one plus the number of preceding siblings of that ancestor that match the count
208    * pattern. If there is no such ancestor, it constructs an empty list. If the from
209    * attribute is specified, then the only ancestors that are searched are those
210    * that are descendants of the nearest ancestor that matches the from pattern.
211    * Preceding siblings has the same meaning here as with the preceding-sibling axis.
212    *
213    * When level="multiple", it constructs a list of all ancestors of the current node
214    * in document order followed by the element itself; it then selects from the list
215    * those nodes that match the count pattern; it then maps each node in the list to
216    * one plus the number of preceding siblings of that node that match the count pattern.
217    * If the from attribute is specified, then the only ancestors that are searched are
218    * those that are descendants of the nearest ancestor that matches the from pattern.
219    * Preceding siblings has the same meaning here as with the preceding-sibling axis.
220    *
221    * When level="any", it constructs a list of length one containing the number of
222    * nodes that match the count pattern and belong to the set containing the current
223    * node and all nodes at any level of the document that are before the current node
224    * in document order, excluding any namespace and attribute nodes (in other words
225    * the union of the members of the preceding and ancestor-or-self axes). If the
226    * from attribute is specified, then only nodes after the first node before the
227    * current node that match the from pattern are considered.
228    * @serial
229    */

230   private int m_level = Constants.NUMBERLEVEL_SINGLE;
231
232   /**
233    * Set the "level" attribute.
234    * The level attribute specifies what levels of the source tree should
235    * be considered; it has the values single, multiple or any. The default
236    * is single.
237    *
238    * @param v Value to set for "level" attribute.
239    */

240   public void setLevel(int v)
241   {
242     m_level = v;
243   }
244
245   /**
246    * Get 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    * @return Value of "level" attribute.
252    */

253   public int getLevel()
254   {
255     return m_level;
256   }
257
258   /**
259    * The value attribute contains an expression. The expression is evaluated
260    * and the resulting object is converted to a number as if by a call to the
261    * number function.
262    * @serial
263    */

264   private XPath m_valueExpr = null;
265
266   /**
267    * Set the "value" attribute.
268    * The value attribute contains an expression. The expression is evaluated
269    * and the resulting object is converted to a number as if by a call to the
270    * number function.
271    *
272    * @param v Value to set for "value" attribute.
273    */

274   public void setValue(XPath v)
275   {
276     m_valueExpr = v;
277   }
278
279   /**
280    * Get 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    * @return Value of "value" attribute.
286    */

287   public XPath getValue()
288   {
289     return m_valueExpr;
290   }
291
292   /**
293    * The "format" attribute is used to control conversion of a list of
294    * numbers into a string.
295    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
296    * @serial
297    */

298   private AVT m_format_avt = null;
299
300   /**
301    * Set the "format" attribute.
302    * The "format" attribute is used to control conversion of a list of
303    * numbers into a string.
304    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
305    *
306    * @param v Value to set for "format" attribute.
307    */

308   public void setFormat(AVT v)
309   {
310     m_format_avt = v;
311   }
312
313   /**
314    * Get 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    * @return Value of "format" attribute.
320    */

321   public AVT getFormat()
322   {
323     return m_format_avt;
324   }
325
326   /**
327    * When numbering with an alphabetic sequence, the lang attribute
328    * specifies which language's alphabet is to be used.
329    * @serial
330    */

331   private AVT m_lang_avt = null;
332
333   /**
334    * Set the "lang" attribute.
335    * When numbering with an alphabetic sequence, the lang attribute
336    * specifies which language's alphabet is to be used; it has the same
337    * range of values as xml:lang [XML]; if no lang value is specified,
338    * the language should be determined from the system environment.
339    * Implementers should document for which languages they support numbering.
340    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
341    *
342    * @param v Value to set for "lang" attribute.
343    */

344   public void setLang(AVT v)
345   {
346     m_lang_avt = v;
347   }
348
349   /**
350    * Get the "lang" attribute.
351    * When numbering with an alphabetic sequence, the lang attribute
352    * specifies which language's alphabet is to be used; it has the same
353    * range of values as xml:lang [XML]; if no lang value is specified,
354    * the language should be determined from the system environment.
355    * Implementers should document for which languages they support numbering.
356    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
357    *
358    * @return Value ofr "lang" attribute.
359    */

360   public AVT getLang()
361   {
362     return m_lang_avt;
363   }
364
365   /**
366    * The letter-value attribute disambiguates between numbering
367    * sequences that use letters.
368    * @serial
369    */

370   private AVT m_lettervalue_avt = null;
371
372   /**
373    * Set the "letter-value" attribute.
374    * The letter-value attribute disambiguates between numbering sequences
375    * that use letters.
376    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
377    *
378    * @param v Value to set for "letter-value" attribute.
379    */

380   public void setLetterValue(AVT v)
381   {
382     m_lettervalue_avt = v;
383   }
384
385   /**
386    * Get 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    * @return Value to set for "letter-value" attribute.
392    */

393   public AVT getLetterValue()
394   {
395     return m_lettervalue_avt;
396   }
397
398   /**
399    * The grouping-separator attribute gives the separator
400    * used as a grouping (e.g. thousands) separator in decimal
401    * numbering sequences.
402    * @serial
403    */

404   private AVT m_groupingSeparator_avt = null;
405
406   /**
407    * Set the "grouping-separator" attribute.
408    * The grouping-separator attribute gives the separator
409    * used as a grouping (e.g. thousands) separator in decimal
410    * numbering sequences.
411    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
412    *
413    * @param v Value to set for "grouping-separator" attribute.
414    */

415   public void setGroupingSeparator(AVT v)
416   {
417     m_groupingSeparator_avt = v;
418   }
419
420   /**
421    * Get the "grouping-separator" attribute.
422    * The grouping-separator attribute gives the separator
423    * used as a grouping (e.g. thousands) separator in decimal
424    * numbering sequences.
425    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
426    *
427    * @return Value of "grouping-separator" attribute.
428    */

429   public AVT getGroupingSeparator()
430   {
431     return m_groupingSeparator_avt;
432   }
433
434   /**
435    * The optional grouping-size specifies the size (normally 3) of the grouping.
436    * @serial
437    */

438   private AVT m_groupingSize_avt = null;
439
440   /**
441    * Set the "grouping-size" attribute.
442    * The optional grouping-size specifies the size (normally 3) of the grouping.
443    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
444    *
445    * @param v Value to set for "grouping-size" attribute.
446    */

447   public void setGroupingSize(AVT v)
448   {
449     m_groupingSize_avt = v;
450   }
451
452   /**
453    * Get the "grouping-size" attribute.
454    * The optional grouping-size specifies the size (normally 3) of the grouping.
455    * @see <a HREF="http://www.w3.org/TR/xslt#convert">convert in XSLT Specification</a>
456    *
457    * @return Value of "grouping-size" attribute.
458    */

459   public AVT getGroupingSize()
460   {
461     return m_groupingSize_avt;
462   }
463
464   /**
465    * Shouldn't this be in the transformer? Big worries about threads...
466    */

467
468   // private XResourceBundle thisBundle;
469

470   /**
471    * Table to help in converting decimals to roman numerals.
472    * @see org.apache.xalan.transformer.DecimalToRoman
473    */

474   private final static DecimalToRoman m_romanConvertTable[] = {
475     new DecimalToRoman(1000, "M", 900, "CM"),
476     new DecimalToRoman(500, "D", 400, "CD"),
477     new DecimalToRoman(100L, "C", 90L, "XC"),
478     new DecimalToRoman(50L, "L", 40L, "XL"),
479     new DecimalToRoman(10L, "X", 9L, "IX"),
480     new DecimalToRoman(5L, "V", 4L, "IV"),
481     new DecimalToRoman(1L, "I", 1L, "I") };
482
483   /**
484    * Chars for converting integers into alpha counts.
485    * @see TransformerImpl#int2alphaCount
486    */

487   private static char[] m_alphaCountTable = null;
488   
489   /**
490    * This function is called after everything else has been
491    * recomposed, and allows the template to set remaining
492    * values that may be based on some other property that
493    * depends on recomposition.
494    */

495   public void compose(StylesheetRoot sroot) throws TransformerException JavaDoc
496   {
497     super.compose(sroot);
498     StylesheetRoot.ComposeState cstate = sroot.getComposeState();
499     java.util.Vector JavaDoc vnames = cstate.getVariableNames();
500     if(null != m_countMatchPattern)
501       m_countMatchPattern.fixupVariables(vnames, cstate.getGlobalsSize());
502     if(null != m_format_avt)
503       m_format_avt.fixupVariables(vnames, cstate.getGlobalsSize());
504     if(null != m_fromMatchPattern)
505       m_fromMatchPattern.fixupVariables(vnames, cstate.getGlobalsSize());
506     if(null != m_groupingSeparator_avt)
507       m_groupingSeparator_avt.fixupVariables(vnames, cstate.getGlobalsSize());
508     if(null != m_groupingSize_avt)
509       m_groupingSize_avt.fixupVariables(vnames, cstate.getGlobalsSize());
510     if(null != m_lang_avt)
511       m_lang_avt.fixupVariables(vnames, cstate.getGlobalsSize());
512     if(null != m_lettervalue_avt)
513       m_lettervalue_avt.fixupVariables(vnames, cstate.getGlobalsSize());
514     if(null != m_valueExpr)
515       m_valueExpr.fixupVariables(vnames, cstate.getGlobalsSize());
516   }
517
518
519   /**
520    * Get an int constant identifying the type of element.
521    * @see org.apache.xalan.templates.Constants
522    *
523    * @return The token ID for this element
524    */

525   public int getXSLToken()
526   {
527     return Constants.ELEMNAME_NUMBER;
528   }
529
530   /**
531    * Return the node name.
532    *
533    * @return The element's name
534    */

535   public String JavaDoc getNodeName()
536   {
537     return Constants.ELEMNAME_NUMBER_STRING;
538   }
539
540   /**
541    * Execute an xsl:number instruction. The xsl:number element is
542    * used to insert a formatted number into the result tree.
543    *
544    * @param transformer non-null reference to the the current transform-time state.
545    * @param sourceNode non-null reference to the <a HREF="http://www.w3.org/TR/xslt#dt-current-node">current source node</a>.
546    * @param mode reference, which may be null, to the <a HREF="http://www.w3.org/TR/xslt#modes">current mode</a>.
547    *
548    * @throws TransformerException
549    */

550   public void execute(
551           TransformerImpl transformer)
552             throws TransformerException JavaDoc
553   {
554
555      if (TransformerImpl.S_DEBUG)
556       transformer.getTraceManager().fireTraceEvent(this);
557
558     int sourceNode = transformer.getXPathContext().getCurrentNode();
559     String JavaDoc countString = getCountString(transformer, sourceNode);
560
561     try
562     {
563       transformer.getResultTreeHandler().characters(countString.toCharArray(),
564                                                     0, countString.length());
565     }
566     catch(SAXException JavaDoc se)
567     {
568       throw new TransformerException JavaDoc(se);
569     }
570     finally
571     {
572       if (TransformerImpl.S_DEBUG)
573         transformer.getTraceManager().fireTraceEndEvent(this);
574     }
575   }
576
577   /**
578    * Add a child to the child list.
579    *
580    * @param newChild Child to add to child list
581    *
582    * @return Child just added to child list
583    *
584    * @throws DOMException
585    */

586   public ElemTemplateElement appendChild(ElemTemplateElement newChild)
587   {
588
589     error(XSLTErrorResources.ER_CANNOT_ADD,
590           new Object JavaDoc[]{ newChild.getNodeName(),
591                         this.getNodeName() }); //"Can not add " +((ElemTemplateElement)newChild).m_elemName +
592

593     //" to " + this.m_elemName);
594
return null;
595   }
596
597   /**
598    * Given a 'from' pattern (ala xsl:number), a match pattern
599    * and a context, find the first ancestor that matches the
600    * pattern (including the context handed in).
601    *
602    * @param xctxt The XPath runtime state for this.
603    * @param fromMatchPattern The ancestor must match this pattern.
604    * @param countMatchPattern The ancestor must also match this pattern.
605    * @param context The node that "." expresses.
606    * @param namespaceContext The context in which namespaces in the
607    * queries are supposed to be expanded.
608    *
609    * @return the first ancestor that matches the given pattern
610    *
611    * @throws javax.xml.transform.TransformerException
612    */

613   int findAncestor(
614           XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern,
615           int context, ElemNumber namespaceContext)
616             throws javax.xml.transform.TransformerException JavaDoc
617   {
618     DTM dtm = xctxt.getDTM(context);
619     while (DTM.NULL != context)
620     {
621       if (null != fromMatchPattern)
622       {
623         if (fromMatchPattern.getMatchScore(xctxt, context)
624                 != XPath.MATCH_SCORE_NONE)
625         {
626
627           //context = null;
628
break;
629         }
630       }
631
632       if (null != countMatchPattern)
633       {
634         if (countMatchPattern.getMatchScore(xctxt, context)
635                 != XPath.MATCH_SCORE_NONE)
636         {
637           break;
638         }
639       }
640
641       context = dtm.getParent(context);
642     }
643
644     return context;
645   }
646
647   /**
648    * Given a 'from' pattern (ala xsl:number), a match pattern
649    * and a context, find the first ancestor that matches the
650    * pattern (including the context handed in).
651    * @param xctxt The XPath runtime state for this.
652    * @param fromMatchPattern The ancestor must match this pattern.
653    * @param countMatchPattern The ancestor must also match this pattern.
654    * @param context The node that "." expresses.
655    * @param namespaceContext The context in which namespaces in the
656    * queries are supposed to be expanded.
657    *
658    * @return the first preceding, ancestor or self node that
659    * matches the given pattern
660    *
661    * @throws javax.xml.transform.TransformerException
662    */

663   private int findPrecedingOrAncestorOrSelf(
664           XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern,
665           int context, ElemNumber namespaceContext)
666             throws javax.xml.transform.TransformerException JavaDoc
667   {
668     DTM dtm = xctxt.getDTM(context);
669     while (DTM.NULL != context)
670     {
671       if (null != fromMatchPattern)
672       {
673         if (fromMatchPattern.getMatchScore(xctxt, context)
674                 != XPath.MATCH_SCORE_NONE)
675         {
676           context = DTM.NULL;
677
678           break;
679         }
680       }
681
682       if (null != countMatchPattern)
683       {
684         if (countMatchPattern.getMatchScore(xctxt, context)
685                 != XPath.MATCH_SCORE_NONE)
686         {
687           break;
688         }
689       }
690
691       int prevSibling = dtm.getPreviousSibling(context);
692
693       if (DTM.NULL == prevSibling)
694       {
695         context = dtm.getParent(context);
696       }
697       else
698       {
699
700         // Now go down the chain of children of this sibling
701
context = dtm.getLastChild(prevSibling);
702
703         if (context == DTM.NULL)
704           context = prevSibling;
705       }
706     }
707
708     return context;
709   }
710
711   /**
712    * Get the count match pattern, or a default value.
713    *
714    * @param support The XPath runtime state for this.
715    * @param contextNode The node that "." expresses.
716    *
717    * @return the count match pattern, or a default value.
718    *
719    * @throws javax.xml.transform.TransformerException
720    */

721   XPath getCountMatchPattern(XPathContext support, int contextNode)
722           throws javax.xml.transform.TransformerException JavaDoc
723   {
724
725     XPath countMatchPattern = m_countMatchPattern;
726     DTM dtm = support.getDTM(contextNode);
727     if (null == countMatchPattern)
728     {
729       switch (dtm.getNodeType(contextNode))
730       {
731       case DTM.ELEMENT_NODE :
732         MyPrefixResolver resolver;
733
734         if (dtm.getNamespaceURI(contextNode) == null) {
735              resolver = new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, false);
736         } else {
737             resolver = new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, true);
738         }
739
740         countMatchPattern = new XPath(dtm.getNodeName(contextNode), this, resolver,
741                                       XPath.MATCH, support.getErrorListener());
742         break;
743
744       case DTM.ATTRIBUTE_NODE :
745
746         // countMatchPattern = m_stylesheet.createMatchPattern("@"+contextNode.getNodeName(), this);
747
countMatchPattern = new XPath("@" + dtm.getNodeName(contextNode), this,
748                                       this, XPath.MATCH, support.getErrorListener());
749         break;
750       case DTM.CDATA_SECTION_NODE :
751       case DTM.TEXT_NODE :
752
753         // countMatchPattern = m_stylesheet.createMatchPattern("text()", this);
754
countMatchPattern = new XPath("text()", this, this, XPath.MATCH, support.getErrorListener());
755         break;
756       case DTM.COMMENT_NODE :
757
758         // countMatchPattern = m_stylesheet.createMatchPattern("comment()", this);
759
countMatchPattern = new XPath("comment()", this, this, XPath.MATCH, support.getErrorListener());
760         break;
761       case DTM.DOCUMENT_NODE :
762
763         // countMatchPattern = m_stylesheet.createMatchPattern("/", this);
764
countMatchPattern = new XPath("/", this, this, XPath.MATCH, support.getErrorListener());
765         break;
766       case DTM.PROCESSING_INSTRUCTION_NODE :
767
768         // countMatchPattern = m_stylesheet.createMatchPattern("pi("+contextNode.getNodeName()+")", this);
769
countMatchPattern = new XPath("pi(" + dtm.getNodeName(contextNode)
770                                       + ")", this, this, XPath.MATCH, support.getErrorListener());
771         break;
772       default :
773         countMatchPattern = null;
774       }
775     }
776
777     return countMatchPattern;
778   }
779
780   /**
781    * Given an XML source node, get the count according to the
782    * parameters set up by the xsl:number attributes.
783    * @param transformer non-null reference to the the current transform-time state.
784    * @param sourceNode The source node being counted.
785    *
786    * @return The count of nodes
787    *
788    * @throws TransformerException
789    */

790   String JavaDoc getCountString(TransformerImpl transformer, int sourceNode)
791           throws TransformerException JavaDoc
792   {
793
794     long[] list = null;
795     XPathContext xctxt = transformer.getXPathContext();
796     CountersTable ctable = transformer.getCountersTable();
797
798     if (null != m_valueExpr)
799     {
800       XObject countObj = m_valueExpr.execute(xctxt, sourceNode, this);
801       long count = (long)java.lang.Math.floor(countObj.num()+ 0.5);
802
803       list = new long[1];
804       list[0] = count;
805     }
806     else
807     {
808       if (Constants.NUMBERLEVEL_ANY == m_level)
809       {
810         list = new long[1];
811         list[0] = ctable.countNode(xctxt, this, sourceNode);
812       }
813       else
814       {
815         NodeVector ancestors =
816           getMatchingAncestors(xctxt, sourceNode,
817                                Constants.NUMBERLEVEL_SINGLE == m_level);
818         int lastIndex = ancestors.size() - 1;
819
820         if (lastIndex >= 0)
821         {
822           list = new long[lastIndex + 1];
823
824           for (int i = lastIndex; i >= 0; i--)
825           {
826             int target = ancestors.elementAt(i);
827
828             list[lastIndex - i] = ctable.countNode(xctxt, this, target);
829           }
830         }
831       }
832     }
833
834     return (null != list)
835            ? formatNumberList(transformer, list, sourceNode) : "";
836   }
837
838   /**
839    * Get the previous node to be counted.
840    *
841    * @param xctxt The XPath runtime state for this.
842    * @param pos The current node
843    *
844    * @return the previous node to be counted.
845    *
846    * @throws TransformerException
847    */

848   public int getPreviousNode(XPathContext xctxt, int pos)
849           throws TransformerException JavaDoc
850   {
851
852     XPath countMatchPattern = getCountMatchPattern(xctxt, pos);
853     DTM dtm = xctxt.getDTM(pos);
854
855     if (Constants.NUMBERLEVEL_ANY == m_level)
856     {
857       XPath fromMatchPattern = m_fromMatchPattern;
858
859       // Do a backwards document-order walk 'till a node is found that matches
860
// the 'from' pattern, or a node is found that matches the 'count' pattern,
861
// or the top of the tree is found.
862
while (DTM.NULL != pos)
863       {
864
865         // Get the previous sibling, if there is no previous sibling,
866
// then count the parent, but if there is a previous sibling,
867
// dive down to the lowest right-hand (last) child of that sibling.
868
int next = dtm.getPreviousSibling(pos);
869
870         if (DTM.NULL == next)
871         {
872           next = dtm.getParent(pos);
873
874           if ((DTM.NULL != next) && ((((null != fromMatchPattern) && (fromMatchPattern.getMatchScore(
875                   xctxt, next) != XPath.MATCH_SCORE_NONE)))
876               || (dtm.getNodeType(next) == DTM.DOCUMENT_NODE)))
877           {
878             pos = DTM.NULL; // return null from function.
879

880             break; // from while loop
881
}
882         }
883         else
884         {
885
886           // dive down to the lowest right child.
887
int child = next;
888
889           while (DTM.NULL != child)
890           {
891             child = dtm.getLastChild(next);
892
893             if (DTM.NULL != child)
894               next = child;
895           }
896         }
897
898         pos = next;
899
900         if ((DTM.NULL != pos)
901                 && ((null == countMatchPattern)
902                     || (countMatchPattern.getMatchScore(xctxt, pos)
903                         != XPath.MATCH_SCORE_NONE)))
904         {
905           break;
906         }
907       }
908     }
909     else // NUMBERLEVEL_MULTI or NUMBERLEVEL_SINGLE
910
{
911       while (DTM.NULL != pos)
912       {
913         pos = dtm.getPreviousSibling(pos);
914
915         if ((DTM.NULL != pos)
916                 && ((null == countMatchPattern)
917                     || (countMatchPattern.getMatchScore(xctxt, pos)
918                         != XPath.MATCH_SCORE_NONE)))
919         {
920           break;
921         }
922       }
923     }
924
925     return pos;
926   }
927
928   /**
929    * Get the target node that will be counted..
930    *
931    * @param xctxt The XPath runtime state for this.
932    * @param sourceNode non-null reference to the <a HREF="http://www.w3.org/TR/xslt#dt-current-node">current source node</a>.
933    *
934    * @return the target node that will be counted
935    *
936    * @throws TransformerException
937    */

938   public int getTargetNode(XPathContext xctxt, int sourceNode)
939           throws TransformerException JavaDoc
940   {
941
942     int target = DTM.NULL;
943     XPath countMatchPattern = getCountMatchPattern(xctxt, sourceNode);
944
945     if (Constants.NUMBERLEVEL_ANY == m_level)
946     {
947       target = findPrecedingOrAncestorOrSelf(xctxt, m_fromMatchPattern,
948                                              countMatchPattern, sourceNode,
949                                              this);
950     }
951     else
952     {
953       target = findAncestor(xctxt, m_fromMatchPattern, countMatchPattern,
954                             sourceNode, this);
955     }
956
957     return target;
958   }
959
960   /**
961    * Get the ancestors, up to the root, that match the
962    * pattern.
963    *
964    * @param patterns if non-null, count only nodes
965    * that match this pattern, if null count all ancestors.
966    * @param xctxt The XPath runtime state for this.
967    * @param node Count this node and it's ancestors.
968    * @param stopAtFirstFound Flag indicating to stop after the
969    * first node is found (difference between level = single
970    * or multiple)
971    * @return The number of ancestors that match the pattern.
972    *
973    * @throws javax.xml.transform.TransformerException
974    */

975   NodeVector getMatchingAncestors(
976           XPathContext xctxt, int node, boolean stopAtFirstFound)
977             throws javax.xml.transform.TransformerException JavaDoc
978   {
979
980     NodeSetDTM ancestors = new NodeSetDTM(xctxt.getDTMManager());
981     XPath countMatchPattern = getCountMatchPattern(xctxt, node);
982     DTM dtm = xctxt.getDTM(node);
983
984     while (DTM.NULL != node)
985     {
986       if ((null != m_fromMatchPattern)
987               && (m_fromMatchPattern.getMatchScore(xctxt, node)
988                   != XPath.MATCH_SCORE_NONE))
989       {
990
991         // The following if statement gives level="single" different
992
// behavior from level="multiple", which seems incorrect according
993
// to the XSLT spec. For now we are leaving this in to replicate
994
// the same behavior in XT, but, for all intents and purposes we
995
// think this is a bug, or there is something about level="single"
996
// that we still don't understand.
997
if (!stopAtFirstFound)
998           break;
999       }
1000
1001      if (null == countMatchPattern)
1002        System.out.println(
1003          "Programmers error! countMatchPattern should never be null!");
1004
1005      if (countMatchPattern.getMatchScore(xctxt, node)
1006              != XPath.MATCH_SCORE_NONE)
1007      {
1008        ancestors.addElement(node);
1009
1010        if (stopAtFirstFound)
1011          break;
1012      }
1013
1014      node = dtm.getParent(node);
1015    }
1016
1017    return ancestors;
1018  } // end getMatchingAncestors method
1019

1020  /**
1021   * Get the locale we should be using.
1022   *
1023   * @param transformer non-null reference to the the current transform-time state.
1024   * @param contextNode The node that "." expresses.
1025   *
1026   * @return The locale to use. May be specified by "lang" attribute,
1027   * but if not, use default locale on the system.
1028   *
1029   * @throws TransformerException
1030   */

1031  Locale JavaDoc getLocale(TransformerImpl transformer, int contextNode)
1032          throws TransformerException JavaDoc
1033  {
1034
1035    Locale JavaDoc locale = null;
1036
1037    if (null != m_lang_avt)
1038    {
1039      XPathContext xctxt = transformer.getXPathContext();
1040      String JavaDoc langValue = m_lang_avt.evaluate(xctxt, contextNode, this);
1041
1042      if (null != langValue)
1043      {
1044
1045        // Not really sure what to do about the country code, so I use the
1046
// default from the system.
1047
// TODO: fix xml:lang handling.
1048
locale = new Locale JavaDoc(langValue.toUpperCase(), "");
1049
1050        //Locale.getDefault().getDisplayCountry());
1051
if (null == locale)
1052        {
1053          transformer.getMsgMgr().warn(this, null, xctxt.getDTM(contextNode).getNode(contextNode),
1054                                       XSLTErrorResources.WG_LOCALE_NOT_FOUND,
1055                                       new Object JavaDoc[]{ langValue }); //"Warning: Could not find locale for xml:lang="+langValue);
1056

1057          locale = Locale.getDefault();
1058        }
1059      }
1060    }
1061    else
1062    {
1063      locale = Locale.getDefault();
1064    }
1065
1066    return locale;
1067  }
1068
1069  /**
1070   * Get the number formatter to be used the format the numbers
1071   *
1072   * @param transformer non-null reference to the the current transform-time state.
1073   * @param contextNode The node that "." expresses.
1074   *
1075   * ($objectName$) @return The number formatter to be used
1076   *
1077   * @throws TransformerException
1078   */

1079  private DecimalFormat JavaDoc getNumberFormatter(
1080          TransformerImpl transformer, int contextNode) throws TransformerException JavaDoc
1081  {
1082    // Patch from Steven Serocki
1083
// Maybe we really want to do the clone in getLocale() and return
1084
// a clone of the default Locale??
1085
Locale JavaDoc locale = (Locale JavaDoc)getLocale(transformer, contextNode).clone();
1086
1087    // Helper to format local specific numbers to strings.
1088
DecimalFormat JavaDoc formatter = null;
1089
1090    //synchronized (locale)
1091
//{
1092
// formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale);
1093
//}
1094

1095    String JavaDoc digitGroupSepValue =
1096      (null != m_groupingSeparator_avt)
1097      ? m_groupingSeparator_avt.evaluate(
1098      transformer.getXPathContext(), contextNode, this) : null;
1099      
1100      
1101    // Validate grouping separator if an AVT was used; otherwise this was
1102
// validated statically in XSLTAttributeDef.java.
1103
if ((digitGroupSepValue != null) && (!m_groupingSeparator_avt.isSimple()) &&
1104        (digitGroupSepValue.length() != 1))
1105    {
1106            transformer.getMsgMgr().warn(
1107               this, XSLTErrorResources.WG_ILLEGAL_ATTRIBUTE_VALUE,
1108               new Object JavaDoc[]{ Constants.ATTRNAME_NAME, m_groupingSeparator_avt.getName()});
1109    }
1110      
1111      
1112    String JavaDoc nDigitsPerGroupValue =
1113      (null != m_groupingSize_avt)
1114      ? m_groupingSize_avt.evaluate(
1115      transformer.getXPathContext(), contextNode, this) : null;
1116
1117    // TODO: Handle digit-group attributes
1118
if ((null != digitGroupSepValue) && (null != nDigitsPerGroupValue) &&
1119        // Ignore if separation value is empty string
1120
(digitGroupSepValue.length() > 0))
1121    {
1122      try
1123      {
1124        formatter = (DecimalFormat JavaDoc) NumberFormat.getNumberInstance(locale);
1125        formatter.setGroupingSize(
1126          Integer.valueOf(nDigitsPerGroupValue).intValue());
1127        
1128        DecimalFormatSymbols JavaDoc symbols = formatter.getDecimalFormatSymbols();
1129        symbols.setGroupingSeparator(digitGroupSepValue.charAt(0));
1130        formatter.setDecimalFormatSymbols(symbols);
1131        formatter.setGroupingUsed(true);
1132      }
1133      catch (NumberFormatException JavaDoc ex)
1134      {
1135        formatter.setGroupingUsed(false);
1136      }
1137    }
1138
1139    return formatter;
1140  }
1141
1142  /**
1143   * Format a vector of numbers into a formatted string.
1144   *
1145   * @param xslNumberElement Element that takes %conversion-atts; attributes.
1146   * @param transformer non-null reference to the the current transform-time state.
1147   * @param list Array of one or more long integer numbers.
1148   * @param contextNode The node that "." expresses.
1149   * @return String that represents list according to
1150   * %conversion-atts; attributes.
1151   * TODO: Optimize formatNumberList so that it caches the last count and
1152   * reuses that info for the next count.
1153   *
1154   * @throws TransformerException
1155   */

1156  String JavaDoc formatNumberList(
1157          TransformerImpl transformer, long[] list, int contextNode)
1158            throws TransformerException JavaDoc
1159  {
1160
1161    String JavaDoc numStr;
1162    FastStringBuffer formattedNumber = StringBufferPool.get();
1163
1164    try
1165    {
1166      int nNumbers = list.length, numberWidth = 1;
1167      char numberType = '1';
1168      String JavaDoc formatToken, lastSepString = null, formatTokenString = null;
1169
1170      // If a seperator hasn't been specified, then use "."
1171
// as a default separator.
1172
// For instance: [2][1][5] with a format value of "1 "
1173
// should format to "2.1.5 " (I think).
1174
// Otherwise, use the seperator specified in the format string.
1175
// For instance: [2][1][5] with a format value of "01-001. "
1176
// should format to "02-001-005 ".
1177
String JavaDoc lastSep = ".";
1178      boolean isFirstToken = true; // true if first token
1179
String JavaDoc formatValue =
1180        (null != m_format_avt)
1181        ? m_format_avt.evaluate(
1182        transformer.getXPathContext(), contextNode, this) : null;
1183
1184      if (null == formatValue)
1185        formatValue = "1";
1186
1187      NumberFormatStringTokenizer formatTokenizer =
1188        new NumberFormatStringTokenizer(formatValue);
1189
1190      // int sepCount = 0; // keep track of seperators
1191
// Loop through all the numbers in the list.
1192
for (int i = 0; i < nNumbers; i++)
1193      {
1194
1195        // Loop to the next digit, letter, or separator.
1196
if (formatTokenizer.hasMoreTokens())
1197        {
1198          formatToken = formatTokenizer.nextToken();
1199
1200          // If the first character of this token is a character or digit, then
1201
// it is a number format directive.
1202
if (Character.isLetterOrDigit(
1203                  formatToken.charAt(formatToken.length() - 1)))
1204          {
1205            numberWidth = formatToken.length();
1206            numberType = formatToken.charAt(numberWidth - 1);
1207          }
1208
1209          // If there is a number format directive ahead,
1210
// then append the formatToken.
1211
else if (formatTokenizer.isLetterOrDigitAhead())
1212          {
1213            formatTokenString = formatToken;
1214
1215            // Append the formatToken string...
1216
// For instance [2][1][5] with a format value of "1--1. "
1217
// should format to "2--1--5. " (I guess).
1218
while (formatTokenizer.nextIsSep())
1219            {
1220              formatToken = formatTokenizer.nextToken();
1221              formatTokenString += formatToken;
1222            }
1223
1224            // Record this separator, so it can be used as the
1225
// next separator, if the next is the last.
1226
// For instance: [2][1][5] with a format value of "1-1 "
1227
// should format to "2-1-5 ".
1228
if (!isFirstToken)
1229              lastSep = formatTokenString;
1230
1231            // Since we know the next is a number or digit, we get it now.
1232
formatToken = formatTokenizer.nextToken();
1233            numberWidth = formatToken.length();
1234            numberType = formatToken.charAt(numberWidth - 1);
1235          }
1236          else // only separators left
1237
{
1238
1239            // Set up the string for the trailing characters after
1240
// the last number is formatted (i.e. after the loop).
1241
lastSepString = formatToken;
1242
1243            // And append any remaining characters to the lastSepString.
1244
while (formatTokenizer.hasMoreTokens())
1245            {
1246              formatToken = formatTokenizer.nextToken();
1247              lastSepString += formatToken;
1248            }
1249          } // else
1250
} // end if(formatTokenizer.hasMoreTokens())
1251

1252        // if this is the first token and there was a prefix
1253
// append the prefix else, append the separator
1254
// For instance, [2][1][5] with a format value of "(1-1.) "
1255
// should format to "(2-1-5.) " (I guess).
1256
if (null != formatTokenString && isFirstToken)
1257        {
1258          formattedNumber.append(formatTokenString);
1259        }
1260        else if (null != lastSep &&!isFirstToken)
1261          formattedNumber.append(lastSep);
1262
1263        getFormattedNumber(transformer, contextNode, numberType, numberWidth,
1264                           list[i], formattedNumber);
1265
1266        isFirstToken = false; // After the first pass, this should be false
1267
} // end for loop
1268

1269      // Check to see if we finished up the format string...
1270
// Skip past all remaining letters or digits
1271
while (formatTokenizer.isLetterOrDigitAhead())
1272      {
1273        formatTokenizer.nextToken();
1274      }
1275
1276      if (lastSepString != null)
1277        formattedNumber.append(lastSepString);
1278
1279      while (formatTokenizer.hasMoreTokens())
1280      {
1281        formatToken = formatTokenizer.nextToken();
1282
1283        formattedNumber.append(formatToken);
1284      }
1285
1286      numStr = formattedNumber.toString();
1287    }
1288    finally
1289    {
1290      StringBufferPool.free(formattedNumber);
1291    }
1292
1293    return numStr;
1294  } // end formatNumberList method
1295

1296  /*
1297  * Get Formatted number
1298  */

1299
1300  /**
1301   * Format the given number and store it in the given buffer
1302   *
1303   *
1304   * @param transformer non-null reference to the the current transform-time state.
1305   * @param contextNode The node that "." expresses.
1306   * @param numberType Type to format to
1307   * @param numberWidth Maximum length of formatted number
1308   * @param listElement Number to format
1309   * @param formattedNumber Buffer to store formatted number
1310   *
1311   * @throws javax.xml.transform.TransformerException
1312   */

1313  private void getFormattedNumber(
1314          TransformerImpl transformer, int contextNode,
1315          char numberType, int numberWidth, long listElement,
1316          FastStringBuffer formattedNumber)
1317            throws javax.xml.transform.TransformerException JavaDoc
1318  {
1319
1320
1321    String JavaDoc letterVal =
1322      (m_lettervalue_avt != null)
1323      ? m_lettervalue_avt.evaluate(
1324      transformer.getXPathContext(), contextNode, this) : null;
1325
1326    switch (numberType)
1327    {
1328    case 'A' :
1329      if (m_alphaCountTable == null)
1330      {
1331        XResourceBundle thisBundle;
1332
1333        thisBundle =
1334          (XResourceBundle) XResourceBundle.loadResourceBundle(
1335            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, getLocale(transformer, contextNode));
1336
1337        char[] alphabet;
1338
1339        alphabet = (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET);
1340        m_alphaCountTable = alphabet;
1341      }
1342
1343      int2alphaCount(listElement, m_alphaCountTable, formattedNumber);
1344      break;
1345    case 'a' :
1346      if (m_alphaCountTable == null)
1347      {
1348        XResourceBundle thisBundle;
1349
1350        thisBundle =
1351          (XResourceBundle) XResourceBundle.loadResourceBundle(
1352            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, getLocale(transformer, contextNode));
1353
1354        char[] alphabet;
1355
1356        alphabet = (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET);
1357        m_alphaCountTable = alphabet;
1358      }
1359
1360      FastStringBuffer stringBuf = StringBufferPool.get();
1361
1362      try
1363      {
1364        int2alphaCount(listElement, m_alphaCountTable, stringBuf);
1365        formattedNumber.append(
1366          stringBuf.toString().toLowerCase(
1367            getLocale(transformer, contextNode)));
1368      }
1369      finally
1370      {
1371        StringBufferPool.free(stringBuf);
1372      }
1373      break;
1374    case 'I' :
1375      formattedNumber.append(long2roman(listElement, true));
1376      break;
1377    case 'i' :
1378      formattedNumber.append(
1379        long2roman(listElement, true).toLowerCase(
1380          getLocale(transformer, contextNode)));
1381      break;
1382    case 0x3042 :
1383    {
1384      XResourceBundle thisBundle;
1385
1386      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1387        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("ja", "JP", "HA"));
1388
1389      if (letterVal != null
1390              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1391        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1392      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1393
formattedNumber.append(
1394          int2singlealphaCount(
1395            listElement,
1396            (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1397
1398      break;
1399    }
1400    case 0x3044 :
1401    {
1402      XResourceBundle thisBundle;
1403
1404      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1405        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("ja", "JP", "HI"));
1406
1407      if ((letterVal != null)
1408              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1409        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1410      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1411
formattedNumber.append(
1412          int2singlealphaCount(
1413            listElement,
1414            (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1415
1416      break;
1417    }
1418    case 0x30A2 :
1419    {
1420      XResourceBundle thisBundle;
1421
1422      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1423        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("ja", "JP", "A"));
1424
1425      if (letterVal != null
1426              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1427        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1428      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1429
formattedNumber.append(
1430          int2singlealphaCount(
1431            listElement,
1432            (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1433
1434      break;
1435    }
1436    case 0x30A4 :
1437    {
1438      XResourceBundle thisBundle;
1439
1440      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1441        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("ja", "JP", "I"));
1442
1443      if (letterVal != null
1444              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1445        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1446      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1447
formattedNumber.append(
1448          int2singlealphaCount(
1449            listElement,
1450            (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1451
1452      break;
1453    }
1454    case 0x4E00 :
1455    {
1456      XResourceBundle thisBundle;
1457
1458      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1459        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("zh", "CN"));
1460
1461      if (letterVal != null
1462              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1463      {
1464        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1465      }
1466      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1467
int2alphaCount(listElement,
1468                       (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1469                       formattedNumber);
1470
1471      break;
1472    }
1473    case 0x58F9 :
1474    {
1475      XResourceBundle thisBundle;
1476
1477      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1478        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("zh", "TW"));
1479
1480      if (letterVal != null
1481              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1482        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1483      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1484
int2alphaCount(listElement,
1485                       (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1486                       formattedNumber);
1487
1488      break;
1489    }
1490    case 0x0E51 :
1491    {
1492      XResourceBundle thisBundle;
1493
1494      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1495        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("th", ""));
1496
1497      if (letterVal != null
1498              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1499        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1500      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1501
int2alphaCount(listElement,
1502                       (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1503                       formattedNumber);
1504
1505      break;
1506    }
1507    case 0x05D0 :
1508    {
1509      XResourceBundle thisBundle;
1510
1511      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1512        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("he", ""));
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                       (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1520                       formattedNumber);
1521
1522      break;
1523    }
1524    case 0x10D0 :
1525    {
1526      XResourceBundle thisBundle;
1527
1528      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1529        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("ka", ""));
1530
1531      if (letterVal != null
1532              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1533        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1534      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1535
int2alphaCount(listElement,
1536                       (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1537                       formattedNumber);
1538
1539      break;
1540    }
1541    case 0x03B1 :
1542    {
1543      XResourceBundle thisBundle;
1544
1545      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1546        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("el", ""));
1547
1548      if (letterVal != null
1549              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1550        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1551      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1552
int2alphaCount(listElement,
1553                       (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1554                       formattedNumber);
1555
1556      break;
1557    }
1558    case 0x0430 :
1559    {
1560      XResourceBundle thisBundle;
1561
1562      thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1563        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale JavaDoc("cy", ""));
1564
1565      if (letterVal != null
1566              && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1567        formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1568      else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1569
int2alphaCount(listElement,
1570                       (char[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1571                       formattedNumber);
1572
1573      break;
1574    }
1575    default : // "1"
1576
DecimalFormat JavaDoc formatter = getNumberFormatter(transformer, contextNode);
1577      String JavaDoc padString = formatter == null ? String.valueOf(0) : formatter.format(0);
1578      String JavaDoc numString = formatter == null ? String.valueOf(listElement) : formatter.format(listElement);
1579      int nPadding = numberWidth - numString.length();
1580
1581      for (int k = 0; k < nPadding; k++)
1582      {
1583        formattedNumber.append(padString);
1584      }
1585
1586      formattedNumber.append(numString);
1587    }
1588  }
1589  
1590  /**
1591   * Get a string value for zero, which is not really defined by the 1.0 spec,
1592   * thought I think it might be cleared up by the erreta.
1593   */

1594   String JavaDoc getZeroString()
1595   {
1596     return ""+0;
1597   }
1598
1599  /**
1600   * Convert a long integer into alphabetic counting, in other words
1601   * count using the sequence A B C ... Z.
1602   *
1603   * @param val Value to convert -- must be greater than zero.
1604   * @param table a table containing one character for each digit in the radix
1605   * @return String representing alpha count of number.
1606   * @see TransformerImpl#DecimalToRoman
1607   *
1608   * Note that the radix of the conversion is inferred from the size
1609   * of the table.
1610   */

1611  protected String JavaDoc int2singlealphaCount(long val, char[] table)
1612  {
1613
1614    int radix = table.length;
1615
1616    // TODO: throw error on out of range input
1617
if (val > radix)
1618    {
1619      return getZeroString();
1620    }
1621    else
1622      return (new Character JavaDoc(table[(int)val - 1])).toString(); // index into table is off one, starts at 0
1623
}
1624
1625  /**
1626   * Convert a long integer into alphabetic counting, in other words
1627   * count using the sequence A B C ... Z AA AB AC.... etc.
1628   *
1629   * @param val Value to convert -- must be greater than zero.
1630   * @param table a table containing one character for each digit in the radix
1631   * @param aTable Array of alpha characters representing numbers
1632   * @param stringBuf Buffer where to save the string representing alpha count of number.
1633   *
1634   * @see TransformerImpl#DecimalToRoman
1635   *
1636   * Note that the radix of the conversion is inferred from the size
1637   * of the table.
1638   */

1639  protected void int2alphaCount(long val, char[] aTable,
1640                                FastStringBuffer stringBuf)
1641  {
1642
1643    int radix = aTable.length;
1644    char[] table = new char[aTable.length];
1645
1646    // start table at 1, add last char at index 0. Reason explained above and below.
1647
int i;
1648
1649    for (i = 0; i < aTable.length - 1; i++)
1650    {
1651      table[i + 1] = aTable[i];
1652    }
1653
1654    table[0] = aTable[i];
1655
1656    // Create a buffer to hold the result
1657
// TODO: size of the table can be detereined by computing
1658
// logs of the radix. For now, we fake it.
1659
char buf[] = new char[100];
1660
1661    //some languages go left to right(ie. english), right to left (ie. Hebrew),
1662
//top to bottom (ie.Japanese), etc... Handle them differently
1663
//String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION);
1664
// next character to set in the buffer
1665
int charPos;
1666
1667    charPos = buf.length - 1; // work backward through buf[]
1668

1669    // index in table of the last character that we stored
1670
int lookupIndex = 1; // start off with anything other than zero to make correction work
1671

1672    // Correction number
1673
//
1674
// Correction can take on exactly two values:
1675
//
1676
// 0 if the next character is to be emitted is usual
1677
//
1678
// radix - 1
1679
// if the next char to be emitted should be one less than
1680
// you would expect
1681
//
1682
// For example, consider radix 10, where 1="A" and 10="J"
1683
//
1684
// In this scheme, we count: A, B, C ... H, I, J (not A0 and certainly
1685
// not AJ), A1
1686
//
1687
// So, how do we keep from emitting AJ for 10? After correctly emitting the
1688
// J, lookupIndex is zero. We now compute a correction number of 9 (radix-1).
1689
// In the following line, we'll compute (val+correction) % radix, which is,
1690
// (val+9)/10. By this time, val is 1, so we compute (1+9) % 10, which
1691
// is 10 % 10 or zero. So, we'll prepare to emit "JJ", but then we'll
1692
// later suppress the leading J as representing zero (in the mod system,
1693
// it can represent either 10 or zero). In summary, the correction value of
1694
// "radix-1" acts like "-1" when run through the mod operator, but with the
1695
// desireable characteristic that it never produces a negative number.
1696
long correction = 0;
1697
1698    // TODO: throw error on out of range input
1699
do
1700    {
1701
1702      // most of the correction calculation is explained above, the reason for the
1703
// term after the "|| " is that it correctly propagates carries across
1704
// multiple columns.
1705
correction =
1706        ((lookupIndex == 0) || (correction != 0 && lookupIndex == radix - 1))
1707        ? (radix - 1) : 0;
1708
1709      // index in "table" of the next char to emit
1710
lookupIndex = (int)(val + correction) % radix;
1711
1712      // shift input by one "column"
1713
val = (val / radix);
1714
1715      // if the next value we'd put out would be a leading zero, we're done.
1716
if (lookupIndex == 0 && val == 0)
1717        break;
1718
1719      // put out the next character of output
1720
buf[charPos--] = table[lookupIndex]; // left to right or top to bottom
1721
}
1722    while (val > 0);
1723
1724    stringBuf.append(buf, charPos + 1, (buf.length - charPos - 1));
1725  }
1726
1727  /**
1728   * Convert a long integer into traditional alphabetic counting, in other words
1729   * count using the traditional numbering.
1730   *
1731   * @param val Value to convert -- must be greater than zero.
1732   * @param table a table containing one character for each digit in the radix
1733   * @param thisBundle Resource bundle to use
1734   *
1735   * @return String representing alpha count of number.
1736   * @see XSLProcessor#DecimalToRoman
1737   *
1738   * Note that the radix of the conversion is inferred from the size
1739   * of the table.
1740   */

1741  protected String JavaDoc tradAlphaCount(long val, XResourceBundle thisBundle)
1742  {
1743
1744    // if this number is larger than the largest number we can represent, error!
1745
if (val > Long.MAX_VALUE)
1746    {
1747      this.error(XSLTErrorResources.ER_NUMBER_TOO_BIG);
1748      return XSLTErrorResources.ERROR_STRING;
1749    }
1750    char[] table = null;
1751
1752    // index in table of the last character that we stored
1753
int lookupIndex = 1; // start off with anything other than zero to make correction work
1754

1755    // Create a buffer to hold the result
1756
// TODO: size of the table can be detereined by computing
1757
// logs of the radix. For now, we fake it.
1758
char buf[] = new char[100];
1759
1760    //some languages go left to right(ie. english), right to left (ie. Hebrew),
1761
//top to bottom (ie.Japanese), etc... Handle them differently
1762
//String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION);
1763
// next character to set in the buffer
1764
int charPos;
1765
1766    charPos = 0; //start at 0
1767

1768    // array of number groups: ie.1000, 100, 10, 1
1769
int[] groups = (int[]) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERGROUPS);
1770
1771    // array of tables of hundreds, tens, digits...
1772
String JavaDoc[] tables =
1773      (String JavaDoc[]) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUM_TABLES));
1774
1775    //some languages have additive alphabetical notation,
1776
//some multiplicative-additive, etc... Handle them differently.
1777
String JavaDoc numbering = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERING);
1778
1779    // do multiplicative part first
1780
if (numbering.equals(org.apache.xml.utils.res.XResourceBundle.LANG_MULT_ADD))
1781    {
1782      String JavaDoc mult_order = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.MULT_ORDER);
1783      long[] multiplier =
1784        (long[]) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER));
1785      char[] zeroChar = (char[]) thisBundle.getObject("zero");
1786      int i = 0;
1787
1788      // skip to correct multiplier
1789
while (i < multiplier.length && val < multiplier[i])
1790      {
1791        i++;
1792      }
1793
1794      do
1795      {
1796        if (i >= multiplier.length)
1797          break; //number is smaller than multipliers
1798

1799        // some languages (ie chinese) put a zero character (and only one) when
1800
// the multiplier is multiplied by zero. (ie, 1001 is 1X1000 + 0X100 + 0X10 + 1)
1801
// 0X100 is replaced by the zero character, we don't need one for 0X10
1802
if (val < multiplier[i])
1803        {
1804          if (zeroChar.length == 0)
1805          {
1806            i++;
1807          }
1808          else
1809          {
1810            if (buf[charPos - 1] != zeroChar[0])
1811              buf[charPos++] = zeroChar[0];
1812
1813            i++;
1814          }
1815        }
1816        else if (val >= multiplier[i])
1817        {
1818          long mult = val / multiplier[i];
1819
1820          val = val % multiplier[i]; // save this.
1821

1822          int k = 0;
1823
1824          while (k < groups.length)
1825          {
1826            lookupIndex = 1; // initialize for each table
1827

1828            if (mult / groups[k] <= 0) // look for right table
1829
k++;
1830            else
1831            {
1832
1833              // get the table
1834
char[] THEletters = (char[]) thisBundle.getObject(tables[k]);
1835
1836              table = new char[THEletters.length + 1];
1837
1838              int j;
1839
1840              for (j = 0; j < THEletters.length; j++)
1841              {
1842                table[j + 1] = THEletters[j];
1843              }
1844
1845              table[0] = THEletters[j - 1]; // don't need this
1846

1847              // index in "table" of the next char to emit
1848
lookupIndex = (int)mult / groups[k];
1849
1850              //this should not happen
1851
if (lookupIndex == 0 && mult == 0)
1852                break;
1853
1854              char multiplierChar = ((char[]) (thisBundle.getObject(
1855                org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER_CHAR)))[i];
1856
1857              // put out the next character of output
1858
if (lookupIndex < table.length)
1859              {
1860                if (mult_order.equals(org.apache.xml.utils.res.XResourceBundle.MULT_PRECEDES))
1861                {
1862                  buf[charPos++] = multiplierChar;
1863                  buf[charPos++] = table[lookupIndex];
1864                }
1865                else
1866                {
1867
1868                  // don't put out 1 (ie 1X10 is just 10)
1869
if (lookupIndex == 1 && i == multiplier.length - 1){}
1870                  else
1871                    buf[charPos++] = table[lookupIndex];
1872
1873                  buf[charPos++] = multiplierChar;
1874                }
1875
1876                break; // all done!
1877
}
1878              else
1879                return XSLTErrorResources.ERROR_STRING;
1880            } //end else
1881
} // end while
1882

1883          i++;
1884        } // end else if
1885
} // end do while
1886
while (i < multiplier.length);
1887    }
1888
1889    // Now do additive part...
1890
int count = 0;
1891    String JavaDoc tableName;
1892
1893    // do this for each table of hundreds, tens, digits...
1894
while (count < groups.length)
1895    {
1896      if (val / groups[count] <= 0) // look for correct table
1897
count++;
1898      else
1899      {
1900        char[] theletters = (char[]) thisBundle.getObject(tables[count]);
1901
1902        table = new char[theletters.length + 1];
1903
1904        int j;
1905
1906        // need to start filling the table up at index 1
1907
for (j = 0; j < theletters.length; j++)
1908        {
1909          table[j + 1] = theletters[j];
1910        }
1911
1912        table[0] = theletters[j - 1]; // don't need this
1913

1914        // index in "table" of the next char to emit
1915
lookupIndex = (int)val / groups[count];
1916
1917        // shift input by one "column"
1918
val = val % groups[count];
1919
1920        // this should not happen
1921
if (lookupIndex == 0 && val == 0)
1922          break;
1923
1924        if (lookupIndex < table.length)
1925        {
1926
1927          // put out the next character of output
1928
buf[charPos++] = table[lookupIndex]; // left to right or top to bottom
1929
}
1930        else
1931          return XSLTErrorResources.ERROR_STRING;
1932
1933        count++;
1934      }
1935    } // end while
1936

1937    // String s = new String(buf, 0, charPos);
1938
return new String JavaDoc(buf, 0, charPos);
1939  }
1940
1941  /**
1942   * Convert a long integer into roman numerals.
1943   * @param val Value to convert.
1944   * @param prefixesAreOK true_ to enable prefix notation (e.g. 4 = "IV"),
1945   * false_ to disable prefix notation (e.g. 4 = "IIII").
1946   * @return Roman numeral string.
1947   * @see DecimalToRoman
1948   * @see m_romanConvertTable
1949   */

1950  protected String JavaDoc long2roman(long val, boolean prefixesAreOK)
1951  {
1952
1953    if (val <= 0)
1954    {
1955      return getZeroString();
1956    }
1957
1958    String JavaDoc roman = "";
1959    int place = 0;
1960
1961    if (val <= 3999L)
1962    {
1963      do
1964      {
1965        while (val >= m_romanConvertTable[place].m_postValue)
1966        {
1967          roman += m_romanConvertTable[place].m_postLetter;
1968          val -= m_romanConvertTable[place].m_postValue;
1969        }
1970
1971        if (prefixesAreOK)
1972        {
1973          if (val >= m_romanConvertTable[place].m_preValue)
1974          {
1975            roman += m_romanConvertTable[place].m_preLetter;
1976            val -= m_romanConvertTable[place].m_preValue;
1977          }
1978        }
1979
1980        place++;
1981      }
1982      while (val > 0);
1983    }
1984    else
1985    {
1986      roman = XSLTErrorResources.ERROR_STRING;
1987    }
1988
1989    return roman;
1990  } // end long2roman
1991

1992  /**
1993   * Call the children visitors.
1994   * @param visitor The visitor whose appropriate method will be called.
1995   */

1996  public void callChildVisitors(XSLTVisitor visitor, boolean callAttrs)
1997  {
1998    if(callAttrs)
1999    {
2000        if(null != m_countMatchPattern)
2001            m_countMatchPattern.getExpression().callVisitors(m_countMatchPattern, visitor);
2002        if(null != m_fromMatchPattern)
2003            m_fromMatchPattern.getExpression().callVisitors(m_fromMatchPattern, visitor);
2004        if(null != m_valueExpr)
2005            m_valueExpr.getExpression().callVisitors(m_valueExpr, visitor);
2006    
2007        if(null != m_format_avt)
2008            m_format_avt.callVisitors(visitor);
2009        if(null != m_groupingSeparator_avt)
2010            m_groupingSeparator_avt.callVisitors(visitor);
2011        if(null != m_groupingSize_avt)
2012            m_groupingSize_avt.callVisitors(visitor);
2013        if(null != m_lang_avt)
2014            m_lang_avt.callVisitors(visitor);
2015        if(null != m_lettervalue_avt)
2016            m_lettervalue_avt.callVisitors(visitor);
2017    }
2018
2019    super.callChildVisitors(visitor, callAttrs);
2020  }
2021
2022
2023  /**
2024   * This class returns tokens using non-alphanumberic
2025   * characters as delimiters.
2026   */

2027  class NumberFormatStringTokenizer
2028  {
2029
2030    /** Current position in the format string */
2031    private int currentPosition;
2032
2033    /** Index of last character in the format string */
2034    private int maxPosition;
2035
2036    /** Format string to be tokenized */
2037    private String JavaDoc str;
2038
2039    /**
2040     * Construct a NumberFormatStringTokenizer.
2041     *
2042     * @param str Format string to be tokenized
2043     */

2044    public NumberFormatStringTokenizer(String JavaDoc str)
2045    {
2046      this.str = str;
2047      maxPosition = str.length();
2048    }
2049
2050    /**
2051     * Reset tokenizer so that nextToken() starts from the beginning.
2052     */

2053    public void reset()
2054    {
2055      currentPosition = 0;
2056    }
2057
2058    /**
2059     * Returns the next token from this string tokenizer.
2060     *
2061     * @return the next token from this string tokenizer.
2062     * @throws NoSuchElementException if there are no more tokens in this
2063     * tokenizer's string.
2064     */

2065    public String JavaDoc nextToken()
2066    {
2067
2068      if (currentPosition >= maxPosition)
2069      {
2070        throw new NoSuchElementException JavaDoc();
2071      }
2072
2073      int start = currentPosition;
2074
2075      while ((currentPosition < maxPosition)
2076             && Character.isLetterOrDigit(str.charAt(currentPosition)))
2077      {
2078        currentPosition++;
2079      }
2080
2081      if ((start == currentPosition)
2082              && (!Character.isLetterOrDigit(str.charAt(currentPosition))))
2083      {
2084        currentPosition++;
2085      }
2086
2087      return str.substring(start, currentPosition);
2088    }
2089
2090    /**
2091     * Tells if there is a digit or a letter character ahead.
2092     *
2093     * @return true if there is a number or character ahead.
2094     */

2095    public boolean isLetterOrDigitAhead()
2096    {
2097
2098      int pos = currentPosition;
2099
2100      while (pos < maxPosition)
2101      {
2102        if (Character.isLetterOrDigit(str.charAt(pos)))
2103          return true;
2104
2105        pos++;
2106      }
2107
2108      return false;
2109    }
2110
2111    /**
2112     * Tells if there is a digit or a letter character ahead.
2113     *
2114     * @return true if there is a number or character ahead.
2115     */

2116    public boolean nextIsSep()
2117    {
2118
2119      if (Character.isLetterOrDigit(str.charAt(currentPosition)))
2120        return false;
2121      else
2122        return true;
2123    }
2124
2125    /**
2126     * Tells if <code>nextToken</code> will throw an exception
2127     * if it is called.
2128     *
2129     * @return true if <code>nextToken</code> can be called
2130     * without throwing an exception.
2131     */

2132    public boolean hasMoreTokens()
2133    {
2134      return (currentPosition >= maxPosition) ? false : true;
2135    }
2136
2137    /**
2138     * Calculates the number of times that this tokenizer's
2139     * <code>nextToken</code> method can be called before it generates an
2140     * exception.
2141     *
2142     * @return the number of tokens remaining in the string using the current
2143     * delimiter set.
2144     * @see java.util.StringTokenizer#nextToken()
2145     */

2146    public int countTokens()
2147    {
2148
2149      int count = 0;
2150      int currpos = currentPosition;
2151
2152      while (currpos < maxPosition)
2153      {
2154        int start = currpos;
2155
2156        while ((currpos < maxPosition)
2157               && Character.isLetterOrDigit(str.charAt(currpos)))
2158        {
2159          currpos++;
2160        }
2161
2162        if ((start == currpos)
2163                && (Character.isLetterOrDigit(str.charAt(currpos)) == false))
2164        {
2165          currpos++;
2166        }
2167
2168        count++;
2169      }
2170
2171      return count;
2172    }
2173  } // end NumberFormatStringTokenizer
2174

2175
2176
2177}
2178
Popular Tags