Home | History | Annotate | Download | only in templates
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one
      3  * or more contributor license agreements. See the NOTICE file
      4  * distributed with this work for additional information
      5  * regarding copyright ownership. The ASF licenses this file
      6  * to you under the Apache License, Version 2.0 (the  "License");
      7  * you may not use this file except in compliance with the License.
      8  * You may obtain a copy of the License at
      9  *
     10  *     http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software
     13  * distributed under the License is distributed on an "AS IS" BASIS,
     14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  * See the License for the specific language governing permissions and
     16  * limitations under the License.
     17  */
     18 /*
     19  * $Id: ElemNumber.java 468643 2006-10-28 06:56:03Z minchau $
     20  */
     21 package org.apache.xalan.templates;
     22 
     23 import java.text.DecimalFormat;
     24 import java.text.DecimalFormatSymbols;
     25 import java.text.NumberFormat;
     26 import java.util.Locale;
     27 import java.util.NoSuchElementException;
     28 
     29 import javax.xml.transform.TransformerException;
     30 
     31 import org.apache.xalan.res.XSLTErrorResources;
     32 import org.apache.xalan.transformer.CountersTable;
     33 import org.apache.xalan.transformer.DecimalToRoman;
     34 import org.apache.xalan.transformer.TransformerImpl;
     35 import org.apache.xml.dtm.DTM;
     36 import org.apache.xml.utils.FastStringBuffer;
     37 import org.apache.xml.utils.NodeVector;
     38 import org.apache.xml.utils.PrefixResolver;
     39 import org.apache.xml.utils.StringBufferPool;
     40 import org.apache.xml.utils.res.XResourceBundle;
     41 import org.apache.xml.utils.res.CharArrayWrapper;
     42 import org.apache.xml.utils.res.IntArrayWrapper;
     43 import org.apache.xml.utils.res.LongArrayWrapper;
     44 import org.apache.xml.utils.res.StringArrayWrapper;
     45 import org.apache.xpath.NodeSetDTM;
     46 import org.apache.xpath.XPath;
     47 import org.apache.xpath.XPathContext;
     48 import org.apache.xpath.objects.XObject;
     49 
     50 import org.w3c.dom.Node;
     51 
     52 import org.xml.sax.SAXException;
     53 
     54 // import org.apache.xalan.dtm.*;
     55 
     56 /**
     57  * Implement xsl:number.
     58  * <pre>
     59  * <!ELEMENT xsl:number EMPTY>
     60  * <!ATTLIST xsl:number
     61  *    level (single|multiple|any) "single"
     62  *    count %pattern; #IMPLIED
     63  *    from %pattern; #IMPLIED
     64  *    value %expr; #IMPLIED
     65  *    format %avt; '1'
     66  *    lang %avt; #IMPLIED
     67  *    letter-value %avt; #IMPLIED
     68  *    grouping-separator %avt; #IMPLIED
     69  *    grouping-size %avt; #IMPLIED
     70  * >
     71  * </pre>
     72  * @see <a href="http://www.w3.org/TR/xslt#number">number in XSLT Specification</a>
     73  * @xsl.usage advanced
     74  */
     75 public class ElemNumber extends ElemTemplateElement
     76 {
     77     static final long serialVersionUID = 8118472298274407610L;
     78 
     79     /**
     80      * Chars for converting integers into alpha counts.
     81      * @see TransformerImpl#int2alphaCount
     82      */
     83     private CharArrayWrapper m_alphaCountTable = null;
     84 
     85     private class MyPrefixResolver implements PrefixResolver {
     86 
     87         DTM dtm;
     88         int handle;
     89         boolean handleNullPrefix;
     90 
     91 		/**
     92 		 * Constructor for MyPrefixResolver.
     93 		 * @param xpathExpressionContext
     94 		 */
     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