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