001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: ElemNumber.java 1225442 2011-12-29 05:36:43Z mrglavas $
020     */
021    package org.apache.xalan.templates;
022    
023    import java.text.DecimalFormat;
024    import java.text.DecimalFormatSymbols;
025    import java.text.NumberFormat;
026    import java.util.Locale;
027    import java.util.NoSuchElementException;
028    
029    import javax.xml.transform.TransformerException;
030    
031    import org.apache.xalan.res.XSLTErrorResources;
032    import org.apache.xalan.transformer.CountersTable;
033    import org.apache.xalan.transformer.DecimalToRoman;
034    import org.apache.xalan.transformer.TransformerImpl;
035    import org.apache.xml.dtm.DTM;
036    import org.apache.xml.utils.FastStringBuffer;
037    import org.apache.xml.utils.NodeVector;
038    import org.apache.xml.utils.PrefixResolver;
039    import org.apache.xml.utils.StringBufferPool;
040    import org.apache.xml.utils.res.XResourceBundle;
041    import org.apache.xml.utils.res.CharArrayWrapper;
042    import org.apache.xml.utils.res.IntArrayWrapper;
043    import org.apache.xml.utils.res.LongArrayWrapper;
044    import org.apache.xml.utils.res.StringArrayWrapper;
045    import org.apache.xpath.NodeSetDTM;
046    import org.apache.xpath.XPath;
047    import org.apache.xpath.XPathContext;
048    import org.apache.xpath.objects.XObject;
049    
050    import org.w3c.dom.Node;
051    
052    import org.xml.sax.SAXException;
053    
054    // import org.apache.xalan.dtm.*;
055    
056    /**
057     * Implement xsl:number.
058     * <pre>
059     * <!ELEMENT xsl:number EMPTY>
060     * <!ATTLIST xsl:number
061     *    level (single|multiple|any) "single"
062     *    count %pattern; #IMPLIED
063     *    from %pattern; #IMPLIED
064     *    value %expr; #IMPLIED
065     *    format %avt; '1'
066     *    lang %avt; #IMPLIED
067     *    letter-value %avt; #IMPLIED
068     *    grouping-separator %avt; #IMPLIED
069     *    grouping-size %avt; #IMPLIED
070     * >
071     * </pre>
072     * @see <a href="http://www.w3.org/TR/xslt#number">number in XSLT Specification</a>
073     * @xsl.usage advanced
074     */
075    public class ElemNumber extends ElemTemplateElement 
076    {
077        static final long serialVersionUID = 8118472298274407610L;
078    
079        /**
080         * Chars for converting integers into alpha counts.
081         * @see TransformerImpl#int2alphaCount
082         */
083        private CharArrayWrapper m_alphaCountTable = null;
084        
085        private class MyPrefixResolver implements PrefixResolver {
086            
087            DTM dtm;
088            int handle;
089            boolean handleNullPrefix;
090            
091                    /**
092                     * Constructor for MyPrefixResolver.
093                     * @param xpathExpressionContext
094                     */
095                    public MyPrefixResolver(Node xpathExpressionContext, DTM dtm, int handle, boolean handleNullPrefix) {
096                this.dtm = dtm;
097                this.handle = handle;
098                this.handleNullPrefix = handleNullPrefix;
099                    }
100    
101            /**
102                     * @see PrefixResolver#getNamespaceForPrefix(String, Node)
103                     */
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             */
112            public String getNamespaceForPrefix(String prefix, Node context) {
113                return getNamespaceForPrefix(prefix);
114            }
115    
116                    /**
117                     * @see PrefixResolver#getBaseIdentifier()
118                     */
119                    public String getBaseIdentifier() {
120                            return ElemNumber.this.getBaseIdentifier();
121                    }
122    
123                    /**
124                     * @see PrefixResolver#handlesNullPrefixes()
125                     */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
555      public void execute(
556              TransformerImpl transformer)
557                throws TransformerException
558      {
559    
560         if (transformer.getDebug())
561          transformer.getTraceManager().fireTraceEvent(this);
562    
563        int sourceNode = transformer.getXPathContext().getCurrentNode();
564        String countString = getCountString(transformer, sourceNode);
565    
566        try
567        {
568          transformer.getResultTreeHandler().characters(countString.toCharArray(),
569                                                        0, countString.length());
570        }
571        catch(SAXException se)
572        {
573          throw new TransformerException(se);
574        }
575        finally
576        {
577          if (transformer.getDebug())
578                transformer.getTraceManager().fireTraceEndEvent(this); 
579        }
580      }
581    
582      /**
583       * Add a child to the child list.
584       *
585       * @param newChild Child to add to child list
586       *
587       * @return Child just added to child list
588       *
589       * @throws DOMException
590       */
591      public ElemTemplateElement appendChild(ElemTemplateElement newChild)
592      {
593    
594        error(XSLTErrorResources.ER_CANNOT_ADD,
595              new Object[]{ newChild.getNodeName(),
596                            this.getNodeName() });  //"Can not add " +((ElemTemplateElement)newChild).m_elemName +
597    
598        //" to " + this.m_elemName);
599        return null;
600      }
601    
602      /**
603       * Given a 'from' pattern (ala xsl:number), a match pattern
604       * and a context, find the first ancestor that matches the
605       * pattern (including the context handed in).
606       *
607       * @param xctxt The XPath runtime state for this.
608       * @param fromMatchPattern The ancestor must match this pattern.
609       * @param countMatchPattern The ancestor must also match this pattern.
610       * @param context The node that "." expresses.
611       * @param namespaceContext The context in which namespaces in the
612       * queries are supposed to be expanded.
613       *
614       * @return the first ancestor that matches the given pattern
615       *
616       * @throws javax.xml.transform.TransformerException
617       */
618      int findAncestor(
619              XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern, 
620              int context, ElemNumber namespaceContext)
621                throws javax.xml.transform.TransformerException
622      {
623        DTM dtm = xctxt.getDTM(context);
624        while (DTM.NULL != context)
625        {
626          if (null != fromMatchPattern)
627          {
628            if (fromMatchPattern.getMatchScore(xctxt, context)
629                    != XPath.MATCH_SCORE_NONE)
630            {
631    
632              //context = null;
633              break;
634            }
635          }
636    
637          if (null != countMatchPattern)
638          {
639            if (countMatchPattern.getMatchScore(xctxt, context)
640                    != XPath.MATCH_SCORE_NONE)
641            {
642              break;
643            }
644          }
645    
646          context = dtm.getParent(context);
647        }
648    
649        return context;
650      }
651    
652      /**
653       * Given a 'from' pattern (ala xsl:number), a match pattern
654       * and a context, find the first ancestor that matches the
655       * pattern (including the context handed in).
656       * @param xctxt The XPath runtime state for this.
657       * @param fromMatchPattern The ancestor must match this pattern.
658       * @param countMatchPattern The ancestor must also match this pattern.
659       * @param context The node that "." expresses.
660       * @param namespaceContext The context in which namespaces in the
661       * queries are supposed to be expanded.
662       *
663       * @return the first preceding, ancestor or self node that 
664       * matches the given pattern
665       *
666       * @throws javax.xml.transform.TransformerException
667       */
668      private int findPrecedingOrAncestorOrSelf(
669              XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern, 
670              int context, ElemNumber namespaceContext)
671                throws javax.xml.transform.TransformerException
672      {
673        DTM dtm = xctxt.getDTM(context);
674        while (DTM.NULL != context)
675        {
676          if (null != fromMatchPattern)
677          {
678            if (fromMatchPattern.getMatchScore(xctxt, context)
679                    != XPath.MATCH_SCORE_NONE)
680            {
681              context = DTM.NULL;
682    
683              break;
684            }
685          }
686    
687          if (null != countMatchPattern)
688          {
689            if (countMatchPattern.getMatchScore(xctxt, context)
690                    != XPath.MATCH_SCORE_NONE)
691            {
692              break;
693            }
694          }
695    
696          int prevSibling = dtm.getPreviousSibling(context);
697    
698          if (DTM.NULL == prevSibling)
699          {
700            context = dtm.getParent(context);
701          }
702          else
703          {
704    
705            // Now go down the chain of children of this sibling 
706            context = dtm.getLastChild(prevSibling);
707    
708            if (context == DTM.NULL)
709              context = prevSibling;
710          }
711        }
712    
713        return context;
714      }
715    
716      /**
717       * Get the count match pattern, or a default value.
718       *
719       * @param support The XPath runtime state for this.
720       * @param contextNode The node that "." expresses.
721       *
722       * @return the count match pattern, or a default value. 
723       *
724       * @throws javax.xml.transform.TransformerException
725       */
726      XPath getCountMatchPattern(XPathContext support, int contextNode)
727              throws javax.xml.transform.TransformerException
728      {
729    
730        XPath countMatchPattern = m_countMatchPattern;
731        DTM dtm = support.getDTM(contextNode);
732        if (null == countMatchPattern)
733        {
734          switch (dtm.getNodeType(contextNode))
735          {
736          case DTM.ELEMENT_NODE :
737            MyPrefixResolver resolver;
738    
739            if (dtm.getNamespaceURI(contextNode) == null) {
740                 resolver =  new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, false);
741            } else {
742                resolver = new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, true);
743            }
744    
745            countMatchPattern = new XPath(dtm.getNodeName(contextNode), this, resolver,
746                                          XPath.MATCH, support.getErrorListener());
747            break;
748    
749          case DTM.ATTRIBUTE_NODE :
750    
751            // countMatchPattern = m_stylesheet.createMatchPattern("@"+contextNode.getNodeName(), this);
752            countMatchPattern = new XPath("@" + dtm.getNodeName(contextNode), this,
753                                          this, XPath.MATCH, support.getErrorListener());
754            break;
755          case DTM.CDATA_SECTION_NODE :
756          case DTM.TEXT_NODE :
757    
758            // countMatchPattern = m_stylesheet.createMatchPattern("text()", this);
759            countMatchPattern = new XPath("text()", this, this, XPath.MATCH, support.getErrorListener());
760            break;
761          case DTM.COMMENT_NODE :
762    
763            // countMatchPattern = m_stylesheet.createMatchPattern("comment()", this);
764            countMatchPattern = new XPath("comment()", this, this, XPath.MATCH, support.getErrorListener());
765            break;
766          case DTM.DOCUMENT_NODE :
767    
768            // countMatchPattern = m_stylesheet.createMatchPattern("/", this);
769            countMatchPattern = new XPath("/", this, this, XPath.MATCH, support.getErrorListener());
770            break;
771          case DTM.PROCESSING_INSTRUCTION_NODE :
772    
773            // countMatchPattern = m_stylesheet.createMatchPattern("pi("+contextNode.getNodeName()+")", this);
774            countMatchPattern = new XPath("pi(" + dtm.getNodeName(contextNode)
775                                          + ")", this, this, XPath.MATCH, support.getErrorListener());
776            break;
777          default :
778            countMatchPattern = null;
779          }
780        }
781    
782        return countMatchPattern;
783      }
784    
785      /**
786       * Given an XML source node, get the count according to the
787       * parameters set up by the xsl:number attributes.
788       * @param transformer non-null reference to the the current transform-time state.
789       * @param sourceNode The source node being counted.
790       *
791       * @return The count of nodes
792       *
793       * @throws TransformerException
794       */
795      String getCountString(TransformerImpl transformer, int sourceNode)
796              throws TransformerException
797      {
798    
799        long[] list = null;
800        XPathContext xctxt = transformer.getXPathContext();
801        CountersTable ctable = transformer.getCountersTable();
802    
803        if (null != m_valueExpr)
804        {
805          XObject countObj = m_valueExpr.execute(xctxt, sourceNode, this);
806          //According to Errata E24
807          double d_count = java.lang.Math.floor(countObj.num()+ 0.5);
808          if (Double.isNaN(d_count)) return "NaN";
809          else if (d_count < 0 && Double.isInfinite(d_count)) return "-Infinity";
810          else if (Double.isInfinite(d_count)) return "Infinity";
811          else if (d_count == 0) return "0";
812          else{
813                  long count = (long)d_count;
814                  list = new long[1];
815                  list[0] = count;              
816          }
817        }
818        else
819        {
820          if (Constants.NUMBERLEVEL_ANY == m_level)
821          {
822            list = new long[1];
823            list[0] = ctable.countNode(xctxt, this, sourceNode);
824          }
825          else
826          {
827            NodeVector ancestors =
828              getMatchingAncestors(xctxt, sourceNode,
829                                   Constants.NUMBERLEVEL_SINGLE == m_level);
830            int lastIndex = ancestors.size() - 1;
831    
832            if (lastIndex >= 0)
833            {
834              list = new long[lastIndex + 1];
835    
836              for (int i = lastIndex; i >= 0; i--)
837              {
838                int target = ancestors.elementAt(i);
839    
840                list[lastIndex - i] = ctable.countNode(xctxt, this, target);
841              }
842            }
843          }
844        }
845    
846        return (null != list)
847               ? formatNumberList(transformer, list, sourceNode) : "";
848      }
849    
850      /**
851       * Get the previous node to be counted.
852       *
853       * @param xctxt The XPath runtime state for this.
854       * @param pos The current node
855       *
856       * @return the previous node to be counted.
857       *
858       * @throws TransformerException
859       */
860      public int getPreviousNode(XPathContext xctxt, int pos)
861              throws TransformerException
862      {
863    
864        XPath countMatchPattern = getCountMatchPattern(xctxt, pos);
865        DTM dtm = xctxt.getDTM(pos);
866    
867        if (Constants.NUMBERLEVEL_ANY == m_level)
868        {
869          XPath fromMatchPattern = m_fromMatchPattern;
870    
871          // Do a backwards document-order walk 'till a node is found that matches 
872          // the 'from' pattern, or a node is found that matches the 'count' pattern, 
873          // or the top of the tree is found.
874          while (DTM.NULL != pos)
875          {
876    
877            // Get the previous sibling, if there is no previous sibling, 
878            // then count the parent, but if there is a previous sibling, 
879            // dive down to the lowest right-hand (last) child of that sibling.
880            int next = dtm.getPreviousSibling(pos);
881    
882            if (DTM.NULL == next)
883            {
884              next = dtm.getParent(pos);
885    
886              if ((DTM.NULL != next) && ((((null != fromMatchPattern) && (fromMatchPattern.getMatchScore(
887                      xctxt, next) != XPath.MATCH_SCORE_NONE))) 
888                  || (dtm.getNodeType(next) == DTM.DOCUMENT_NODE)))
889              {
890                pos = DTM.NULL;  // return null from function.
891    
892                break;  // from while loop
893              }
894            }
895            else
896            {
897    
898              // dive down to the lowest right child.
899              int child = next;
900    
901              while (DTM.NULL != child)
902              {
903                child = dtm.getLastChild(next);
904    
905                if (DTM.NULL != child)
906                  next = child;
907              }
908            }
909    
910            pos = next;
911    
912            if ((DTM.NULL != pos)
913                    && ((null == countMatchPattern)
914                        || (countMatchPattern.getMatchScore(xctxt, pos)
915                            != XPath.MATCH_SCORE_NONE)))
916            {
917              break;
918            }
919          }
920        }
921        else  // NUMBERLEVEL_MULTI or NUMBERLEVEL_SINGLE
922        {
923          while (DTM.NULL != pos)
924          {
925            pos = dtm.getPreviousSibling(pos);
926    
927            if ((DTM.NULL != pos)
928                    && ((null == countMatchPattern)
929                        || (countMatchPattern.getMatchScore(xctxt, pos)
930                            != XPath.MATCH_SCORE_NONE)))
931            {
932              break;
933            }
934          }
935        }
936    
937        return pos;
938      }
939    
940      /**
941       * Get the target node that will be counted..
942       *
943       * @param xctxt The XPath runtime state for this.
944       * @param sourceNode non-null reference to the <a href="http://www.w3.org/TR/xslt#dt-current-node">current source node</a>.
945       *
946       * @return the target node that will be counted
947       *
948       * @throws TransformerException
949       */
950      public int getTargetNode(XPathContext xctxt, int sourceNode)
951              throws TransformerException
952      {
953    
954        int target = DTM.NULL;
955        XPath countMatchPattern = getCountMatchPattern(xctxt, sourceNode);
956    
957        if (Constants.NUMBERLEVEL_ANY == m_level)
958        {
959          target = findPrecedingOrAncestorOrSelf(xctxt, m_fromMatchPattern,
960                                                 countMatchPattern, sourceNode,
961                                                 this);
962        }
963        else
964        {
965          target = findAncestor(xctxt, m_fromMatchPattern, countMatchPattern,
966                                sourceNode, this);
967        }
968    
969        return target;
970      }
971    
972      /**
973       * Get the ancestors, up to the root, that match the
974       * pattern.
975       * 
976       * @param xctxt The XPath runtime state for this.
977       * @param node Count this node and it's ancestors.
978       * @param stopAtFirstFound Flag indicating to stop after the
979       * first node is found (difference between level = single
980       * or multiple)
981       * @return The number of ancestors that match the pattern.
982       *
983       * @throws javax.xml.transform.TransformerException
984       */
985      NodeVector getMatchingAncestors(
986              XPathContext xctxt, int node, boolean stopAtFirstFound)
987                throws javax.xml.transform.TransformerException
988      {
989    
990        NodeSetDTM ancestors = new NodeSetDTM(xctxt.getDTMManager());
991        XPath countMatchPattern = getCountMatchPattern(xctxt, node);
992        DTM dtm = xctxt.getDTM(node);
993    
994        while (DTM.NULL != node)
995        {
996          if ((null != m_fromMatchPattern)
997                  && (m_fromMatchPattern.getMatchScore(xctxt, node)
998                      != XPath.MATCH_SCORE_NONE))
999          {
1000    
1001            // The following if statement gives level="single" different 
1002            // behavior from level="multiple", which seems incorrect according 
1003            // to the XSLT spec.  For now we are leaving this in to replicate 
1004            // the same behavior in XT, but, for all intents and purposes we 
1005            // think this is a bug, or there is something about level="single" 
1006            // that we still don't understand.
1007            if (!stopAtFirstFound)
1008              break;
1009          }
1010    
1011          if (null == countMatchPattern)
1012            System.out.println(
1013              "Programmers error! countMatchPattern should never be null!");
1014    
1015          if (countMatchPattern.getMatchScore(xctxt, node)
1016                  != XPath.MATCH_SCORE_NONE)
1017          {
1018            ancestors.addElement(node);
1019    
1020            if (stopAtFirstFound)
1021              break;
1022          }
1023    
1024          node = dtm.getParent(node);
1025        }
1026    
1027        return ancestors;
1028      }  // end getMatchingAncestors method
1029    
1030      /**
1031       * Get the locale we should be using.
1032       *
1033       * @param transformer non-null reference to the the current transform-time state.
1034       * @param contextNode The node that "." expresses.
1035       *
1036       * @return The locale to use. May be specified by "lang" attribute,
1037       * but if not, use default locale on the system. 
1038       *
1039       * @throws TransformerException
1040       */
1041      Locale getLocale(TransformerImpl transformer, int contextNode)
1042              throws TransformerException
1043      {
1044    
1045        Locale locale = null;
1046    
1047        if (null != m_lang_avt)
1048        {
1049          XPathContext xctxt = transformer.getXPathContext();
1050          String langValue = m_lang_avt.evaluate(xctxt, contextNode, this);
1051    
1052          if (null != langValue)
1053          {
1054    
1055            // Not really sure what to do about the country code, so I use the
1056            // default from the system.
1057            // TODO: fix xml:lang handling.
1058            locale = new Locale(langValue.toUpperCase(), "");
1059    
1060            //Locale.getDefault().getDisplayCountry());
1061            if (null == locale)
1062            {
1063              transformer.getMsgMgr().warn(this, null, xctxt.getDTM(contextNode).getNode(contextNode),
1064                                           XSLTErrorResources.WG_LOCALE_NOT_FOUND,
1065                                           new Object[]{ langValue });  //"Warning: Could not find locale for xml:lang="+langValue);
1066    
1067              locale = Locale.getDefault();
1068            }
1069          }
1070        }
1071        else
1072        {
1073          locale = Locale.getDefault();
1074        }
1075    
1076        return locale;
1077      }
1078    
1079      /**
1080       * Get the number formatter to be used the format the numbers
1081       *
1082       * @param transformer non-null reference to the the current transform-time state.
1083       * @param contextNode The node that "." expresses.
1084       *
1085       * ($objectName$) @return The number formatter to be used
1086       *
1087       * @throws TransformerException
1088       */
1089      private DecimalFormat getNumberFormatter(
1090              TransformerImpl transformer, int contextNode) throws TransformerException
1091      {
1092        // Patch from Steven Serocki
1093        // Maybe we really want to do the clone in getLocale() and return  
1094        // a clone of the default Locale??
1095        Locale locale = (Locale)getLocale(transformer, contextNode).clone();
1096    
1097        // Helper to format local specific numbers to strings.
1098        DecimalFormat formatter = null;
1099    
1100        //synchronized (locale)
1101        //{
1102        //     formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale);
1103        //}
1104    
1105        String digitGroupSepValue =
1106          (null != m_groupingSeparator_avt)
1107          ? m_groupingSeparator_avt.evaluate(
1108          transformer.getXPathContext(), contextNode, this) : null;
1109          
1110          
1111        // Validate grouping separator if an AVT was used; otherwise this was 
1112        // validated statically in XSLTAttributeDef.java.
1113        if ((digitGroupSepValue != null) && (!m_groupingSeparator_avt.isSimple()) &&
1114            (digitGroupSepValue.length() != 1))
1115        {
1116                transformer.getMsgMgr().warn(
1117                   this, XSLTErrorResources.WG_ILLEGAL_ATTRIBUTE_VALUE,
1118                   new Object[]{ Constants.ATTRNAME_NAME, m_groupingSeparator_avt.getName()});   
1119        }                  
1120          
1121          
1122        String nDigitsPerGroupValue =
1123          (null != m_groupingSize_avt)
1124          ? m_groupingSize_avt.evaluate(
1125          transformer.getXPathContext(), contextNode, this) : null;
1126    
1127        // TODO: Handle digit-group attributes
1128        if ((null != digitGroupSepValue) && (null != nDigitsPerGroupValue) &&
1129            // Ignore if separation value is empty string
1130            (digitGroupSepValue.length() > 0))
1131        {
1132          try
1133          {
1134            formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale);
1135            formatter.setGroupingSize(
1136              Integer.valueOf(nDigitsPerGroupValue).intValue());
1137            
1138            DecimalFormatSymbols symbols = formatter.getDecimalFormatSymbols();
1139            symbols.setGroupingSeparator(digitGroupSepValue.charAt(0));
1140            formatter.setDecimalFormatSymbols(symbols);
1141            formatter.setGroupingUsed(true);
1142          }
1143          catch (NumberFormatException ex)
1144          {
1145            formatter.setGroupingUsed(false);
1146          }
1147        }
1148    
1149        return formatter;
1150      }
1151    
1152      /**
1153       * Format a vector of numbers into a formatted string.
1154       * 
1155       * @param transformer non-null reference to the the current transform-time state.
1156       * @param list Array of one or more long integer numbers.
1157       * @param contextNode The node that "." expresses.
1158       * @return String that represents list according to
1159       * %conversion-atts; attributes.
1160       * TODO: Optimize formatNumberList so that it caches the last count and
1161       * reuses that info for the next count.
1162       *
1163       * @throws TransformerException
1164       */
1165      String formatNumberList(
1166              TransformerImpl transformer, long[] list, int contextNode)
1167                throws TransformerException
1168      {
1169    
1170        String numStr;
1171        FastStringBuffer formattedNumber = StringBufferPool.get();
1172    
1173        try
1174        {
1175          int nNumbers = list.length, numberWidth = 1;
1176          char numberType = '1';
1177          String formatToken, lastSepString = null, formatTokenString = null;
1178    
1179          // If a seperator hasn't been specified, then use "."  
1180          // as a default separator. 
1181          // For instance: [2][1][5] with a format value of "1 "
1182          // should format to "2.1.5 " (I think).
1183          // Otherwise, use the seperator specified in the format string.
1184          // For instance: [2][1][5] with a format value of "01-001. "
1185          // should format to "02-001-005 ".
1186          String lastSep = ".";
1187          boolean isFirstToken = true;  // true if first token  
1188          String formatValue =
1189            (null != m_format_avt)
1190            ? m_format_avt.evaluate(
1191            transformer.getXPathContext(), contextNode, this) : null;
1192    
1193          if (null == formatValue)
1194            formatValue = "1";
1195    
1196          NumberFormatStringTokenizer formatTokenizer =
1197            new NumberFormatStringTokenizer(formatValue);
1198    
1199          // int sepCount = 0;                  // keep track of seperators
1200          // Loop through all the numbers in the list.
1201          for (int i = 0; i < nNumbers; i++)
1202          {
1203    
1204            // Loop to the next digit, letter, or separator.
1205            if (formatTokenizer.hasMoreTokens())
1206            {
1207              formatToken = formatTokenizer.nextToken();
1208    
1209              // If the first character of this token is a character or digit, then 
1210              // it is a number format directive.
1211              if (Character.isLetterOrDigit(
1212                      formatToken.charAt(formatToken.length() - 1)))
1213              {
1214                numberWidth = formatToken.length();
1215                numberType = formatToken.charAt(numberWidth - 1);
1216              }
1217    
1218              // If there is a number format directive ahead, 
1219              // then append the formatToken.
1220              else if (formatTokenizer.isLetterOrDigitAhead())
1221              {
1222                final StringBuffer formatTokenStringBuffer = new StringBuffer(formatToken);
1223    
1224                // Append the formatToken string...
1225                // For instance [2][1][5] with a format value of "1--1. "
1226                // should format to "2--1--5. " (I guess).
1227                while (formatTokenizer.nextIsSep())
1228                {
1229                  formatToken = formatTokenizer.nextToken();
1230                  formatTokenStringBuffer.append(formatToken);
1231                }
1232                formatTokenString = formatTokenStringBuffer.toString();
1233    
1234                // Record this separator, so it can be used as the 
1235                // next separator, if the next is the last.
1236                // For instance: [2][1][5] with a format value of "1-1 "
1237                // should format to "2-1-5 ".
1238                if (!isFirstToken)
1239                  lastSep = formatTokenString;
1240    
1241                // Since we know the next is a number or digit, we get it now.
1242                formatToken = formatTokenizer.nextToken();
1243                numberWidth = formatToken.length();
1244                numberType = formatToken.charAt(numberWidth - 1);
1245              }
1246              else  // only separators left
1247              {
1248    
1249                // Set up the string for the trailing characters after 
1250                // the last number is formatted (i.e. after the loop).
1251                lastSepString = formatToken;
1252    
1253                // And append any remaining characters to the lastSepString.
1254                while (formatTokenizer.hasMoreTokens())
1255                {
1256                  formatToken = formatTokenizer.nextToken();
1257                  lastSepString += formatToken;
1258                }
1259              }  // else
1260            }  // end if(formatTokenizer.hasMoreTokens())
1261    
1262            // if this is the first token and there was a prefix
1263            // append the prefix else, append the separator
1264            // For instance, [2][1][5] with a format value of "(1-1.) "
1265            // should format to "(2-1-5.) " (I guess).
1266            if (null != formatTokenString && isFirstToken)
1267            {
1268              formattedNumber.append(formatTokenString);
1269            }
1270            else if (null != lastSep &&!isFirstToken)
1271              formattedNumber.append(lastSep);
1272    
1273            getFormattedNumber(transformer, contextNode, numberType, numberWidth,
1274                               list[i], formattedNumber);
1275    
1276            isFirstToken = false;  // After the first pass, this should be false
1277          }  // end for loop
1278    
1279          // Check to see if we finished up the format string...
1280          // Skip past all remaining letters or digits
1281          while (formatTokenizer.isLetterOrDigitAhead())
1282          {
1283            formatTokenizer.nextToken();
1284          }
1285    
1286          if (lastSepString != null)
1287            formattedNumber.append(lastSepString);
1288    
1289          while (formatTokenizer.hasMoreTokens())
1290          {
1291            formatToken = formatTokenizer.nextToken();
1292    
1293            formattedNumber.append(formatToken);
1294          }
1295    
1296          numStr = formattedNumber.toString();
1297        }
1298        finally
1299        {
1300          StringBufferPool.free(formattedNumber);
1301        }
1302    
1303        return numStr;
1304      }  // end formatNumberList method
1305    
1306      /*
1307      * Get Formatted number
1308      */
1309    
1310      /**
1311       * Format the given number and store it in the given buffer 
1312       *
1313       *
1314       * @param transformer non-null reference to the the current transform-time state.
1315       * @param contextNode The node that "." expresses.
1316       * @param numberType Type to format to
1317       * @param numberWidth Maximum length of formatted number
1318       * @param listElement Number to format
1319       * @param formattedNumber Buffer to store formatted number
1320       *
1321       * @throws javax.xml.transform.TransformerException
1322       */
1323      private void getFormattedNumber(
1324              TransformerImpl transformer, int contextNode, 
1325              char numberType, int numberWidth, long listElement, 
1326              FastStringBuffer formattedNumber)
1327                throws javax.xml.transform.TransformerException
1328      {
1329    
1330    
1331        String letterVal =
1332          (m_lettervalue_avt != null)
1333          ? m_lettervalue_avt.evaluate(
1334          transformer.getXPathContext(), contextNode, this) : null;
1335    
1336        /**
1337         * Wrapper of Chars for converting integers into alpha counts.
1338         */
1339        CharArrayWrapper alphaCountTable = null;
1340        
1341        XResourceBundle thisBundle = null;
1342         
1343        switch (numberType)
1344        {
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          int2alphaCount(listElement, m_alphaCountTable, formattedNumber);
1353          break;
1354        case 'a' :
1355            if (null == m_alphaCountTable){
1356                    thisBundle =
1357                      (XResourceBundle) XResourceBundle.loadResourceBundle(
1358                        org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, getLocale(transformer, contextNode));
1359                    m_alphaCountTable = (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET);                
1360            }
1361          FastStringBuffer stringBuf = StringBufferPool.get();
1362    
1363          try
1364          {
1365            int2alphaCount(listElement, m_alphaCountTable, stringBuf);
1366            formattedNumber.append(
1367              stringBuf.toString().toLowerCase(
1368                getLocale(transformer, contextNode)));
1369          }
1370          finally
1371          {
1372            StringBufferPool.free(stringBuf);
1373          }
1374          break;
1375        case 'I' :
1376          formattedNumber.append(long2roman(listElement, true));
1377          break;
1378        case 'i' :
1379          formattedNumber.append(
1380            long2roman(listElement, true).toLowerCase(
1381              getLocale(transformer, contextNode)));
1382          break;
1383        case 0x3042 :
1384        {
1385    
1386          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1387            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("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                (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1397    
1398          break;
1399        }
1400        case 0x3044 :
1401        {
1402    
1403          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1404            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "HI"));
1405    
1406          if ((letterVal != null)
1407                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1408            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1409          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1410            formattedNumber.append(
1411              int2singlealphaCount(
1412                listElement,
1413                (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1414    
1415          break;
1416        }
1417        case 0x30A2 :
1418        {
1419    
1420          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1421            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "A"));
1422    
1423          if (letterVal != null
1424                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1425            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1426          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1427            formattedNumber.append(
1428              int2singlealphaCount(
1429                listElement,
1430                (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1431    
1432          break;
1433        }
1434        case 0x30A4 :
1435        {
1436    
1437          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1438            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "I"));
1439    
1440          if (letterVal != null
1441                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1442            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1443          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1444            formattedNumber.append(
1445              int2singlealphaCount(
1446                listElement,
1447                (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET)));
1448    
1449          break;
1450        }
1451        case 0x4E00 :
1452        {
1453    
1454          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1455            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("zh", "CN"));
1456    
1457          if (letterVal != null
1458                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1459          {
1460            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1461          }
1462          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1463            int2alphaCount(listElement,
1464                           (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1465                           formattedNumber);
1466    
1467          break;
1468        }
1469        case 0x58F9 :
1470        {
1471    
1472          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1473            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("zh", "TW"));
1474    
1475          if (letterVal != null
1476                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1477            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1478          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1479            int2alphaCount(listElement,
1480                           (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1481                           formattedNumber);
1482    
1483          break;
1484        }
1485        case 0x0E51 :
1486        {
1487    
1488          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1489            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("th", ""));
1490    
1491          if (letterVal != null
1492                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1493            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1494          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1495            int2alphaCount(listElement,
1496                           (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1497                           formattedNumber);
1498    
1499          break;
1500        }
1501        case 0x05D0 :
1502        {
1503    
1504          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1505            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("he", ""));
1506    
1507          if (letterVal != null
1508                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1509            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1510          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1511            int2alphaCount(listElement,
1512                           (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1513                           formattedNumber);
1514    
1515          break;
1516        }
1517        case 0x10D0 :
1518        {
1519    
1520          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1521            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ka", ""));
1522    
1523          if (letterVal != null
1524                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1525            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1526          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1527            int2alphaCount(listElement,
1528                           (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1529                           formattedNumber);
1530    
1531          break;
1532        }
1533        case 0x03B1 :
1534        {
1535    
1536          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1537            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("el", ""));
1538    
1539          if (letterVal != null
1540                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1541            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1542          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1543            int2alphaCount(listElement,
1544                           (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1545                           formattedNumber);
1546    
1547          break;
1548        }
1549        case 0x0430 :
1550        {
1551    
1552          thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle(
1553            org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("cy", ""));
1554    
1555          if (letterVal != null
1556                  && letterVal.equals(Constants.ATTRVAL_TRADITIONAL))
1557            formattedNumber.append(tradAlphaCount(listElement, thisBundle));
1558          else  //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC))
1559            int2alphaCount(listElement,
1560                           (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET),
1561                           formattedNumber);
1562    
1563          break;
1564        }
1565        default :  // "1"
1566          DecimalFormat formatter = getNumberFormatter(transformer, contextNode);
1567          String padString = formatter == null ? String.valueOf(0) : formatter.format(0);    
1568          String numString = formatter == null ? String.valueOf(listElement) : formatter.format(listElement);
1569          int nPadding = numberWidth - numString.length();
1570    
1571          for (int k = 0; k < nPadding; k++)
1572          {
1573            formattedNumber.append(padString);
1574          }
1575    
1576          formattedNumber.append(numString);
1577        }
1578      }
1579      
1580      /**
1581       * Get a string value for zero, which is not really defined by the 1.0 spec, 
1582       * thought I think it might be cleared up by the erreta.
1583       */
1584       String getZeroString()
1585       {
1586         return ""+0;
1587       }
1588    
1589      /**
1590       * Convert a long integer into alphabetic counting, in other words
1591       * count using the sequence A B C ... Z.
1592       * 
1593       * @param val Value to convert -- must be greater than zero.
1594       * @param table a table containing one character for each digit in the radix
1595       * @return String representing alpha count of number.
1596       * @see TransformerImpl#DecimalToRoman
1597       *
1598       * Note that the radix of the conversion is inferred from the size
1599       * of the table.
1600       */
1601      protected String int2singlealphaCount(long val, CharArrayWrapper table)
1602      {
1603    
1604        int radix = table.getLength();
1605    
1606        // TODO:  throw error on out of range input
1607        if (val > radix)
1608        {
1609          return getZeroString();
1610        }
1611        else
1612          return (new Character(table.getChar((int)val - 1))).toString();  // index into table is off one, starts at 0
1613      }
1614    
1615      /**
1616       * Convert a long integer into alphabetic counting, in other words
1617       * count using the sequence A B C ... Z AA AB AC.... etc.
1618       * 
1619       * @param val Value to convert -- must be greater than zero.
1620       * @param table a table containing one character for each digit in the radix
1621       * @param aTable Array of alpha characters representing numbers
1622       * @param stringBuf Buffer where to save the string representing alpha count of number.
1623       * 
1624       * @see TransformerImpl#DecimalToRoman
1625       *
1626       * Note that the radix of the conversion is inferred from the size
1627       * of the table.
1628       */
1629      protected void int2alphaCount(long val, CharArrayWrapper aTable,
1630                                    FastStringBuffer stringBuf)
1631      {
1632    
1633        int radix = aTable.getLength();
1634        char[] table = new char[radix];
1635    
1636        // start table at 1, add last char at index 0. Reason explained above and below.
1637        int i;
1638    
1639        for (i = 0; i < radix - 1; i++)
1640        {
1641          table[i + 1] = aTable.getChar(i);
1642        }
1643    
1644        table[0] = aTable.getChar(i);
1645    
1646        // Create a buffer to hold the result
1647        // TODO:  size of the table can be detereined by computing
1648        // logs of the radix.  For now, we fake it.
1649        char buf[] = new char[100];
1650    
1651        //some languages go left to right(ie. english), right to left (ie. Hebrew),
1652        //top to bottom (ie.Japanese), etc... Handle them differently
1653        //String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION);
1654        // next character to set in the buffer
1655        int charPos;
1656    
1657        charPos = buf.length - 1;  // work backward through buf[]  
1658    
1659        // index in table of the last character that we stored
1660        int lookupIndex = 1;  // start off with anything other than zero to make correction work
1661    
1662        //                                          Correction number
1663        //
1664        //  Correction can take on exactly two values:
1665        //
1666        //          0       if the next character is to be emitted is usual
1667        //
1668        //      radix - 1
1669        //                  if the next char to be emitted should be one less than
1670        //                  you would expect
1671        //                  
1672        // For example, consider radix 10, where 1="A" and 10="J"
1673        //
1674        // In this scheme, we count: A, B, C ...   H, I, J (not A0 and certainly
1675        // not AJ), A1
1676        //
1677        // So, how do we keep from emitting AJ for 10?  After correctly emitting the
1678        // J, lookupIndex is zero.  We now compute a correction number of 9 (radix-1).
1679        // In the following line, we'll compute (val+correction) % radix, which is,
1680        // (val+9)/10.  By this time, val is 1, so we compute (1+9) % 10, which
1681        // is 10 % 10 or zero.  So, we'll prepare to emit "JJ", but then we'll
1682        // later suppress the leading J as representing zero (in the mod system,
1683        // it can represent either 10 or zero).  In summary, the correction value of
1684        // "radix-1" acts like "-1" when run through the mod operator, but with the
1685        // desireable characteristic that it never produces a negative number.
1686        long correction = 0;
1687    
1688        // TODO:  throw error on out of range input
1689        do
1690        {
1691    
1692          // most of the correction calculation is explained above,  the reason for the
1693          // term after the "|| " is that it correctly propagates carries across
1694          // multiple columns.
1695          correction =
1696            ((lookupIndex == 0) || (correction != 0 && lookupIndex == radix - 1))
1697            ? (radix - 1) : 0;
1698    
1699          // index in "table" of the next char to emit
1700          lookupIndex = (int)(val + correction) % radix;
1701    
1702          // shift input by one "column"
1703          val = (val / radix);
1704    
1705          // if the next value we'd put out would be a leading zero, we're done.
1706          if (lookupIndex == 0 && val == 0)
1707            break;
1708    
1709          // put out the next character of output
1710          buf[charPos--] = table[lookupIndex];  // left to right or top to bottom   
1711        }
1712        while (val > 0);
1713    
1714        stringBuf.append(buf, charPos + 1, (buf.length - charPos - 1));
1715      }
1716    
1717      /**
1718       * Convert a long integer into traditional alphabetic counting, in other words
1719       * count using the traditional numbering.
1720       * 
1721       * @param val Value to convert -- must be greater than zero.
1722       * @param thisBundle Resource bundle to use
1723       * 
1724       * @return String representing alpha count of number.
1725       * @see XSLProcessor#DecimalToRoman
1726       *
1727       * Note that the radix of the conversion is inferred from the size
1728       * of the table.
1729       */
1730      protected String tradAlphaCount(long val, XResourceBundle thisBundle)
1731      {
1732    
1733        // if this number is larger than the largest number we can represent, error!
1734        if (val > Long.MAX_VALUE)
1735        {
1736          this.error(XSLTErrorResources.ER_NUMBER_TOO_BIG);
1737          return XSLTErrorResources.ERROR_STRING;
1738        }
1739        char[] table = null;
1740    
1741        // index in table of the last character that we stored
1742        int lookupIndex = 1;  // start off with anything other than zero to make correction work
1743    
1744        // Create a buffer to hold the result
1745        // TODO:  size of the table can be detereined by computing
1746        // logs of the radix.  For now, we fake it.
1747        char buf[] = new char[100];
1748    
1749        //some languages go left to right(ie. english), right to left (ie. Hebrew),
1750        //top to bottom (ie.Japanese), etc... Handle them differently
1751        //String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION);
1752        // next character to set in the buffer
1753        int charPos;
1754    
1755        charPos = 0;  //start at 0
1756    
1757        // array of number groups: ie.1000, 100, 10, 1
1758        IntArrayWrapper groups = (IntArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERGROUPS);
1759    
1760        // array of tables of hundreds, tens, digits...
1761        StringArrayWrapper tables =
1762          (StringArrayWrapper) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUM_TABLES));
1763    
1764        //some languages have additive alphabetical notation,
1765        //some multiplicative-additive, etc... Handle them differently.
1766        String numbering = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERING);
1767    
1768        // do multiplicative part first
1769        if (numbering.equals(org.apache.xml.utils.res.XResourceBundle.LANG_MULT_ADD))
1770        {
1771          String mult_order = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.MULT_ORDER);
1772          LongArrayWrapper multiplier =
1773            (LongArrayWrapper) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER));
1774          CharArrayWrapper zeroChar = (CharArrayWrapper) thisBundle.getObject("zero");
1775          int i = 0;
1776    
1777          // skip to correct multiplier
1778          while (i < multiplier.getLength() && val < multiplier.getLong(i))
1779          {
1780            i++;
1781          }
1782    
1783          do
1784          {
1785            if (i >= multiplier.getLength())
1786              break;  //number is smaller than multipliers
1787    
1788            // some languages (ie chinese) put a zero character (and only one) when
1789            // the multiplier is multiplied by zero. (ie, 1001 is 1X1000 + 0X100 + 0X10 + 1)
1790            // 0X100 is replaced by the zero character, we don't need one for 0X10
1791            if (val < multiplier.getLong(i))
1792            {
1793              if (zeroChar.getLength() == 0)
1794              {
1795                i++;
1796              }
1797              else
1798              {
1799                if (buf[charPos - 1] != zeroChar.getChar(0))
1800                  buf[charPos++] = zeroChar.getChar(0);
1801    
1802                i++;
1803              }
1804            }
1805            else if (val >= multiplier.getLong(i))
1806            {
1807              long mult = val / multiplier.getLong(i);
1808    
1809              val = val % multiplier.getLong(i);  // save this.
1810    
1811              int k = 0;
1812    
1813              while (k < groups.getLength())
1814              {
1815                lookupIndex = 1;  // initialize for each table
1816    
1817                if (mult / groups.getInt(k) <= 0)  // look for right table
1818                  k++;
1819                else
1820                {
1821    
1822                  // get the table
1823                  CharArrayWrapper THEletters = (CharArrayWrapper) thisBundle.getObject(tables.getString(k));
1824    
1825                  table = new char[THEletters.getLength() + 1];
1826    
1827                  int j;
1828    
1829                  for (j = 0; j < THEletters.getLength(); j++)
1830                  {
1831                    table[j + 1] = THEletters.getChar(j);
1832                  }
1833    
1834                  table[0] = THEletters.getChar(j - 1);  // don't need this                                                                         
1835    
1836                  // index in "table" of the next char to emit
1837                  lookupIndex = (int)mult / groups.getInt(k);
1838    
1839                  //this should not happen
1840                  if (lookupIndex == 0 && mult == 0)
1841                    break;
1842    
1843                  char multiplierChar = ((CharArrayWrapper) (thisBundle.getObject(
1844                    org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER_CHAR))).getChar(i);
1845    
1846                  // put out the next character of output   
1847                  if (lookupIndex < table.length)
1848                  {
1849                    if (mult_order.equals(org.apache.xml.utils.res.XResourceBundle.MULT_PRECEDES))
1850                    {
1851                      buf[charPos++] = multiplierChar;
1852                      buf[charPos++] = table[lookupIndex];
1853                    }
1854                    else
1855                    {
1856    
1857                      // don't put out 1 (ie 1X10 is just 10)
1858                      if (lookupIndex == 1 && i == multiplier.getLength() - 1){}
1859                      else
1860                        buf[charPos++] = table[lookupIndex];
1861    
1862                      buf[charPos++] = multiplierChar;
1863                    }
1864    
1865                    break;  // all done!
1866                  }
1867                  else
1868                    return XSLTErrorResources.ERROR_STRING;
1869                }  //end else
1870              }  // end while        
1871    
1872              i++;
1873            }  // end else if
1874          }  // end do while
1875          while (i < multiplier.getLength());
1876        }
1877    
1878        // Now do additive part...
1879        int count = 0;
1880        String tableName;
1881    
1882        // do this for each table of hundreds, tens, digits...
1883        while (count < groups.getLength())
1884        {
1885          if (val / groups.getInt(count) <= 0)  // look for correct table
1886            count++;
1887          else
1888          {
1889            CharArrayWrapper theletters = (CharArrayWrapper) thisBundle.getObject(tables.getString(count));
1890    
1891            table = new char[theletters.getLength() + 1];
1892    
1893            int j;
1894    
1895            // need to start filling the table up at index 1
1896            for (j = 0; j < theletters.getLength(); j++)
1897            {
1898              table[j + 1] = theletters.getChar(j);
1899            }
1900    
1901            table[0] = theletters.getChar(j - 1);  // don't need this
1902    
1903            // index in "table" of the next char to emit
1904            lookupIndex = (int)val / groups.getInt(count);
1905    
1906            // shift input by one "column"
1907            val = val % groups.getInt(count);
1908    
1909            // this should not happen
1910            if (lookupIndex == 0 && val == 0)
1911              break;
1912    
1913            if (lookupIndex < table.length)
1914            {
1915    
1916              // put out the next character of output       
1917              buf[charPos++] = table[lookupIndex];  // left to right or top to bottom                                       
1918            }
1919            else
1920              return XSLTErrorResources.ERROR_STRING;
1921    
1922            count++;
1923          }
1924        }  // end while
1925    
1926        // String s = new String(buf, 0, charPos);
1927        return new String(buf, 0, charPos);
1928      }
1929    
1930      /**
1931       * Convert a long integer into roman numerals.
1932       * @param val Value to convert.
1933       * @param prefixesAreOK true_ to enable prefix notation (e.g. 4 = "IV"),
1934       * false_ to disable prefix notation (e.g. 4 = "IIII").
1935       * @return Roman numeral string.
1936       * @see DecimalToRoman
1937       * @see m_romanConvertTable
1938       */
1939      protected String long2roman(long val, boolean prefixesAreOK)
1940      {
1941    
1942        if (val <= 0)
1943        {
1944          return getZeroString();
1945        }
1946    
1947        final String roman;
1948        int place = 0;
1949    
1950        if (val <= 3999L)
1951        {
1952          StringBuffer romanBuffer = new StringBuffer();
1953          do
1954          {
1955            while (val >= m_romanConvertTable[place].m_postValue)
1956            {
1957              romanBuffer.append(m_romanConvertTable[place].m_postLetter);
1958              val -= m_romanConvertTable[place].m_postValue;
1959            }
1960    
1961            if (prefixesAreOK)
1962            {
1963              if (val >= m_romanConvertTable[place].m_preValue)
1964              {
1965                romanBuffer.append(m_romanConvertTable[place].m_preLetter);
1966                val -= m_romanConvertTable[place].m_preValue;
1967              }
1968            }
1969    
1970            place++;
1971          }
1972          while (val > 0);
1973          roman = romanBuffer.toString();
1974        }
1975        else
1976        {
1977          roman = XSLTErrorResources.ERROR_STRING;
1978        }
1979    
1980        return roman;
1981      }  // end long2roman
1982      
1983      /**
1984       * Call the children visitors.
1985       * @param visitor The visitor whose appropriate method will be called.
1986       */
1987      public void callChildVisitors(XSLTVisitor visitor, boolean callAttrs)
1988      {
1989            if(callAttrs)
1990            {
1991                    if(null != m_countMatchPattern)
1992                            m_countMatchPattern.getExpression().callVisitors(m_countMatchPattern, visitor);
1993                    if(null != m_fromMatchPattern)
1994                            m_fromMatchPattern.getExpression().callVisitors(m_fromMatchPattern, visitor);
1995                    if(null != m_valueExpr)
1996                            m_valueExpr.getExpression().callVisitors(m_valueExpr, visitor);
1997            
1998                    if(null != m_format_avt)
1999                            m_format_avt.callVisitors(visitor);
2000                    if(null != m_groupingSeparator_avt)
2001                            m_groupingSeparator_avt.callVisitors(visitor);
2002                    if(null != m_groupingSize_avt)
2003                            m_groupingSize_avt.callVisitors(visitor);
2004                    if(null != m_lang_avt)
2005                            m_lang_avt.callVisitors(visitor);
2006                    if(null != m_lettervalue_avt)
2007                            m_lettervalue_avt.callVisitors(visitor);
2008            }
2009    
2010        super.callChildVisitors(visitor, callAttrs);
2011      }
2012    
2013    
2014      /**
2015       * This class returns tokens using non-alphanumberic
2016       * characters as delimiters.
2017       */
2018      class NumberFormatStringTokenizer
2019      {
2020    
2021        /** Current position in the format string          */
2022        private int currentPosition;
2023    
2024        /** Index of last character in the format string      */
2025        private int maxPosition;
2026    
2027        /** Format string to be tokenized        */
2028        private String str;
2029    
2030        /**
2031         * Construct a NumberFormatStringTokenizer.
2032         *
2033         * @param str Format string to be tokenized
2034         */
2035        public NumberFormatStringTokenizer(String str)
2036        {
2037          this.str = str;
2038          maxPosition = str.length();
2039        }
2040    
2041        /**
2042         * Reset tokenizer so that nextToken() starts from the beginning.
2043         */
2044        public void reset()
2045        {
2046          currentPosition = 0;
2047        }
2048    
2049        /**
2050         * Returns the next token from this string tokenizer.
2051         *
2052         * @return     the next token from this string tokenizer.
2053         * @throws  NoSuchElementException  if there are no more tokens in this
2054         *               tokenizer's string.
2055         */
2056        public String nextToken()
2057        {
2058    
2059          if (currentPosition >= maxPosition)
2060          {
2061            throw new NoSuchElementException();
2062          }
2063    
2064          int start = currentPosition;
2065    
2066          while ((currentPosition < maxPosition)
2067                 && Character.isLetterOrDigit(str.charAt(currentPosition)))
2068          {
2069            currentPosition++;
2070          }
2071    
2072          if ((start == currentPosition)
2073                  && (!Character.isLetterOrDigit(str.charAt(currentPosition))))
2074          {
2075            currentPosition++;
2076          }
2077    
2078          return str.substring(start, currentPosition);
2079        }
2080    
2081        /**
2082         * Tells if there is a digit or a letter character ahead.
2083         *
2084         * @return     true if there is a number or character ahead.
2085         */
2086        public boolean isLetterOrDigitAhead()
2087        {
2088    
2089          int pos = currentPosition;
2090    
2091          while (pos < maxPosition)
2092          {
2093            if (Character.isLetterOrDigit(str.charAt(pos)))
2094              return true;
2095    
2096            pos++;
2097          }
2098    
2099          return false;
2100        }
2101    
2102        /**
2103         * Tells if there is a digit or a letter character ahead.
2104         *
2105         * @return     true if there is a number or character ahead.
2106         */
2107        public boolean nextIsSep()
2108        {
2109    
2110          if (Character.isLetterOrDigit(str.charAt(currentPosition)))
2111            return false;
2112          else
2113            return true;
2114        }
2115    
2116        /**
2117         * Tells if <code>nextToken</code> will throw an exception
2118         * if it is called.
2119         *
2120         * @return true if <code>nextToken</code> can be called
2121         * without throwing an exception.
2122         */
2123        public boolean hasMoreTokens()
2124        {
2125          return (currentPosition >= maxPosition) ? false : true;
2126        }
2127    
2128        /**
2129         * Calculates the number of times that this tokenizer's
2130         * <code>nextToken</code> method can be called before it generates an
2131         * exception.
2132         *
2133         * @return  the number of tokens remaining in the string using the current
2134         *          delimiter set.
2135         * @see     java.util.StringTokenizer#nextToken()
2136         */
2137        public int countTokens()
2138        {
2139    
2140          int count = 0;
2141          int currpos = currentPosition;
2142    
2143          while (currpos < maxPosition)
2144          {
2145            int start = currpos;
2146    
2147            while ((currpos < maxPosition)
2148                   && Character.isLetterOrDigit(str.charAt(currpos)))
2149            {
2150              currpos++;
2151            }
2152    
2153            if ((start == currpos)
2154                    && (Character.isLetterOrDigit(str.charAt(currpos)) == false))
2155            {
2156              currpos++;
2157            }
2158    
2159            count++;
2160          }
2161    
2162          return count;
2163        }
2164      }  // end NumberFormatStringTokenizer
2165    
2166    
2167    
2168    }