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 }