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