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: XStringForFSB.java 468655 2006-10-28 07:12:06Z minchau $ 020 */ 021 package org.apache.xpath.objects; 022 023 import org.apache.xalan.res.XSLMessages; 024 import org.apache.xml.utils.FastStringBuffer; 025 import org.apache.xml.utils.XMLCharacterRecognizer; 026 import org.apache.xml.utils.XMLString; 027 import org.apache.xml.utils.XMLStringFactory; 028 import org.apache.xpath.res.XPATHErrorResources; 029 030 /** 031 * This class will wrap a FastStringBuffer and allow for 032 */ 033 public class XStringForFSB extends XString 034 { 035 static final long serialVersionUID = -1533039186550674548L; 036 037 /** The start position in the fsb. */ 038 int m_start; 039 040 /** The length of the string. */ 041 int m_length; 042 043 /** If the str() function is called, the string will be cached here. */ 044 protected String m_strCache = null; 045 046 /** cached hash code */ 047 protected int m_hash = 0; 048 049 /** 050 * Construct a XNodeSet object. 051 * 052 * @param val FastStringBuffer object this will wrap, must be non-null. 053 * @param start The start position in the array. 054 * @param length The number of characters to read from the array. 055 */ 056 public XStringForFSB(FastStringBuffer val, int start, int length) 057 { 058 059 super(val); 060 061 m_start = start; 062 m_length = length; 063 064 if (null == val) 065 throw new IllegalArgumentException( 066 XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FASTSTRINGBUFFER_CANNOT_BE_NULL, null)); 067 } 068 069 /** 070 * Construct a XNodeSet object. 071 * 072 * @param val String object this will wrap. 073 */ 074 private XStringForFSB(String val) 075 { 076 077 super(val); 078 079 throw new IllegalArgumentException( 080 XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FSB_CANNOT_TAKE_STRING, null)); // "XStringForFSB can not take a string for an argument!"); 081 } 082 083 /** 084 * Cast result object to a string. 085 * 086 * @return The string this wraps or the empty string if null 087 */ 088 public FastStringBuffer fsb() 089 { 090 return ((FastStringBuffer) m_obj); 091 } 092 093 /** 094 * Cast result object to a string. 095 * 096 * @return The string this wraps or the empty string if null 097 */ 098 public void appendToFsb(org.apache.xml.utils.FastStringBuffer fsb) 099 { 100 // %OPT% !!! FSB has to be updated to take partial fsb's for append. 101 fsb.append(str()); 102 } 103 104 /** 105 * Tell if this object contains a java String object. 106 * 107 * @return true if this XMLString can return a string without creating one. 108 */ 109 public boolean hasString() 110 { 111 return (null != m_strCache); 112 } 113 114 // /** NEEDSDOC Field strCount */ 115 // public static int strCount = 0; 116 // 117 // /** NEEDSDOC Field xtable */ 118 // static java.util.Hashtable xtable = new java.util.Hashtable(); 119 120 /** 121 * Since this object is incomplete without the length and the offset, we 122 * have to convert to a string when this function is called. 123 * 124 * @return The java String representation of this object. 125 */ 126 public Object object() 127 { 128 return str(); 129 } 130 131 /** 132 * Cast result object to a string. 133 * 134 * @return The string this wraps or the empty string if null 135 */ 136 public String str() 137 { 138 139 if (null == m_strCache) 140 { 141 m_strCache = fsb().getString(m_start, m_length); 142 143 // strCount++; 144 // 145 // RuntimeException e = new RuntimeException("Bad! Bad!"); 146 // java.io.CharArrayWriter writer = new java.io.CharArrayWriter(); 147 // java.io.PrintWriter pw = new java.io.PrintWriter(writer); 148 // 149 // e.printStackTrace(pw); 150 // 151 // String str = writer.toString(); 152 // 153 // str = str.substring(0, 600); 154 // 155 // if (null == xtable.get(str)) 156 // { 157 // xtable.put(str, str); 158 // System.out.println(str); 159 // } 160 // System.out.println("strCount: " + strCount); 161 162 // throw e; 163 // e.printStackTrace(); 164 // System.exit(-1); 165 } 166 167 return m_strCache; 168 } 169 170 /** 171 * Directly call the 172 * characters method on the passed ContentHandler for the 173 * string-value. Multiple calls to the 174 * ContentHandler's characters methods may well occur for a single call to 175 * this method. 176 * 177 * @param ch A non-null reference to a ContentHandler. 178 * 179 * @throws org.xml.sax.SAXException 180 */ 181 public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch) 182 throws org.xml.sax.SAXException 183 { 184 fsb().sendSAXcharacters(ch, m_start, m_length); 185 } 186 187 /** 188 * Directly call the 189 * comment method on the passed LexicalHandler for the 190 * string-value. 191 * 192 * @param lh A non-null reference to a LexicalHandler. 193 * 194 * @throws org.xml.sax.SAXException 195 */ 196 public void dispatchAsComment(org.xml.sax.ext.LexicalHandler lh) 197 throws org.xml.sax.SAXException 198 { 199 fsb().sendSAXComment(lh, m_start, m_length); 200 } 201 202 /** 203 * Returns the length of this string. 204 * 205 * @return the length of the sequence of characters represented by this 206 * object. 207 */ 208 public int length() 209 { 210 return m_length; 211 } 212 213 /** 214 * Returns the character at the specified index. An index ranges 215 * from <code>0</code> to <code>length() - 1</code>. The first character 216 * of the sequence is at index <code>0</code>, the next at index 217 * <code>1</code>, and so on, as for array indexing. 218 * 219 * @param index the index of the character. 220 * @return the character at the specified index of this string. 221 * The first character is at index <code>0</code>. 222 * @exception IndexOutOfBoundsException if the <code>index</code> 223 * argument is negative or not less than the length of this 224 * string. 225 */ 226 public char charAt(int index) 227 { 228 return fsb().charAt(m_start + index); 229 } 230 231 /** 232 * Copies characters from this string into the destination character 233 * array. 234 * 235 * @param srcBegin index of the first character in the string 236 * to copy. 237 * @param srcEnd index after the last character in the string 238 * to copy. 239 * @param dst the destination array. 240 * @param dstBegin the start offset in the destination array. 241 * @exception IndexOutOfBoundsException If any of the following 242 * is true: 243 * <ul><li><code>srcBegin</code> is negative. 244 * <li><code>srcBegin</code> is greater than <code>srcEnd</code> 245 * <li><code>srcEnd</code> is greater than the length of this 246 * string 247 * <li><code>dstBegin</code> is negative 248 * <li><code>dstBegin+(srcEnd-srcBegin)</code> is larger than 249 * <code>dst.length</code></ul> 250 * @exception NullPointerException if <code>dst</code> is <code>null</code> 251 */ 252 public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) 253 { 254 255 // %OPT% Need to call this on FSB when it is implemented. 256 // %UNTESTED% (I don't think anyone calls this yet?) 257 int n = srcEnd - srcBegin; 258 259 if (n > m_length) 260 n = m_length; 261 262 if (n > (dst.length - dstBegin)) 263 n = (dst.length - dstBegin); 264 265 int end = srcBegin + m_start + n; 266 int d = dstBegin; 267 FastStringBuffer fsb = fsb(); 268 269 for (int i = srcBegin + m_start; i < end; i++) 270 { 271 dst[d++] = fsb.charAt(i); 272 } 273 } 274 275 /** 276 * Compares this string to the specified object. 277 * The result is <code>true</code> if and only if the argument is not 278 * <code>null</code> and is a <code>String</code> object that represents 279 * the same sequence of characters as this object. 280 * 281 * @param obj2 the object to compare this <code>String</code> 282 * against. 283 * 284 * @return <code>true</code> if the <code>String </code>are equal; 285 * <code>false</code> otherwise. 286 * @see java.lang.String#compareTo(java.lang.String) 287 * @see java.lang.String#equalsIgnoreCase(java.lang.String) 288 */ 289 public boolean equals(XMLString obj2) 290 { 291 292 if (this == obj2) 293 { 294 return true; 295 } 296 297 int n = m_length; 298 299 if (n == obj2.length()) 300 { 301 FastStringBuffer fsb = fsb(); 302 int i = m_start; 303 int j = 0; 304 305 while (n-- != 0) 306 { 307 if (fsb.charAt(i) != obj2.charAt(j)) 308 { 309 return false; 310 } 311 312 i++; 313 j++; 314 } 315 316 return true; 317 } 318 319 return false; 320 } 321 322 /** 323 * Tell if two objects are functionally equal. 324 * 325 * @param obj2 Object to compare this to 326 * 327 * @return true if the two objects are equal 328 * 329 * @throws javax.xml.transform.TransformerException 330 */ 331 public boolean equals(XObject obj2) 332 { 333 334 if (this == obj2) 335 { 336 return true; 337 } 338 if(obj2.getType() == XObject.CLASS_NUMBER) 339 return obj2.equals(this); 340 341 String str = obj2.str(); 342 int n = m_length; 343 344 if (n == str.length()) 345 { 346 FastStringBuffer fsb = fsb(); 347 int i = m_start; 348 int j = 0; 349 350 while (n-- != 0) 351 { 352 if (fsb.charAt(i) != str.charAt(j)) 353 { 354 return false; 355 } 356 357 i++; 358 j++; 359 } 360 361 return true; 362 } 363 364 return false; 365 } 366 367 /** 368 * Tell if two objects are functionally equal. 369 * 370 * @param anotherString Object to compare this to 371 * 372 * @return true if the two objects are equal 373 * 374 * @throws javax.xml.transform.TransformerException 375 */ 376 public boolean equals(String anotherString) 377 { 378 379 int n = m_length; 380 381 if (n == anotherString.length()) 382 { 383 FastStringBuffer fsb = fsb(); 384 int i = m_start; 385 int j = 0; 386 387 while (n-- != 0) 388 { 389 if (fsb.charAt(i) != anotherString.charAt(j)) 390 { 391 return false; 392 } 393 394 i++; 395 j++; 396 } 397 398 return true; 399 } 400 401 return false; 402 } 403 404 /** 405 * Compares this string to the specified object. 406 * The result is <code>true</code> if and only if the argument is not 407 * <code>null</code> and is a <code>String</code> object that represents 408 * the same sequence of characters as this object. 409 * 410 * @param obj2 the object to compare this <code>String</code> 411 * against. 412 * 413 * @return <code>true</code> if the <code>String </code>are equal; 414 * <code>false</code> otherwise. 415 * @see java.lang.String#compareTo(java.lang.String) 416 * @see java.lang.String#equalsIgnoreCase(java.lang.String) 417 */ 418 public boolean equals(Object obj2) 419 { 420 421 if (null == obj2) 422 return false; 423 424 if(obj2 instanceof XNumber) 425 return obj2.equals(this); 426 427 // In order to handle the 'all' semantics of 428 // nodeset comparisons, we always call the 429 // nodeset function. 430 else if (obj2 instanceof XNodeSet) 431 return obj2.equals(this); 432 else if (obj2 instanceof XStringForFSB) 433 return equals((XMLString) obj2); 434 else 435 return equals(obj2.toString()); 436 } 437 438 /** 439 * Compares this <code>String</code> to another <code>String</code>, 440 * ignoring case considerations. Two strings are considered equal 441 * ignoring case if they are of the same length, and corresponding 442 * characters in the two strings are equal ignoring case. 443 * 444 * @param anotherString the <code>String</code> to compare this 445 * <code>String</code> against. 446 * @return <code>true</code> if the argument is not <code>null</code> 447 * and the <code>String</code>s are equal, 448 * ignoring case; <code>false</code> otherwise. 449 * @see #equals(Object) 450 * @see java.lang.Character#toLowerCase(char) 451 * @see java.lang.Character#toUpperCase(char) 452 */ 453 public boolean equalsIgnoreCase(String anotherString) 454 { 455 return (m_length == anotherString.length()) 456 ? str().equalsIgnoreCase(anotherString) : false; 457 } 458 459 /** 460 * Compares two strings lexicographically. 461 * 462 * @param xstr the <code>String</code> to be compared. 463 * 464 * @return the value <code>0</code> if the argument string is equal to 465 * this string; a value less than <code>0</code> if this string 466 * is lexicographically less than the string argument; and a 467 * value greater than <code>0</code> if this string is 468 * lexicographically greater than the string argument. 469 * @exception java.lang.NullPointerException if <code>anotherString</code> 470 * is <code>null</code>. 471 */ 472 public int compareTo(XMLString xstr) 473 { 474 475 int len1 = m_length; 476 int len2 = xstr.length(); 477 int n = Math.min(len1, len2); 478 FastStringBuffer fsb = fsb(); 479 int i = m_start; 480 int j = 0; 481 482 while (n-- != 0) 483 { 484 char c1 = fsb.charAt(i); 485 char c2 = xstr.charAt(j); 486 487 if (c1 != c2) 488 { 489 return c1 - c2; 490 } 491 492 i++; 493 j++; 494 } 495 496 return len1 - len2; 497 } 498 499 /** 500 * Compares two strings lexicographically, ignoring case considerations. 501 * This method returns an integer whose sign is that of 502 * <code>this.toUpperCase().toLowerCase().compareTo( 503 * str.toUpperCase().toLowerCase())</code>. 504 * <p> 505 * Note that this method does <em>not</em> take locale into account, 506 * and will result in an unsatisfactory ordering for certain locales. 507 * The java.text package provides <em>collators</em> to allow 508 * locale-sensitive ordering. 509 * 510 * @param xstr the <code>String</code> to be compared. 511 * 512 * @return a negative integer, zero, or a positive integer as the 513 * the specified String is greater than, equal to, or less 514 * than this String, ignoring case considerations. 515 * @see java.text.Collator#compare(String, String) 516 * @since 1.2 517 */ 518 public int compareToIgnoreCase(XMLString xstr) 519 { 520 521 int len1 = m_length; 522 int len2 = xstr.length(); 523 int n = Math.min(len1, len2); 524 FastStringBuffer fsb = fsb(); 525 int i = m_start; 526 int j = 0; 527 528 while (n-- != 0) 529 { 530 char c1 = Character.toLowerCase(fsb.charAt(i)); 531 char c2 = Character.toLowerCase(xstr.charAt(j)); 532 533 if (c1 != c2) 534 { 535 return c1 - c2; 536 } 537 538 i++; 539 j++; 540 } 541 542 return len1 - len2; 543 } 544 545 /** 546 * Returns a hashcode for this string. The hashcode for a 547 * <code>String</code> object is computed as 548 * <blockquote><pre> 549 * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 550 * </pre></blockquote> 551 * using <code>int</code> arithmetic, where <code>s[i]</code> is the 552 * <i>i</i>th character of the string, <code>n</code> is the length of 553 * the string, and <code>^</code> indicates exponentiation. 554 * (The hash value of the empty string is zero.) 555 * 556 * @return a hash code value for this object. 557 */ 558 public int hashCode() 559 { 560 // Commenting this out because in JDK1.1.8 and VJ++ 561 // we don't match XMLStrings. Defaulting to the super 562 // causes us to create a string, but at this point 563 // this only seems to get called in key processing. 564 // Maybe we can live with it? 565 566 /* 567 int h = m_hash; 568 569 if (h == 0) 570 { 571 int off = m_start; 572 int len = m_length; 573 FastStringBuffer fsb = fsb(); 574 575 for (int i = 0; i < len; i++) 576 { 577 h = 31 * h + fsb.charAt(off); 578 579 off++; 580 } 581 582 m_hash = h; 583 } 584 */ 585 586 return super.hashCode(); // h; 587 } 588 589 /** 590 * Tests if this string starts with the specified prefix beginning 591 * a specified index. 592 * 593 * @param prefix the prefix. 594 * @param toffset where to begin looking in the string. 595 * @return <code>true</code> if the character sequence represented by the 596 * argument is a prefix of the substring of this object starting 597 * at index <code>toffset</code>; <code>false</code> otherwise. 598 * The result is <code>false</code> if <code>toffset</code> is 599 * negative or greater than the length of this 600 * <code>String</code> object; otherwise the result is the same 601 * as the result of the expression 602 * <pre> 603 * this.subString(toffset).startsWith(prefix) 604 * </pre> 605 * @exception java.lang.NullPointerException if <code>prefix</code> is 606 * <code>null</code>. 607 */ 608 public boolean startsWith(XMLString prefix, int toffset) 609 { 610 611 FastStringBuffer fsb = fsb(); 612 int to = m_start + toffset; 613 int tlim = m_start + m_length; 614 int po = 0; 615 int pc = prefix.length(); 616 617 // Note: toffset might be near -1>>>1. 618 if ((toffset < 0) || (toffset > m_length - pc)) 619 { 620 return false; 621 } 622 623 while (--pc >= 0) 624 { 625 if (fsb.charAt(to) != prefix.charAt(po)) 626 { 627 return false; 628 } 629 630 to++; 631 po++; 632 } 633 634 return true; 635 } 636 637 /** 638 * Tests if this string starts with the specified prefix. 639 * 640 * @param prefix the prefix. 641 * @return <code>true</code> if the character sequence represented by the 642 * argument is a prefix of the character sequence represented by 643 * this string; <code>false</code> otherwise. 644 * Note also that <code>true</code> will be returned if the 645 * argument is an empty string or is equal to this 646 * <code>String</code> object as determined by the 647 * {@link #equals(Object)} method. 648 * @exception java.lang.NullPointerException if <code>prefix</code> is 649 * <code>null</code>. 650 * @since JDK1. 0 651 */ 652 public boolean startsWith(XMLString prefix) 653 { 654 return startsWith(prefix, 0); 655 } 656 657 /** 658 * Returns the index within this string of the first occurrence of the 659 * specified character. If a character with value <code>ch</code> occurs 660 * in the character sequence represented by this <code>String</code> 661 * object, then the index of the first such occurrence is returned -- 662 * that is, the smallest value <i>k</i> such that: 663 * <blockquote><pre> 664 * this.charAt(<i>k</i>) == ch 665 * </pre></blockquote> 666 * is <code>true</code>. If no such character occurs in this string, 667 * then <code>-1</code> is returned. 668 * 669 * @param ch a character. 670 * @return the index of the first occurrence of the character in the 671 * character sequence represented by this object, or 672 * <code>-1</code> if the character does not occur. 673 */ 674 public int indexOf(int ch) 675 { 676 return indexOf(ch, 0); 677 } 678 679 /** 680 * Returns the index within this string of the first occurrence of the 681 * specified character, starting the search at the specified index. 682 * <p> 683 * If a character with value <code>ch</code> occurs in the character 684 * sequence represented by this <code>String</code> object at an index 685 * no smaller than <code>fromIndex</code>, then the index of the first 686 * such occurrence is returned--that is, the smallest value <i>k</i> 687 * such that: 688 * <blockquote><pre> 689 * (this.charAt(<i>k</i>) == ch) && (<i>k</i> >= fromIndex) 690 * </pre></blockquote> 691 * is true. If no such character occurs in this string at or after 692 * position <code>fromIndex</code>, then <code>-1</code> is returned. 693 * <p> 694 * There is no restriction on the value of <code>fromIndex</code>. If it 695 * is negative, it has the same effect as if it were zero: this entire 696 * string may be searched. If it is greater than the length of this 697 * string, it has the same effect as if it were equal to the length of 698 * this string: <code>-1</code> is returned. 699 * 700 * @param ch a character. 701 * @param fromIndex the index to start the search from. 702 * @return the index of the first occurrence of the character in the 703 * character sequence represented by this object that is greater 704 * than or equal to <code>fromIndex</code>, or <code>-1</code> 705 * if the character does not occur. 706 */ 707 public int indexOf(int ch, int fromIndex) 708 { 709 710 int max = m_start + m_length; 711 FastStringBuffer fsb = fsb(); 712 713 if (fromIndex < 0) 714 { 715 fromIndex = 0; 716 } 717 else if (fromIndex >= m_length) 718 { 719 720 // Note: fromIndex might be near -1>>>1. 721 return -1; 722 } 723 724 for (int i = m_start + fromIndex; i < max; i++) 725 { 726 if (fsb.charAt(i) == ch) 727 { 728 return i - m_start; 729 } 730 } 731 732 return -1; 733 } 734 735 /** 736 * Returns a new string that is a substring of this string. The 737 * substring begins with the character at the specified index and 738 * extends to the end of this string. <p> 739 * Examples: 740 * <blockquote><pre> 741 * "unhappy".substring(2) returns "happy" 742 * "Harbison".substring(3) returns "bison" 743 * "emptiness".substring(9) returns "" (an empty string) 744 * </pre></blockquote> 745 * 746 * @param beginIndex the beginning index, inclusive. 747 * @return the specified substring. 748 * @exception IndexOutOfBoundsException if 749 * <code>beginIndex</code> is negative or larger than the 750 * length of this <code>String</code> object. 751 */ 752 public XMLString substring(int beginIndex) 753 { 754 755 int len = m_length - beginIndex; 756 757 if (len <= 0) 758 return XString.EMPTYSTRING; 759 else 760 { 761 int start = m_start + beginIndex; 762 763 return new XStringForFSB(fsb(), start, len); 764 } 765 } 766 767 /** 768 * Returns a new string that is a substring of this string. The 769 * substring begins at the specified <code>beginIndex</code> and 770 * extends to the character at index <code>endIndex - 1</code>. 771 * Thus the length of the substring is <code>endIndex-beginIndex</code>. 772 * 773 * @param beginIndex the beginning index, inclusive. 774 * @param endIndex the ending index, exclusive. 775 * @return the specified substring. 776 * @exception IndexOutOfBoundsException if the 777 * <code>beginIndex</code> is negative, or 778 * <code>endIndex</code> is larger than the length of 779 * this <code>String</code> object, or 780 * <code>beginIndex</code> is larger than 781 * <code>endIndex</code>. 782 */ 783 public XMLString substring(int beginIndex, int endIndex) 784 { 785 786 int len = endIndex - beginIndex; 787 788 if (len > m_length) 789 len = m_length; 790 791 if (len <= 0) 792 return XString.EMPTYSTRING; 793 else 794 { 795 int start = m_start + beginIndex; 796 797 return new XStringForFSB(fsb(), start, len); 798 } 799 } 800 801 /** 802 * Concatenates the specified string to the end of this string. 803 * 804 * @param str the <code>String</code> that is concatenated to the end 805 * of this <code>String</code>. 806 * @return a string that represents the concatenation of this object's 807 * characters followed by the string argument's characters. 808 * @exception java.lang.NullPointerException if <code>str</code> is 809 * <code>null</code>. 810 */ 811 public XMLString concat(String str) 812 { 813 814 // %OPT% Make an FSB here? 815 return new XString(str().concat(str)); 816 } 817 818 /** 819 * Removes white space from both ends of this string. 820 * 821 * @return this string, with white space removed from the front and end. 822 */ 823 public XMLString trim() 824 { 825 return fixWhiteSpace(true, true, false); 826 } 827 828 /** 829 * Returns whether the specified <var>ch</var> conforms to the XML 1.0 definition 830 * of whitespace. Refer to <A href="http://www.w3.org/TR/1998/REC-xml-19980210#NT-S"> 831 * the definition of <CODE>S</CODE></A> for details. 832 * @param ch Character to check as XML whitespace. 833 * @return =true if <var>ch</var> is XML whitespace; otherwise =false. 834 */ 835 private static boolean isSpace(char ch) 836 { 837 return XMLCharacterRecognizer.isWhiteSpace(ch); // Take the easy way out for now. 838 } 839 840 /** 841 * Conditionally trim all leading and trailing whitespace in the specified String. 842 * All strings of white space are 843 * replaced by a single space character (#x20), except spaces after punctuation which 844 * receive double spaces if doublePunctuationSpaces is true. 845 * This function may be useful to a formatter, but to get first class 846 * results, the formatter should probably do it's own white space handling 847 * based on the semantics of the formatting object. 848 * 849 * @param trimHead Trim leading whitespace? 850 * @param trimTail Trim trailing whitespace? 851 * @param doublePunctuationSpaces Use double spaces for punctuation? 852 * @return The trimmed string. 853 */ 854 public XMLString fixWhiteSpace(boolean trimHead, boolean trimTail, 855 boolean doublePunctuationSpaces) 856 { 857 858 int end = m_length + m_start; 859 char[] buf = new char[m_length]; 860 FastStringBuffer fsb = fsb(); 861 boolean edit = false; 862 863 /* replace S to ' '. and ' '+ -> single ' '. */ 864 int d = 0; 865 boolean pres = false; 866 867 for (int s = m_start; s < end; s++) 868 { 869 char c = fsb.charAt(s); 870 871 if (isSpace(c)) 872 { 873 if (!pres) 874 { 875 if (' ' != c) 876 { 877 edit = true; 878 } 879 880 buf[d++] = ' '; 881 882 if (doublePunctuationSpaces && (d != 0)) 883 { 884 char prevChar = buf[d - 1]; 885 886 if (!((prevChar == '.') || (prevChar == '!') 887 || (prevChar == '?'))) 888 { 889 pres = true; 890 } 891 } 892 else 893 { 894 pres = true; 895 } 896 } 897 else 898 { 899 edit = true; 900 pres = true; 901 } 902 } 903 else 904 { 905 buf[d++] = c; 906 pres = false; 907 } 908 } 909 910 if (trimTail && 1 <= d && ' ' == buf[d - 1]) 911 { 912 edit = true; 913 914 d--; 915 } 916 917 int start = 0; 918 919 if (trimHead && 0 < d && ' ' == buf[0]) 920 { 921 edit = true; 922 923 start++; 924 } 925 926 XMLStringFactory xsf = XMLStringFactoryImpl.getFactory(); 927 928 return edit ? xsf.newstr(buf, start, d - start) : this; 929 } 930 931 /** 932 * Convert a string to a double -- Allowed input is in fixed 933 * notation ddd.fff. 934 * 935 * %OPT% CHECK PERFORMANCE against generating a Java String and 936 * converting it to double. The advantage of running in native 937 * machine code -- perhaps even microcode, on some systems -- may 938 * more than make up for the cost of allocating and discarding the 939 * additional object. We need to benchmark this. 940 * 941 * %OPT% More importantly, we need to decide whether we _care_ about 942 * the performance of this operation. Does XString.toDouble constitute 943 * any measurable percentage of our typical runtime? I suspect not! 944 * 945 * @return A double value representation of the string, or return Double.NaN 946 * if the string can not be converted. */ 947 public double toDouble() 948 { 949 if(m_length == 0) 950 return Double.NaN; 951 int i; 952 char c; 953 String valueString = fsb().getString(m_start,m_length); 954 955 // The following are permitted in the Double.valueOf, but not by the XPath spec: 956 // - a plus sign 957 // - The use of e or E to indicate exponents 958 // - trailing f, F, d, or D 959 // See function comments; not sure if this is slower than actually doing the 960 // conversion ourselves (as was before). 961 962 for (i=0;i<m_length;i++) 963 if (!XMLCharacterRecognizer.isWhiteSpace(valueString.charAt(i))) 964 break; 965 if (i == m_length) return Double.NaN; 966 if (valueString.charAt(i) == '-') 967 i++; 968 for (;i<m_length;i++) { 969 c = valueString.charAt(i); 970 if (c != '.' && (c < '0' || c > '9')) 971 break; 972 } 973 for (;i<m_length;i++) 974 if (!XMLCharacterRecognizer.isWhiteSpace(valueString.charAt(i))) 975 break; 976 if (i != m_length) 977 return Double.NaN; 978 979 try { 980 return new Double(valueString).doubleValue(); 981 } catch (NumberFormatException nfe) { 982 // This should catch double periods, empty strings. 983 return Double.NaN; 984 } 985 } 986 }