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