/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: ElemNumber.java 468643 2006-10-28 06:56:03Z minchau $ */ package org.apache.xalan.templates; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.Locale; import java.util.NoSuchElementException; import javax.xml.transform.TransformerException; import org.apache.xalan.res.XSLTErrorResources; import org.apache.xalan.transformer.CountersTable; import org.apache.xalan.transformer.DecimalToRoman; import org.apache.xalan.transformer.TransformerImpl; import org.apache.xml.dtm.DTM; import org.apache.xml.utils.FastStringBuffer; import org.apache.xml.utils.NodeVector; import org.apache.xml.utils.PrefixResolver; import org.apache.xml.utils.StringBufferPool; import org.apache.xml.utils.res.XResourceBundle; import org.apache.xml.utils.res.CharArrayWrapper; import org.apache.xml.utils.res.IntArrayWrapper; import org.apache.xml.utils.res.LongArrayWrapper; import org.apache.xml.utils.res.StringArrayWrapper; import org.apache.xpath.NodeSetDTM; import org.apache.xpath.XPath; import org.apache.xpath.XPathContext; import org.apache.xpath.objects.XObject; import org.w3c.dom.Node; import org.xml.sax.SAXException; // import org.apache.xalan.dtm.*; /** * Implement xsl:number. *
 * 
 * 
 * 
* @see number in XSLT Specification * @xsl.usage advanced */ public class ElemNumber extends ElemTemplateElement { static final long serialVersionUID = 8118472298274407610L; /** * Chars for converting integers into alpha counts. * @see TransformerImpl#int2alphaCount */ private CharArrayWrapper m_alphaCountTable = null; private class MyPrefixResolver implements PrefixResolver { DTM dtm; int handle; boolean handleNullPrefix; /** * Constructor for MyPrefixResolver. * @param xpathExpressionContext */ public MyPrefixResolver(Node xpathExpressionContext, DTM dtm, int handle, boolean handleNullPrefix) { this.dtm = dtm; this.handle = handle; this.handleNullPrefix = handleNullPrefix; } /** * @see PrefixResolver#getNamespaceForPrefix(String, Node) */ public String getNamespaceForPrefix(String prefix) { return dtm.getNamespaceURI(handle); } /** * @see PrefixResolver#getNamespaceForPrefix(String, Node) * this shouldn't get called. */ public String getNamespaceForPrefix(String prefix, Node context) { return getNamespaceForPrefix(prefix); } /** * @see PrefixResolver#getBaseIdentifier() */ public String getBaseIdentifier() { return ElemNumber.this.getBaseIdentifier(); } /** * @see PrefixResolver#handlesNullPrefixes() */ public boolean handlesNullPrefixes() { return handleNullPrefix; } } /** * Only nodes are counted that match this pattern. * @serial */ private XPath m_countMatchPattern = null; /** * Set the "count" attribute. * The count attribute is a pattern that specifies what nodes * should be counted at those levels. If count attribute is not * specified, then it defaults to the pattern that matches any * node with the same node type as the current node and, if the * current node has an expanded-name, with the same expanded-name * as the current node. * * @param v Value to set for "count" attribute. */ public void setCount(XPath v) { m_countMatchPattern = v; } /** * Get the "count" attribute. * The count attribute is a pattern that specifies what nodes * should be counted at those levels. If count attribute is not * specified, then it defaults to the pattern that matches any * node with the same node type as the current node and, if the * current node has an expanded-name, with the same expanded-name * as the current node. * * @return Value of "count" attribute. */ public XPath getCount() { return m_countMatchPattern; } /** * Specifies where to count from. * For level="single" or level="multiple": * Only ancestors that are searched are * those that are descendants of the nearest ancestor that matches * the from pattern. * For level="any: * Only nodes after the first node before the * current node that match the from pattern are considered. * @serial */ private XPath m_fromMatchPattern = null; /** * Set the "from" attribute. Specifies where to count from. * For level="single" or level="multiple": * Only ancestors that are searched are * those that are descendants of the nearest ancestor that matches * the from pattern. * For level="any: * Only nodes after the first node before the * current node that match the from pattern are considered. * * @param v Value to set for "from" attribute. */ public void setFrom(XPath v) { m_fromMatchPattern = v; } /** * Get the "from" attribute. * For level="single" or level="multiple": * Only ancestors that are searched are * those that are descendants of the nearest ancestor that matches * the from pattern. * For level="any: * Only nodes after the first node before the * current node that match the from pattern are considered. * * @return Value of "from" attribute. */ public XPath getFrom() { return m_fromMatchPattern; } /** * When level="single", it goes up to the first node in the ancestor-or-self axis * that matches the count pattern, and constructs a list of length one containing * one plus the number of preceding siblings of that ancestor that match the count * pattern. If there is no such ancestor, it constructs an empty list. If the from * attribute is specified, then the only ancestors that are searched are those * that are descendants of the nearest ancestor that matches the from pattern. * Preceding siblings has the same meaning here as with the preceding-sibling axis. * * When level="multiple", it constructs a list of all ancestors of the current node * in document order followed by the element itself; it then selects from the list * those nodes that match the count pattern; it then maps each node in the list to * one plus the number of preceding siblings of that node that match the count pattern. * If the from attribute is specified, then the only ancestors that are searched are * those that are descendants of the nearest ancestor that matches the from pattern. * Preceding siblings has the same meaning here as with the preceding-sibling axis. * * When level="any", it constructs a list of length one containing the number of * nodes that match the count pattern and belong to the set containing the current * node and all nodes at any level of the document that are before the current node * in document order, excluding any namespace and attribute nodes (in other words * the union of the members of the preceding and ancestor-or-self axes). If the * from attribute is specified, then only nodes after the first node before the * current node that match the from pattern are considered. * @serial */ private int m_level = Constants.NUMBERLEVEL_SINGLE; /** * Set the "level" attribute. * The level attribute specifies what levels of the source tree should * be considered; it has the values single, multiple or any. The default * is single. * * @param v Value to set for "level" attribute. */ public void setLevel(int v) { m_level = v; } /** * Get the "level" attribute. * The level attribute specifies what levels of the source tree should * be considered; it has the values single, multiple or any. The default * is single. * * @return Value of "level" attribute. */ public int getLevel() { return m_level; } /** * The value attribute contains an expression. The expression is evaluated * and the resulting object is converted to a number as if by a call to the * number function. * @serial */ private XPath m_valueExpr = null; /** * Set the "value" attribute. * The value attribute contains an expression. The expression is evaluated * and the resulting object is converted to a number as if by a call to the * number function. * * @param v Value to set for "value" attribute. */ public void setValue(XPath v) { m_valueExpr = v; } /** * Get the "value" attribute. * The value attribute contains an expression. The expression is evaluated * and the resulting object is converted to a number as if by a call to the * number function. * * @return Value of "value" attribute. */ public XPath getValue() { return m_valueExpr; } /** * The "format" attribute is used to control conversion of a list of * numbers into a string. * @see convert in XSLT Specification * @serial */ private AVT m_format_avt = null; /** * Set the "format" attribute. * The "format" attribute is used to control conversion of a list of * numbers into a string. * @see convert in XSLT Specification * * @param v Value to set for "format" attribute. */ public void setFormat(AVT v) { m_format_avt = v; } /** * Get the "format" attribute. * The "format" attribute is used to control conversion of a list of * numbers into a string. * @see convert in XSLT Specification * * @return Value of "format" attribute. */ public AVT getFormat() { return m_format_avt; } /** * When numbering with an alphabetic sequence, the lang attribute * specifies which language's alphabet is to be used. * @serial */ private AVT m_lang_avt = null; /** * Set the "lang" attribute. * When numbering with an alphabetic sequence, the lang attribute * specifies which language's alphabet is to be used; it has the same * range of values as xml:lang [XML]; if no lang value is specified, * the language should be determined from the system environment. * Implementers should document for which languages they support numbering. * @see convert in XSLT Specification * * @param v Value to set for "lang" attribute. */ public void setLang(AVT v) { m_lang_avt = v; } /** * Get the "lang" attribute. * When numbering with an alphabetic sequence, the lang attribute * specifies which language's alphabet is to be used; it has the same * range of values as xml:lang [XML]; if no lang value is specified, * the language should be determined from the system environment. * Implementers should document for which languages they support numbering. * @see convert in XSLT Specification * * @return Value ofr "lang" attribute. */ public AVT getLang() { return m_lang_avt; } /** * The letter-value attribute disambiguates between numbering * sequences that use letters. * @serial */ private AVT m_lettervalue_avt = null; /** * Set the "letter-value" attribute. * The letter-value attribute disambiguates between numbering sequences * that use letters. * @see convert in XSLT Specification * * @param v Value to set for "letter-value" attribute. */ public void setLetterValue(AVT v) { m_lettervalue_avt = v; } /** * Get the "letter-value" attribute. * The letter-value attribute disambiguates between numbering sequences * that use letters. * @see convert in XSLT Specification * * @return Value to set for "letter-value" attribute. */ public AVT getLetterValue() { return m_lettervalue_avt; } /** * The grouping-separator attribute gives the separator * used as a grouping (e.g. thousands) separator in decimal * numbering sequences. * @serial */ private AVT m_groupingSeparator_avt = null; /** * Set the "grouping-separator" attribute. * The grouping-separator attribute gives the separator * used as a grouping (e.g. thousands) separator in decimal * numbering sequences. * @see convert in XSLT Specification * * @param v Value to set for "grouping-separator" attribute. */ public void setGroupingSeparator(AVT v) { m_groupingSeparator_avt = v; } /** * Get the "grouping-separator" attribute. * The grouping-separator attribute gives the separator * used as a grouping (e.g. thousands) separator in decimal * numbering sequences. * @see convert in XSLT Specification * * @return Value of "grouping-separator" attribute. */ public AVT getGroupingSeparator() { return m_groupingSeparator_avt; } /** * The optional grouping-size specifies the size (normally 3) of the grouping. * @serial */ private AVT m_groupingSize_avt = null; /** * Set the "grouping-size" attribute. * The optional grouping-size specifies the size (normally 3) of the grouping. * @see convert in XSLT Specification * * @param v Value to set for "grouping-size" attribute. */ public void setGroupingSize(AVT v) { m_groupingSize_avt = v; } /** * Get the "grouping-size" attribute. * The optional grouping-size specifies the size (normally 3) of the grouping. * @see convert in XSLT Specification * * @return Value of "grouping-size" attribute. */ public AVT getGroupingSize() { return m_groupingSize_avt; } /** * Shouldn't this be in the transformer? Big worries about threads... */ // private XResourceBundle thisBundle; /** * Table to help in converting decimals to roman numerals. * @see org.apache.xalan.transformer.DecimalToRoman */ private final static DecimalToRoman m_romanConvertTable[] = { new DecimalToRoman(1000, "M", 900, "CM"), new DecimalToRoman(500, "D", 400, "CD"), new DecimalToRoman(100L, "C", 90L, "XC"), new DecimalToRoman(50L, "L", 40L, "XL"), new DecimalToRoman(10L, "X", 9L, "IX"), new DecimalToRoman(5L, "V", 4L, "IV"), new DecimalToRoman(1L, "I", 1L, "I") }; /** * This function is called after everything else has been * recomposed, and allows the template to set remaining * values that may be based on some other property that * depends on recomposition. */ public void compose(StylesheetRoot sroot) throws TransformerException { super.compose(sroot); StylesheetRoot.ComposeState cstate = sroot.getComposeState(); java.util.Vector vnames = cstate.getVariableNames(); if(null != m_countMatchPattern) m_countMatchPattern.fixupVariables(vnames, cstate.getGlobalsSize()); if(null != m_format_avt) m_format_avt.fixupVariables(vnames, cstate.getGlobalsSize()); if(null != m_fromMatchPattern) m_fromMatchPattern.fixupVariables(vnames, cstate.getGlobalsSize()); if(null != m_groupingSeparator_avt) m_groupingSeparator_avt.fixupVariables(vnames, cstate.getGlobalsSize()); if(null != m_groupingSize_avt) m_groupingSize_avt.fixupVariables(vnames, cstate.getGlobalsSize()); if(null != m_lang_avt) m_lang_avt.fixupVariables(vnames, cstate.getGlobalsSize()); if(null != m_lettervalue_avt) m_lettervalue_avt.fixupVariables(vnames, cstate.getGlobalsSize()); if(null != m_valueExpr) m_valueExpr.fixupVariables(vnames, cstate.getGlobalsSize()); } /** * Get an int constant identifying the type of element. * @see org.apache.xalan.templates.Constants * * @return The token ID for this element */ public int getXSLToken() { return Constants.ELEMNAME_NUMBER; } /** * Return the node name. * * @return The element's name */ public String getNodeName() { return Constants.ELEMNAME_NUMBER_STRING; } /** * Execute an xsl:number instruction. The xsl:number element is * used to insert a formatted number into the result tree. * * @param transformer non-null reference to the the current transform-time state. * * @throws TransformerException */ public void execute( TransformerImpl transformer) throws TransformerException { int sourceNode = transformer.getXPathContext().getCurrentNode(); String countString = getCountString(transformer, sourceNode); try { transformer.getResultTreeHandler().characters(countString.toCharArray(), 0, countString.length()); } catch(SAXException se) { throw new TransformerException(se); } } /** * Add a child to the child list. * * @param newChild Child to add to child list * * @return Child just added to child list * * @throws DOMException */ public ElemTemplateElement appendChild(ElemTemplateElement newChild) { error(XSLTErrorResources.ER_CANNOT_ADD, new Object[]{ newChild.getNodeName(), this.getNodeName() }); //"Can not add " +((ElemTemplateElement)newChild).m_elemName + //" to " + this.m_elemName); return null; } /** * Given a 'from' pattern (ala xsl:number), a match pattern * and a context, find the first ancestor that matches the * pattern (including the context handed in). * * @param xctxt The XPath runtime state for this. * @param fromMatchPattern The ancestor must match this pattern. * @param countMatchPattern The ancestor must also match this pattern. * @param context The node that "." expresses. * @param namespaceContext The context in which namespaces in the * queries are supposed to be expanded. * * @return the first ancestor that matches the given pattern * * @throws javax.xml.transform.TransformerException */ int findAncestor( XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern, int context, ElemNumber namespaceContext) throws javax.xml.transform.TransformerException { DTM dtm = xctxt.getDTM(context); while (DTM.NULL != context) { if (null != fromMatchPattern) { if (fromMatchPattern.getMatchScore(xctxt, context) != XPath.MATCH_SCORE_NONE) { //context = null; break; } } if (null != countMatchPattern) { if (countMatchPattern.getMatchScore(xctxt, context) != XPath.MATCH_SCORE_NONE) { break; } } context = dtm.getParent(context); } return context; } /** * Given a 'from' pattern (ala xsl:number), a match pattern * and a context, find the first ancestor that matches the * pattern (including the context handed in). * @param xctxt The XPath runtime state for this. * @param fromMatchPattern The ancestor must match this pattern. * @param countMatchPattern The ancestor must also match this pattern. * @param context The node that "." expresses. * @param namespaceContext The context in which namespaces in the * queries are supposed to be expanded. * * @return the first preceding, ancestor or self node that * matches the given pattern * * @throws javax.xml.transform.TransformerException */ private int findPrecedingOrAncestorOrSelf( XPathContext xctxt, XPath fromMatchPattern, XPath countMatchPattern, int context, ElemNumber namespaceContext) throws javax.xml.transform.TransformerException { DTM dtm = xctxt.getDTM(context); while (DTM.NULL != context) { if (null != fromMatchPattern) { if (fromMatchPattern.getMatchScore(xctxt, context) != XPath.MATCH_SCORE_NONE) { context = DTM.NULL; break; } } if (null != countMatchPattern) { if (countMatchPattern.getMatchScore(xctxt, context) != XPath.MATCH_SCORE_NONE) { break; } } int prevSibling = dtm.getPreviousSibling(context); if (DTM.NULL == prevSibling) { context = dtm.getParent(context); } else { // Now go down the chain of children of this sibling context = dtm.getLastChild(prevSibling); if (context == DTM.NULL) context = prevSibling; } } return context; } /** * Get the count match pattern, or a default value. * * @param support The XPath runtime state for this. * @param contextNode The node that "." expresses. * * @return the count match pattern, or a default value. * * @throws javax.xml.transform.TransformerException */ XPath getCountMatchPattern(XPathContext support, int contextNode) throws javax.xml.transform.TransformerException { XPath countMatchPattern = m_countMatchPattern; DTM dtm = support.getDTM(contextNode); if (null == countMatchPattern) { switch (dtm.getNodeType(contextNode)) { case DTM.ELEMENT_NODE : MyPrefixResolver resolver; if (dtm.getNamespaceURI(contextNode) == null) { resolver = new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, false); } else { resolver = new MyPrefixResolver(dtm.getNode(contextNode), dtm,contextNode, true); } countMatchPattern = new XPath(dtm.getNodeName(contextNode), this, resolver, XPath.MATCH, support.getErrorListener()); break; case DTM.ATTRIBUTE_NODE : // countMatchPattern = m_stylesheet.createMatchPattern("@"+contextNode.getNodeName(), this); countMatchPattern = new XPath("@" + dtm.getNodeName(contextNode), this, this, XPath.MATCH, support.getErrorListener()); break; case DTM.CDATA_SECTION_NODE : case DTM.TEXT_NODE : // countMatchPattern = m_stylesheet.createMatchPattern("text()", this); countMatchPattern = new XPath("text()", this, this, XPath.MATCH, support.getErrorListener()); break; case DTM.COMMENT_NODE : // countMatchPattern = m_stylesheet.createMatchPattern("comment()", this); countMatchPattern = new XPath("comment()", this, this, XPath.MATCH, support.getErrorListener()); break; case DTM.DOCUMENT_NODE : // countMatchPattern = m_stylesheet.createMatchPattern("/", this); countMatchPattern = new XPath("/", this, this, XPath.MATCH, support.getErrorListener()); break; case DTM.PROCESSING_INSTRUCTION_NODE : // countMatchPattern = m_stylesheet.createMatchPattern("pi("+contextNode.getNodeName()+")", this); countMatchPattern = new XPath("pi(" + dtm.getNodeName(contextNode) + ")", this, this, XPath.MATCH, support.getErrorListener()); break; default : countMatchPattern = null; } } return countMatchPattern; } /** * Given an XML source node, get the count according to the * parameters set up by the xsl:number attributes. * @param transformer non-null reference to the the current transform-time state. * @param sourceNode The source node being counted. * * @return The count of nodes * * @throws TransformerException */ String getCountString(TransformerImpl transformer, int sourceNode) throws TransformerException { long[] list = null; XPathContext xctxt = transformer.getXPathContext(); CountersTable ctable = transformer.getCountersTable(); if (null != m_valueExpr) { XObject countObj = m_valueExpr.execute(xctxt, sourceNode, this); //According to Errata E24 double d_count = java.lang.Math.floor(countObj.num()+ 0.5); if (Double.isNaN(d_count)) return "NaN"; else if (d_count < 0 && Double.isInfinite(d_count)) return "-Infinity"; else if (Double.isInfinite(d_count)) return "Infinity"; else if (d_count == 0) return "0"; else{ long count = (long)d_count; list = new long[1]; list[0] = count; } } else { if (Constants.NUMBERLEVEL_ANY == m_level) { list = new long[1]; list[0] = ctable.countNode(xctxt, this, sourceNode); } else { NodeVector ancestors = getMatchingAncestors(xctxt, sourceNode, Constants.NUMBERLEVEL_SINGLE == m_level); int lastIndex = ancestors.size() - 1; if (lastIndex >= 0) { list = new long[lastIndex + 1]; for (int i = lastIndex; i >= 0; i--) { int target = ancestors.elementAt(i); list[lastIndex - i] = ctable.countNode(xctxt, this, target); } } } } return (null != list) ? formatNumberList(transformer, list, sourceNode) : ""; } /** * Get the previous node to be counted. * * @param xctxt The XPath runtime state for this. * @param pos The current node * * @return the previous node to be counted. * * @throws TransformerException */ public int getPreviousNode(XPathContext xctxt, int pos) throws TransformerException { XPath countMatchPattern = getCountMatchPattern(xctxt, pos); DTM dtm = xctxt.getDTM(pos); if (Constants.NUMBERLEVEL_ANY == m_level) { XPath fromMatchPattern = m_fromMatchPattern; // Do a backwards document-order walk 'till a node is found that matches // the 'from' pattern, or a node is found that matches the 'count' pattern, // or the top of the tree is found. while (DTM.NULL != pos) { // Get the previous sibling, if there is no previous sibling, // then count the parent, but if there is a previous sibling, // dive down to the lowest right-hand (last) child of that sibling. int next = dtm.getPreviousSibling(pos); if (DTM.NULL == next) { next = dtm.getParent(pos); if ((DTM.NULL != next) && ((((null != fromMatchPattern) && (fromMatchPattern.getMatchScore( xctxt, next) != XPath.MATCH_SCORE_NONE))) || (dtm.getNodeType(next) == DTM.DOCUMENT_NODE))) { pos = DTM.NULL; // return null from function. break; // from while loop } } else { // dive down to the lowest right child. int child = next; while (DTM.NULL != child) { child = dtm.getLastChild(next); if (DTM.NULL != child) next = child; } } pos = next; if ((DTM.NULL != pos) && ((null == countMatchPattern) || (countMatchPattern.getMatchScore(xctxt, pos) != XPath.MATCH_SCORE_NONE))) { break; } } } else // NUMBERLEVEL_MULTI or NUMBERLEVEL_SINGLE { while (DTM.NULL != pos) { pos = dtm.getPreviousSibling(pos); if ((DTM.NULL != pos) && ((null == countMatchPattern) || (countMatchPattern.getMatchScore(xctxt, pos) != XPath.MATCH_SCORE_NONE))) { break; } } } return pos; } /** * Get the target node that will be counted.. * * @param xctxt The XPath runtime state for this. * @param sourceNode non-null reference to the current source node. * * @return the target node that will be counted * * @throws TransformerException */ public int getTargetNode(XPathContext xctxt, int sourceNode) throws TransformerException { int target = DTM.NULL; XPath countMatchPattern = getCountMatchPattern(xctxt, sourceNode); if (Constants.NUMBERLEVEL_ANY == m_level) { target = findPrecedingOrAncestorOrSelf(xctxt, m_fromMatchPattern, countMatchPattern, sourceNode, this); } else { target = findAncestor(xctxt, m_fromMatchPattern, countMatchPattern, sourceNode, this); } return target; } /** * Get the ancestors, up to the root, that match the * pattern. * * @param xctxt The XPath runtime state for this. * @param node Count this node and it's ancestors. * @param stopAtFirstFound Flag indicating to stop after the * first node is found (difference between level = single * or multiple) * @return The number of ancestors that match the pattern. * * @throws javax.xml.transform.TransformerException */ NodeVector getMatchingAncestors( XPathContext xctxt, int node, boolean stopAtFirstFound) throws javax.xml.transform.TransformerException { NodeSetDTM ancestors = new NodeSetDTM(xctxt.getDTMManager()); XPath countMatchPattern = getCountMatchPattern(xctxt, node); DTM dtm = xctxt.getDTM(node); while (DTM.NULL != node) { if ((null != m_fromMatchPattern) && (m_fromMatchPattern.getMatchScore(xctxt, node) != XPath.MATCH_SCORE_NONE)) { // The following if statement gives level="single" different // behavior from level="multiple", which seems incorrect according // to the XSLT spec. For now we are leaving this in to replicate // the same behavior in XT, but, for all intents and purposes we // think this is a bug, or there is something about level="single" // that we still don't understand. if (!stopAtFirstFound) break; } if (null == countMatchPattern) System.out.println( "Programmers error! countMatchPattern should never be null!"); if (countMatchPattern.getMatchScore(xctxt, node) != XPath.MATCH_SCORE_NONE) { ancestors.addElement(node); if (stopAtFirstFound) break; } node = dtm.getParent(node); } return ancestors; } // end getMatchingAncestors method /** * Get the locale we should be using. * * @param transformer non-null reference to the the current transform-time state. * @param contextNode The node that "." expresses. * * @return The locale to use. May be specified by "lang" attribute, * but if not, use default locale on the system. * * @throws TransformerException */ Locale getLocale(TransformerImpl transformer, int contextNode) throws TransformerException { Locale locale = null; if (null != m_lang_avt) { XPathContext xctxt = transformer.getXPathContext(); String langValue = m_lang_avt.evaluate(xctxt, contextNode, this); if (null != langValue) { // Not really sure what to do about the country code, so I use the // default from the system. // TODO: fix xml:lang handling. locale = new Locale(langValue.toUpperCase(), ""); //Locale.getDefault().getDisplayCountry()); if (null == locale) { transformer.getMsgMgr().warn(this, null, xctxt.getDTM(contextNode).getNode(contextNode), XSLTErrorResources.WG_LOCALE_NOT_FOUND, new Object[]{ langValue }); //"Warning: Could not find locale for xml:lang="+langValue); locale = Locale.getDefault(); } } } else { locale = Locale.getDefault(); } return locale; } /** * Get the number formatter to be used the format the numbers * * @param transformer non-null reference to the the current transform-time state. * @param contextNode The node that "." expresses. * * ($objectName$) @return The number formatter to be used * * @throws TransformerException */ private DecimalFormat getNumberFormatter( TransformerImpl transformer, int contextNode) throws TransformerException { // Patch from Steven Serocki // Maybe we really want to do the clone in getLocale() and return // a clone of the default Locale?? Locale locale = (Locale)getLocale(transformer, contextNode).clone(); // Helper to format local specific numbers to strings. DecimalFormat formatter = null; //synchronized (locale) //{ // formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale); //} String digitGroupSepValue = (null != m_groupingSeparator_avt) ? m_groupingSeparator_avt.evaluate( transformer.getXPathContext(), contextNode, this) : null; // Validate grouping separator if an AVT was used; otherwise this was // validated statically in XSLTAttributeDef.java. if ((digitGroupSepValue != null) && (!m_groupingSeparator_avt.isSimple()) && (digitGroupSepValue.length() != 1)) { transformer.getMsgMgr().warn( this, XSLTErrorResources.WG_ILLEGAL_ATTRIBUTE_VALUE, new Object[]{ Constants.ATTRNAME_NAME, m_groupingSeparator_avt.getName()}); } String nDigitsPerGroupValue = (null != m_groupingSize_avt) ? m_groupingSize_avt.evaluate( transformer.getXPathContext(), contextNode, this) : null; // TODO: Handle digit-group attributes if ((null != digitGroupSepValue) && (null != nDigitsPerGroupValue) && // Ignore if separation value is empty string (digitGroupSepValue.length() > 0)) { try { formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale); formatter.setGroupingSize( Integer.valueOf(nDigitsPerGroupValue).intValue()); DecimalFormatSymbols symbols = formatter.getDecimalFormatSymbols(); symbols.setGroupingSeparator(digitGroupSepValue.charAt(0)); formatter.setDecimalFormatSymbols(symbols); formatter.setGroupingUsed(true); } catch (NumberFormatException ex) { formatter.setGroupingUsed(false); } } return formatter; } /** * Format a vector of numbers into a formatted string. * * @param transformer non-null reference to the the current transform-time state. * @param list Array of one or more long integer numbers. * @param contextNode The node that "." expresses. * @return String that represents list according to * %conversion-atts; attributes. * TODO: Optimize formatNumberList so that it caches the last count and * reuses that info for the next count. * * @throws TransformerException */ String formatNumberList( TransformerImpl transformer, long[] list, int contextNode) throws TransformerException { String numStr; FastStringBuffer formattedNumber = StringBufferPool.get(); try { int nNumbers = list.length, numberWidth = 1; char numberType = '1'; String formatToken, lastSepString = null, formatTokenString = null; // If a seperator hasn't been specified, then use "." // as a default separator. // For instance: [2][1][5] with a format value of "1 " // should format to "2.1.5 " (I think). // Otherwise, use the seperator specified in the format string. // For instance: [2][1][5] with a format value of "01-001. " // should format to "02-001-005 ". String lastSep = "."; boolean isFirstToken = true; // true if first token String formatValue = (null != m_format_avt) ? m_format_avt.evaluate( transformer.getXPathContext(), contextNode, this) : null; if (null == formatValue) formatValue = "1"; NumberFormatStringTokenizer formatTokenizer = new NumberFormatStringTokenizer(formatValue); // int sepCount = 0; // keep track of seperators // Loop through all the numbers in the list. for (int i = 0; i < nNumbers; i++) { // Loop to the next digit, letter, or separator. if (formatTokenizer.hasMoreTokens()) { formatToken = formatTokenizer.nextToken(); // If the first character of this token is a character or digit, then // it is a number format directive. if (Character.isLetterOrDigit( formatToken.charAt(formatToken.length() - 1))) { numberWidth = formatToken.length(); numberType = formatToken.charAt(numberWidth - 1); } // If there is a number format directive ahead, // then append the formatToken. else if (formatTokenizer.isLetterOrDigitAhead()) { formatTokenString = formatToken; // Append the formatToken string... // For instance [2][1][5] with a format value of "1--1. " // should format to "2--1--5. " (I guess). while (formatTokenizer.nextIsSep()) { formatToken = formatTokenizer.nextToken(); formatTokenString += formatToken; } // Record this separator, so it can be used as the // next separator, if the next is the last. // For instance: [2][1][5] with a format value of "1-1 " // should format to "2-1-5 ". if (!isFirstToken) lastSep = formatTokenString; // Since we know the next is a number or digit, we get it now. formatToken = formatTokenizer.nextToken(); numberWidth = formatToken.length(); numberType = formatToken.charAt(numberWidth - 1); } else // only separators left { // Set up the string for the trailing characters after // the last number is formatted (i.e. after the loop). lastSepString = formatToken; // And append any remaining characters to the lastSepString. while (formatTokenizer.hasMoreTokens()) { formatToken = formatTokenizer.nextToken(); lastSepString += formatToken; } } // else } // end if(formatTokenizer.hasMoreTokens()) // if this is the first token and there was a prefix // append the prefix else, append the separator // For instance, [2][1][5] with a format value of "(1-1.) " // should format to "(2-1-5.) " (I guess). if (null != formatTokenString && isFirstToken) { formattedNumber.append(formatTokenString); } else if (null != lastSep &&!isFirstToken) formattedNumber.append(lastSep); getFormattedNumber(transformer, contextNode, numberType, numberWidth, list[i], formattedNumber); isFirstToken = false; // After the first pass, this should be false } // end for loop // Check to see if we finished up the format string... // Skip past all remaining letters or digits while (formatTokenizer.isLetterOrDigitAhead()) { formatTokenizer.nextToken(); } if (lastSepString != null) formattedNumber.append(lastSepString); while (formatTokenizer.hasMoreTokens()) { formatToken = formatTokenizer.nextToken(); formattedNumber.append(formatToken); } numStr = formattedNumber.toString(); } finally { StringBufferPool.free(formattedNumber); } return numStr; } // end formatNumberList method /* * Get Formatted number */ /** * Format the given number and store it in the given buffer * * * @param transformer non-null reference to the the current transform-time state. * @param contextNode The node that "." expresses. * @param numberType Type to format to * @param numberWidth Maximum length of formatted number * @param listElement Number to format * @param formattedNumber Buffer to store formatted number * * @throws javax.xml.transform.TransformerException */ private void getFormattedNumber( TransformerImpl transformer, int contextNode, char numberType, int numberWidth, long listElement, FastStringBuffer formattedNumber) throws javax.xml.transform.TransformerException { String letterVal = (m_lettervalue_avt != null) ? m_lettervalue_avt.evaluate( transformer.getXPathContext(), contextNode, this) : null; /** * Wrapper of Chars for converting integers into alpha counts. */ CharArrayWrapper alphaCountTable = null; XResourceBundle thisBundle = null; switch (numberType) { case 'A' : if (null == m_alphaCountTable){ thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, getLocale(transformer, contextNode)); m_alphaCountTable = (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET); } int2alphaCount(listElement, m_alphaCountTable, formattedNumber); break; case 'a' : if (null == m_alphaCountTable){ thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, getLocale(transformer, contextNode)); m_alphaCountTable = (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET); } FastStringBuffer stringBuf = StringBufferPool.get(); try { int2alphaCount(listElement, m_alphaCountTable, stringBuf); formattedNumber.append( stringBuf.toString().toLowerCase( getLocale(transformer, contextNode))); } finally { StringBufferPool.free(stringBuf); } break; case 'I' : formattedNumber.append(long2roman(listElement, true)); break; case 'i' : formattedNumber.append( long2roman(listElement, true).toLowerCase( getLocale(transformer, contextNode))); break; case 0x3042 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "HA")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) formattedNumber.append( int2singlealphaCount( listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET))); break; } case 0x3044 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "HI")); if ((letterVal != null) && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) formattedNumber.append( int2singlealphaCount( listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET))); break; } case 0x30A2 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "A")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) formattedNumber.append( int2singlealphaCount( listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET))); break; } case 0x30A4 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ja", "JP", "I")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) formattedNumber.append( int2singlealphaCount( listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET))); break; } case 0x4E00 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("zh", "CN")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) { formattedNumber.append(tradAlphaCount(listElement, thisBundle)); } else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) int2alphaCount(listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET), formattedNumber); break; } case 0x58F9 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("zh", "TW")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) int2alphaCount(listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET), formattedNumber); break; } case 0x0E51 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("th", "")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) int2alphaCount(listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET), formattedNumber); break; } case 0x05D0 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("he", "")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) int2alphaCount(listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET), formattedNumber); break; } case 0x10D0 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("ka", "")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) int2alphaCount(listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET), formattedNumber); break; } case 0x03B1 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("el", "")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) int2alphaCount(listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET), formattedNumber); break; } case 0x0430 : { thisBundle = (XResourceBundle) XResourceBundle.loadResourceBundle( org.apache.xml.utils.res.XResourceBundle.LANG_BUNDLE_NAME, new Locale("cy", "")); if (letterVal != null && letterVal.equals(Constants.ATTRVAL_TRADITIONAL)) formattedNumber.append(tradAlphaCount(listElement, thisBundle)); else //if (m_lettervalue_avt != null && m_lettervalue_avt.equals(Constants.ATTRVAL_ALPHABETIC)) int2alphaCount(listElement, (CharArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_ALPHABET), formattedNumber); break; } default : // "1" DecimalFormat formatter = getNumberFormatter(transformer, contextNode); String padString = formatter == null ? String.valueOf(0) : formatter.format(0); String numString = formatter == null ? String.valueOf(listElement) : formatter.format(listElement); int nPadding = numberWidth - numString.length(); for (int k = 0; k < nPadding; k++) { formattedNumber.append(padString); } formattedNumber.append(numString); } } /** * Get a string value for zero, which is not really defined by the 1.0 spec, * thought I think it might be cleared up by the erreta. */ String getZeroString() { return ""+0; } /** * Convert a long integer into alphabetic counting, in other words * count using the sequence A B C ... Z. * * @param val Value to convert -- must be greater than zero. * @param table a table containing one character for each digit in the radix * @return String representing alpha count of number. * @see TransformerImpl#DecimalToRoman * * Note that the radix of the conversion is inferred from the size * of the table. */ protected String int2singlealphaCount(long val, CharArrayWrapper table) { int radix = table.getLength(); // TODO: throw error on out of range input if (val > radix) { return getZeroString(); } else return (new Character(table.getChar((int)val - 1))).toString(); // index into table is off one, starts at 0 } /** * Convert a long integer into alphabetic counting, in other words * count using the sequence A B C ... Z AA AB AC.... etc. * * @param val Value to convert -- must be greater than zero. * @param table a table containing one character for each digit in the radix * @param aTable Array of alpha characters representing numbers * @param stringBuf Buffer where to save the string representing alpha count of number. * * @see TransformerImpl#DecimalToRoman * * Note that the radix of the conversion is inferred from the size * of the table. */ protected void int2alphaCount(long val, CharArrayWrapper aTable, FastStringBuffer stringBuf) { int radix = aTable.getLength(); char[] table = new char[radix]; // start table at 1, add last char at index 0. Reason explained above and below. int i; for (i = 0; i < radix - 1; i++) { table[i + 1] = aTable.getChar(i); } table[0] = aTable.getChar(i); // Create a buffer to hold the result // TODO: size of the table can be detereined by computing // logs of the radix. For now, we fake it. char buf[] = new char[100]; //some languages go left to right(ie. english), right to left (ie. Hebrew), //top to bottom (ie.Japanese), etc... Handle them differently //String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION); // next character to set in the buffer int charPos; charPos = buf.length - 1; // work backward through buf[] // index in table of the last character that we stored int lookupIndex = 1; // start off with anything other than zero to make correction work // Correction number // // Correction can take on exactly two values: // // 0 if the next character is to be emitted is usual // // radix - 1 // if the next char to be emitted should be one less than // you would expect // // For example, consider radix 10, where 1="A" and 10="J" // // In this scheme, we count: A, B, C ... H, I, J (not A0 and certainly // not AJ), A1 // // So, how do we keep from emitting AJ for 10? After correctly emitting the // J, lookupIndex is zero. We now compute a correction number of 9 (radix-1). // In the following line, we'll compute (val+correction) % radix, which is, // (val+9)/10. By this time, val is 1, so we compute (1+9) % 10, which // is 10 % 10 or zero. So, we'll prepare to emit "JJ", but then we'll // later suppress the leading J as representing zero (in the mod system, // it can represent either 10 or zero). In summary, the correction value of // "radix-1" acts like "-1" when run through the mod operator, but with the // desireable characteristic that it never produces a negative number. long correction = 0; // TODO: throw error on out of range input do { // most of the correction calculation is explained above, the reason for the // term after the "|| " is that it correctly propagates carries across // multiple columns. correction = ((lookupIndex == 0) || (correction != 0 && lookupIndex == radix - 1)) ? (radix - 1) : 0; // index in "table" of the next char to emit lookupIndex = (int)(val + correction) % radix; // shift input by one "column" val = (val / radix); // if the next value we'd put out would be a leading zero, we're done. if (lookupIndex == 0 && val == 0) break; // put out the next character of output buf[charPos--] = table[lookupIndex]; // left to right or top to bottom } while (val > 0); stringBuf.append(buf, charPos + 1, (buf.length - charPos - 1)); } /** * Convert a long integer into traditional alphabetic counting, in other words * count using the traditional numbering. * * @param val Value to convert -- must be greater than zero. * @param thisBundle Resource bundle to use * * @return String representing alpha count of number. * @see XSLProcessor#DecimalToRoman * * Note that the radix of the conversion is inferred from the size * of the table. */ protected String tradAlphaCount(long val, XResourceBundle thisBundle) { // if this number is larger than the largest number we can represent, error! if (val > Long.MAX_VALUE) { this.error(XSLTErrorResources.ER_NUMBER_TOO_BIG); return XSLTErrorResources.ERROR_STRING; } char[] table = null; // index in table of the last character that we stored int lookupIndex = 1; // start off with anything other than zero to make correction work // Create a buffer to hold the result // TODO: size of the table can be detereined by computing // logs of the radix. For now, we fake it. char buf[] = new char[100]; //some languages go left to right(ie. english), right to left (ie. Hebrew), //top to bottom (ie.Japanese), etc... Handle them differently //String orientation = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_ORIENTATION); // next character to set in the buffer int charPos; charPos = 0; //start at 0 // array of number groups: ie.1000, 100, 10, 1 IntArrayWrapper groups = (IntArrayWrapper) thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERGROUPS); // array of tables of hundreds, tens, digits... StringArrayWrapper tables = (StringArrayWrapper) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_NUM_TABLES)); //some languages have additive alphabetical notation, //some multiplicative-additive, etc... Handle them differently. String numbering = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.LANG_NUMBERING); // do multiplicative part first if (numbering.equals(org.apache.xml.utils.res.XResourceBundle.LANG_MULT_ADD)) { String mult_order = thisBundle.getString(org.apache.xml.utils.res.XResourceBundle.MULT_ORDER); LongArrayWrapper multiplier = (LongArrayWrapper) (thisBundle.getObject(org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER)); CharArrayWrapper zeroChar = (CharArrayWrapper) thisBundle.getObject("zero"); int i = 0; // skip to correct multiplier while (i < multiplier.getLength() && val < multiplier.getLong(i)) { i++; } do { if (i >= multiplier.getLength()) break; //number is smaller than multipliers // some languages (ie chinese) put a zero character (and only one) when // the multiplier is multiplied by zero. (ie, 1001 is 1X1000 + 0X100 + 0X10 + 1) // 0X100 is replaced by the zero character, we don't need one for 0X10 if (val < multiplier.getLong(i)) { if (zeroChar.getLength() == 0) { i++; } else { if (buf[charPos - 1] != zeroChar.getChar(0)) buf[charPos++] = zeroChar.getChar(0); i++; } } else if (val >= multiplier.getLong(i)) { long mult = val / multiplier.getLong(i); val = val % multiplier.getLong(i); // save this. int k = 0; while (k < groups.getLength()) { lookupIndex = 1; // initialize for each table if (mult / groups.getInt(k) <= 0) // look for right table k++; else { // get the table CharArrayWrapper THEletters = (CharArrayWrapper) thisBundle.getObject(tables.getString(k)); table = new char[THEletters.getLength() + 1]; int j; for (j = 0; j < THEletters.getLength(); j++) { table[j + 1] = THEletters.getChar(j); } table[0] = THEletters.getChar(j - 1); // don't need this // index in "table" of the next char to emit lookupIndex = (int)mult / groups.getInt(k); //this should not happen if (lookupIndex == 0 && mult == 0) break; char multiplierChar = ((CharArrayWrapper) (thisBundle.getObject( org.apache.xml.utils.res.XResourceBundle.LANG_MULTIPLIER_CHAR))).getChar(i); // put out the next character of output if (lookupIndex < table.length) { if (mult_order.equals(org.apache.xml.utils.res.XResourceBundle.MULT_PRECEDES)) { buf[charPos++] = multiplierChar; buf[charPos++] = table[lookupIndex]; } else { // don't put out 1 (ie 1X10 is just 10) if (lookupIndex == 1 && i == multiplier.getLength() - 1){} else buf[charPos++] = table[lookupIndex]; buf[charPos++] = multiplierChar; } break; // all done! } else return XSLTErrorResources.ERROR_STRING; } //end else } // end while i++; } // end else if } // end do while while (i < multiplier.getLength()); } // Now do additive part... int count = 0; String tableName; // do this for each table of hundreds, tens, digits... while (count < groups.getLength()) { if (val / groups.getInt(count) <= 0) // look for correct table count++; else { CharArrayWrapper theletters = (CharArrayWrapper) thisBundle.getObject(tables.getString(count)); table = new char[theletters.getLength() + 1]; int j; // need to start filling the table up at index 1 for (j = 0; j < theletters.getLength(); j++) { table[j + 1] = theletters.getChar(j); } table[0] = theletters.getChar(j - 1); // don't need this // index in "table" of the next char to emit lookupIndex = (int)val / groups.getInt(count); // shift input by one "column" val = val % groups.getInt(count); // this should not happen if (lookupIndex == 0 && val == 0) break; if (lookupIndex < table.length) { // put out the next character of output buf[charPos++] = table[lookupIndex]; // left to right or top to bottom } else return XSLTErrorResources.ERROR_STRING; count++; } } // end while // String s = new String(buf, 0, charPos); return new String(buf, 0, charPos); } /** * Convert a long integer into roman numerals. * @param val Value to convert. * @param prefixesAreOK true_ to enable prefix notation (e.g. 4 = "IV"), * false_ to disable prefix notation (e.g. 4 = "IIII"). * @return Roman numeral string. * @see DecimalToRoman * @see m_romanConvertTable */ protected String long2roman(long val, boolean prefixesAreOK) { if (val <= 0) { return getZeroString(); } String roman = ""; int place = 0; if (val <= 3999L) { do { while (val >= m_romanConvertTable[place].m_postValue) { roman += m_romanConvertTable[place].m_postLetter; val -= m_romanConvertTable[place].m_postValue; } if (prefixesAreOK) { if (val >= m_romanConvertTable[place].m_preValue) { roman += m_romanConvertTable[place].m_preLetter; val -= m_romanConvertTable[place].m_preValue; } } place++; } while (val > 0); } else { roman = XSLTErrorResources.ERROR_STRING; } return roman; } // end long2roman /** * Call the children visitors. * @param visitor The visitor whose appropriate method will be called. */ public void callChildVisitors(XSLTVisitor visitor, boolean callAttrs) { if(callAttrs) { if(null != m_countMatchPattern) m_countMatchPattern.getExpression().callVisitors(m_countMatchPattern, visitor); if(null != m_fromMatchPattern) m_fromMatchPattern.getExpression().callVisitors(m_fromMatchPattern, visitor); if(null != m_valueExpr) m_valueExpr.getExpression().callVisitors(m_valueExpr, visitor); if(null != m_format_avt) m_format_avt.callVisitors(visitor); if(null != m_groupingSeparator_avt) m_groupingSeparator_avt.callVisitors(visitor); if(null != m_groupingSize_avt) m_groupingSize_avt.callVisitors(visitor); if(null != m_lang_avt) m_lang_avt.callVisitors(visitor); if(null != m_lettervalue_avt) m_lettervalue_avt.callVisitors(visitor); } super.callChildVisitors(visitor, callAttrs); } /** * This class returns tokens using non-alphanumberic * characters as delimiters. */ class NumberFormatStringTokenizer { /** Current position in the format string */ private int currentPosition; /** Index of last character in the format string */ private int maxPosition; /** Format string to be tokenized */ private String str; /** * Construct a NumberFormatStringTokenizer. * * @param str Format string to be tokenized */ public NumberFormatStringTokenizer(String str) { this.str = str; maxPosition = str.length(); } /** * Reset tokenizer so that nextToken() starts from the beginning. */ public void reset() { currentPosition = 0; } /** * Returns the next token from this string tokenizer. * * @return the next token from this string tokenizer. * @throws NoSuchElementException if there are no more tokens in this * tokenizer's string. */ public String nextToken() { if (currentPosition >= maxPosition) { throw new NoSuchElementException(); } int start = currentPosition; while ((currentPosition < maxPosition) && Character.isLetterOrDigit(str.charAt(currentPosition))) { currentPosition++; } if ((start == currentPosition) && (!Character.isLetterOrDigit(str.charAt(currentPosition)))) { currentPosition++; } return str.substring(start, currentPosition); } /** * Tells if there is a digit or a letter character ahead. * * @return true if there is a number or character ahead. */ public boolean isLetterOrDigitAhead() { int pos = currentPosition; while (pos < maxPosition) { if (Character.isLetterOrDigit(str.charAt(pos))) return true; pos++; } return false; } /** * Tells if there is a digit or a letter character ahead. * * @return true if there is a number or character ahead. */ public boolean nextIsSep() { if (Character.isLetterOrDigit(str.charAt(currentPosition))) return false; else return true; } /** * Tells if nextToken will throw an exception * if it is called. * * @return true if nextToken can be called * without throwing an exception. */ public boolean hasMoreTokens() { return (currentPosition >= maxPosition) ? false : true; } /** * Calculates the number of times that this tokenizer's * nextToken method can be called before it generates an * exception. * * @return the number of tokens remaining in the string using the current * delimiter set. * @see java.util.StringTokenizer#nextToken() */ public int countTokens() { int count = 0; int currpos = currentPosition; while (currpos < maxPosition) { int start = currpos; while ((currpos < maxPosition) && Character.isLetterOrDigit(str.charAt(currpos))) { currpos++; } if ((start == currpos) && (Character.isLetterOrDigit(str.charAt(currpos)) == false)) { currpos++; } count++; } return count; } } // end NumberFormatStringTokenizer }