1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xpath.objects;
  58. //import org.w3c.dom.*;
  59. import java.util.Locale;
  60. import org.apache.xml.dtm.DTM;
  61. import org.apache.xml.utils.XMLCharacterRecognizer;
  62. import org.apache.xml.utils.XMLString;
  63. import org.apache.xml.utils.XMLStringFactory;
  64. import org.apache.xpath.ExpressionOwner;
  65. import org.apache.xpath.XPathContext;
  66. import org.apache.xpath.XPathVisitor;
  67. import org.xml.sax.ContentHandler;
  68. import org.xml.sax.SAXException;
  69. import org.xml.sax.ext.LexicalHandler;
  70. /**
  71. * <meta name="usage" content="general"/>
  72. * This class represents an XPath string object, and is capable of
  73. * converting the string to other types, such as a number.
  74. */
  75. public class XString extends XObject implements XMLString
  76. {
  77. /** Empty string XString object */
  78. public static XString EMPTYSTRING = new XString("");
  79. /**
  80. * Construct a XString object. This constructor exists for derived classes.
  81. *
  82. * @param val String object this will wrap.
  83. */
  84. protected XString(Object val)
  85. {
  86. super(val);
  87. }
  88. /**
  89. * Construct a XNodeSet object.
  90. *
  91. * @param val String object this will wrap.
  92. */
  93. public XString(String val)
  94. {
  95. super(val);
  96. }
  97. /**
  98. * Tell that this is a CLASS_STRING.
  99. *
  100. * @return type CLASS_STRING
  101. */
  102. public int getType()
  103. {
  104. return CLASS_STRING;
  105. }
  106. /**
  107. * Given a request type, return the equivalent string.
  108. * For diagnostic purposes.
  109. *
  110. * @return type string "#STRING"
  111. */
  112. public String getTypeString()
  113. {
  114. return "#STRING";
  115. }
  116. /**
  117. * Tell if this object contains a java String object.
  118. *
  119. * @return true if this XMLString can return a string without creating one.
  120. */
  121. public boolean hasString()
  122. {
  123. return true;
  124. }
  125. /**
  126. * Cast result object to a number.
  127. *
  128. * @return 0.0 if this string is null, numeric value of this string
  129. * or NaN
  130. */
  131. public double num()
  132. {
  133. return toDouble();
  134. }
  135. /**
  136. * Convert a string to a double -- Allowed input is in fixed
  137. * notation ddd.fff.
  138. *
  139. * @return A double value representation of the string, or return Double.NaN
  140. * if the string can not be converted.
  141. */
  142. public double toDouble()
  143. {
  144. int end = length();
  145. if(0 == end)
  146. return Double.NaN;
  147. double result = 0.0;
  148. int start = 0;
  149. int punctPos = end-1;
  150. // Scan to first whitespace character.
  151. for (int i = start; i < end; i++)
  152. {
  153. char c = charAt(i);
  154. if (!XMLCharacterRecognizer.isWhiteSpace(c))
  155. {
  156. break;
  157. }
  158. else
  159. start++;
  160. }
  161. double sign = 1.0;
  162. if (start < end && charAt(start) == '-')
  163. {
  164. sign = -1.0;
  165. start++;
  166. }
  167. int digitsFound = 0;
  168. for (int i = start; i < end; i++) // parse the string from left to right converting the integer part
  169. {
  170. char c = charAt(i);
  171. if (c != '.')
  172. {
  173. if (XMLCharacterRecognizer.isWhiteSpace(c))
  174. break;
  175. else if (Character.isDigit(c))
  176. {
  177. result = result * 10.0 + (c - 0x30);
  178. digitsFound++;
  179. }
  180. else
  181. {
  182. return Double.NaN;
  183. }
  184. }
  185. else
  186. {
  187. punctPos = i;
  188. break;
  189. }
  190. }
  191. if (charAt(punctPos) == '.') // parse the string from the end to the '.' converting the fractional part
  192. {
  193. double fractPart = 0.0;
  194. for (int i = end - 1; i > punctPos; i--)
  195. {
  196. char c = charAt(i);
  197. if (XMLCharacterRecognizer.isWhiteSpace(c))
  198. break;
  199. else if (Character.isDigit(c))
  200. {
  201. fractPart = fractPart / 10.0 + (c - 0x30);
  202. digitsFound++;
  203. }
  204. else
  205. {
  206. return Double.NaN;
  207. }
  208. }
  209. result += fractPart / 10.0;
  210. }
  211. if (0 == digitsFound)
  212. return Double.NaN;
  213. return result * sign;
  214. }
  215. /**
  216. * Cast result object to a boolean.
  217. *
  218. * @return True if the length of this string object is greater
  219. * than 0.
  220. */
  221. public boolean bool()
  222. {
  223. return str().length() > 0;
  224. }
  225. /**
  226. * Cast result object to a string.
  227. *
  228. * @return The string this wraps or the empty string if null
  229. */
  230. public XMLString xstr()
  231. {
  232. return this;
  233. }
  234. /**
  235. * Cast result object to a string.
  236. *
  237. * @return The string this wraps or the empty string if null
  238. */
  239. public String str()
  240. {
  241. return (null != m_obj) ? ((String) m_obj) : "";
  242. }
  243. /**
  244. * Cast result object to a result tree fragment.
  245. *
  246. * @param support Xpath context to use for the conversion
  247. *
  248. * @return A document fragment with this string as a child node
  249. */
  250. public int rtf(XPathContext support)
  251. {
  252. DTM frag = support.createDocumentFragment();
  253. frag.appendTextChild(str());
  254. return frag.getDocument();
  255. }
  256. /**
  257. * Directly call the
  258. * characters method on the passed ContentHandler for the
  259. * string-value. Multiple calls to the
  260. * ContentHandler's characters methods may well occur for a single call to
  261. * this method.
  262. *
  263. * @param ch A non-null reference to a ContentHandler.
  264. *
  265. * @throws org.xml.sax.SAXException
  266. */
  267. public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch)
  268. throws org.xml.sax.SAXException
  269. {
  270. String str = str();
  271. ch.characters(str.toCharArray(), 0, str.length());
  272. }
  273. /**
  274. * Directly call the
  275. * comment method on the passed LexicalHandler for the
  276. * string-value.
  277. *
  278. * @param lh A non-null reference to a LexicalHandler.
  279. *
  280. * @throws org.xml.sax.SAXException
  281. */
  282. public void dispatchAsComment(org.xml.sax.ext.LexicalHandler lh)
  283. throws org.xml.sax.SAXException
  284. {
  285. String str = str();
  286. lh.comment(str.toCharArray(), 0, str.length());
  287. }
  288. /**
  289. * Returns the length of this string.
  290. *
  291. * @return the length of the sequence of characters represented by this
  292. * object.
  293. */
  294. public int length()
  295. {
  296. return str().length();
  297. }
  298. /**
  299. * Returns the character at the specified index. An index ranges
  300. * from <code>0</code> to <code>length() - 1</code>. The first character
  301. * of the sequence is at index <code>0</code>, the next at index
  302. * <code>1</code>, and so on, as for array indexing.
  303. *
  304. * @param index the index of the character.
  305. * @return the character at the specified index of this string.
  306. * The first character is at index <code>0</code>.
  307. * @exception IndexOutOfBoundsException if the <code>index</code>
  308. * argument is negative or not less than the length of this
  309. * string.
  310. */
  311. public char charAt(int index)
  312. {
  313. return str().charAt(index);
  314. }
  315. /**
  316. * Copies characters from this string into the destination character
  317. * array.
  318. *
  319. * @param srcBegin index of the first character in the string
  320. * to copy.
  321. * @param srcEnd index after the last character in the string
  322. * to copy.
  323. * @param dst the destination array.
  324. * @param dstBegin the start offset in the destination array.
  325. * @exception IndexOutOfBoundsException If any of the following
  326. * is true:
  327. * <ul><li><code>srcBegin</code> is negative.
  328. * <li><code>srcBegin</code> is greater than <code>srcEnd</code>
  329. * <li><code>srcEnd</code> is greater than the length of this
  330. * string
  331. * <li><code>dstBegin</code> is negative
  332. * <li><code>dstBegin+(srcEnd-srcBegin)</code> is larger than
  333. * <code>dst.length</code></ul>
  334. * @exception NullPointerException if <code>dst</code> is <code>null</code>
  335. */
  336. public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
  337. {
  338. str().getChars(srcBegin, srcEnd, dst, dstBegin);
  339. }
  340. /**
  341. * Tell if two objects are functionally equal.
  342. *
  343. * @param obj2 Object to compare this to
  344. *
  345. * @return true if the two objects are equal
  346. *
  347. * @throws javax.xml.transform.TransformerException
  348. */
  349. public boolean equals(XObject obj2)
  350. {
  351. // In order to handle the 'all' semantics of
  352. // nodeset comparisons, we always call the
  353. // nodeset function.
  354. int t = obj2.getType();
  355. try
  356. {
  357. if (XObject.CLASS_NODESET == t)
  358. return obj2.equals(this);
  359. // If at least one object to be compared is a boolean, then each object
  360. // to be compared is converted to a boolean as if by applying the
  361. // boolean function.
  362. else if(XObject.CLASS_BOOLEAN == t)
  363. return obj2.bool() == bool();
  364. // Otherwise, if at least one object to be compared is a number, then each object
  365. // to be compared is converted to a number as if by applying the number function.
  366. else if(XObject.CLASS_NUMBER == t)
  367. return obj2.num() == num();
  368. }
  369. catch(javax.xml.transform.TransformerException te)
  370. {
  371. throw new org.apache.xml.utils.WrappedRuntimeException(te);
  372. }
  373. // Otherwise, both objects to be compared are converted to strings as
  374. // if by applying the string function.
  375. return xstr().equals(obj2.xstr());
  376. }
  377. /**
  378. * Compares this string to the specified object.
  379. * The result is <code>true</code> if and only if the argument is not
  380. * <code>null</code> and is a <code>String</code> object that represents
  381. * the same sequence of characters as this object.
  382. *
  383. * @param obj2 the object to compare this <code>String</code>
  384. * against.
  385. * @return <code>true</code> if the <code>String </code>are equal;
  386. * <code>false</code> otherwise.
  387. * @see java.lang.String#compareTo(java.lang.String)
  388. * @see java.lang.String#equalsIgnoreCase(java.lang.String)
  389. */
  390. public boolean equals(XMLString obj2)
  391. {
  392. if (!obj2.hasString())
  393. return obj2.equals(this);
  394. else
  395. return str().equals(obj2.toString());
  396. }
  397. /**
  398. * Compares this string to the specified object.
  399. * The result is <code>true</code> if and only if the argument is not
  400. * <code>null</code> and is a <code>String</code> object that represents
  401. * the same sequence of characters as this object.
  402. *
  403. * @param anObject the object to compare this <code>String</code>
  404. * against.
  405. *
  406. * NEEDSDOC @param obj2
  407. * @return <code>true</code> if the <code>String </code>are equal;
  408. * <code>false</code> otherwise.
  409. * @see java.lang.String#compareTo(java.lang.String)
  410. * @see java.lang.String#equalsIgnoreCase(java.lang.String)
  411. */
  412. public boolean equals(Object obj2)
  413. {
  414. if (null == obj2)
  415. return false;
  416. // In order to handle the 'all' semantics of
  417. // nodeset comparisons, we always call the
  418. // nodeset function.
  419. else if (obj2 instanceof XNodeSet)
  420. return obj2.equals(this);
  421. else if(obj2 instanceof XNumber)
  422. return obj2.equals(this);
  423. else
  424. return str().equals(obj2.toString());
  425. }
  426. /**
  427. * Compares this <code>String</code> to another <code>String</code>,
  428. * ignoring case considerations. Two strings are considered equal
  429. * ignoring case if they are of the same length, and corresponding
  430. * characters in the two strings are equal ignoring case.
  431. *
  432. * @param anotherString the <code>String</code> to compare this
  433. * <code>String</code> against.
  434. * @return <code>true</code> if the argument is not <code>null</code>
  435. * and the <code>String</code>s are equal,
  436. * ignoring case; <code>false</code> otherwise.
  437. * @see #equals(Object)
  438. * @see java.lang.Character#toLowerCase(char)
  439. * @see java.lang.Character#toUpperCase(char)
  440. */
  441. public boolean equalsIgnoreCase(String anotherString)
  442. {
  443. return str().equalsIgnoreCase(anotherString);
  444. }
  445. /**
  446. * Compares two strings lexicographically.
  447. *
  448. * @param anotherString the <code>String</code> to be compared.
  449. *
  450. * NEEDSDOC @param xstr
  451. * @return the value <code>0</code> if the argument string is equal to
  452. * this string; a value less than <code>0</code> if this string
  453. * is lexicographically less than the string argument; and a
  454. * value greater than <code>0</code> if this string is
  455. * lexicographically greater than the string argument.
  456. * @exception java.lang.NullPointerException if <code>anotherString</code>
  457. * is <code>null</code>.
  458. */
  459. public int compareTo(XMLString xstr)
  460. {
  461. int len1 = this.length();
  462. int len2 = xstr.length();
  463. int n = Math.min(len1, len2);
  464. int i = 0;
  465. int j = 0;
  466. while (n-- != 0)
  467. {
  468. char c1 = this.charAt(i);
  469. char c2 = xstr.charAt(j);
  470. if (c1 != c2)
  471. {
  472. return c1 - c2;
  473. }
  474. i++;
  475. j++;
  476. }
  477. return len1 - len2;
  478. }
  479. /**
  480. * Compares two strings lexicographically, ignoring case considerations.
  481. * This method returns an integer whose sign is that of
  482. * <code>this.toUpperCase().toLowerCase().compareTo(
  483. * str.toUpperCase().toLowerCase())</code>.
  484. * <p>
  485. * Note that this method does <em>not</em> take locale into account,
  486. * and will result in an unsatisfactory ordering for certain locales.
  487. * The java.text package provides <em>collators</em> to allow
  488. * locale-sensitive ordering.
  489. *
  490. * @param str the <code>String</code> to be compared.
  491. * @return a negative integer, zero, or a positive integer as the
  492. * the specified String is greater than, equal to, or less
  493. * than this String, ignoring case considerations.
  494. * @see java.text.Collator#compare(String, String)
  495. * @since 1.2
  496. */
  497. public int compareToIgnoreCase(XMLString str)
  498. {
  499. // %REVIEW% Like it says, @since 1.2. Doesn't exist in earlier
  500. // versions of Java, hence we can't yet shell out to it. We can implement
  501. // it as character-by-character compare, but doing so efficiently
  502. // is likely to be (ahem) interesting.
  503. //
  504. // However, since nobody is actually _using_ this method yet:
  505. // return str().compareToIgnoreCase(str.toString());
  506. throw new org.apache.xml.utils.WrappedRuntimeException(
  507. new java.lang.NoSuchMethodException(
  508. "Java 1.2 method, not yet implemented"));
  509. }
  510. /**
  511. * Tests if this string starts with the specified prefix beginning
  512. * a specified index.
  513. *
  514. * @param prefix the prefix.
  515. * @param toffset where to begin looking in the string.
  516. * @return <code>true</code> if the character sequence represented by the
  517. * argument is a prefix of the substring of this object starting
  518. * at index <code>toffset</code> <code>false</code> otherwise.
  519. * The result is <code>false</code> if <code>toffset</code> is
  520. * negative or greater than the length of this
  521. * <code>String</code> object; otherwise the result is the same
  522. * as the result of the expression
  523. * <pre>
  524. * this.subString(toffset).startsWith(prefix)
  525. * </pre>
  526. * @exception java.lang.NullPointerException if <code>prefix</code> is
  527. * <code>null</code>.
  528. */
  529. public boolean startsWith(String prefix, int toffset)
  530. {
  531. return str().startsWith(prefix, toffset);
  532. }
  533. /**
  534. * Tests if this string starts with the specified prefix.
  535. *
  536. * @param prefix the prefix.
  537. * @return <code>true</code> if the character sequence represented by the
  538. * argument is a prefix of the character sequence represented by
  539. * this string; <code>false</code> otherwise.
  540. * Note also that <code>true</code> will be returned if the
  541. * argument is an empty string or is equal to this
  542. * <code>String</code> object as determined by the
  543. * {@link #equals(Object)} method.
  544. * @exception java.lang.NullPointerException if <code>prefix</code> is
  545. * <code>null</code>.
  546. */
  547. public boolean startsWith(String prefix)
  548. {
  549. return startsWith(prefix, 0);
  550. }
  551. /**
  552. * Tests if this string starts with the specified prefix beginning
  553. * a specified index.
  554. *
  555. * @param prefix the prefix.
  556. * @param toffset where to begin looking in the string.
  557. * @return <code>true</code> if the character sequence represented by the
  558. * argument is a prefix of the substring of this object starting
  559. * at index <code>toffset</code> <code>false</code> otherwise.
  560. * The result is <code>false</code> if <code>toffset</code> is
  561. * negative or greater than the length of this
  562. * <code>String</code> object; otherwise the result is the same
  563. * as the result of the expression
  564. * <pre>
  565. * this.subString(toffset).startsWith(prefix)
  566. * </pre>
  567. * @exception java.lang.NullPointerException if <code>prefix</code> is
  568. * <code>null</code>.
  569. */
  570. public boolean startsWith(XMLString prefix, int toffset)
  571. {
  572. int to = toffset;
  573. int tlim = this.length();
  574. int po = 0;
  575. int pc = prefix.length();
  576. // Note: toffset might be near -1>>>1.
  577. if ((toffset < 0) || (toffset > tlim - pc))
  578. {
  579. return false;
  580. }
  581. while (--pc >= 0)
  582. {
  583. if (this.charAt(to) != prefix.charAt(po))
  584. {
  585. return false;
  586. }
  587. to++;
  588. po++;
  589. }
  590. return true;
  591. }
  592. /**
  593. * Tests if this string starts with the specified prefix.
  594. *
  595. * @param prefix the prefix.
  596. * @return <code>true</code> if the character sequence represented by the
  597. * argument is a prefix of the character sequence represented by
  598. * this string; <code>false</code> otherwise.
  599. * Note also that <code>true</code> will be returned if the
  600. * argument is an empty string or is equal to this
  601. * <code>String</code> object as determined by the
  602. * {@link #equals(Object)} method.
  603. * @exception java.lang.NullPointerException if <code>prefix</code> is
  604. * <code>null</code>.
  605. */
  606. public boolean startsWith(XMLString prefix)
  607. {
  608. return startsWith(prefix, 0);
  609. }
  610. /**
  611. * Tests if this string ends with the specified suffix.
  612. *
  613. * @param suffix the suffix.
  614. * @return <code>true</code> if the character sequence represented by the
  615. * argument is a suffix of the character sequence represented by
  616. * this object; <code>false</code> otherwise. Note that the
  617. * result will be <code>true</code> if the argument is the
  618. * empty string or is equal to this <code>String</code> object
  619. * as determined by the {@link #equals(Object)} method.
  620. * @exception java.lang.NullPointerException if <code>suffix</code> is
  621. * <code>null</code>.
  622. */
  623. public boolean endsWith(String suffix)
  624. {
  625. return str().endsWith(suffix);
  626. }
  627. /**
  628. * Returns a hashcode for this string. The hashcode for a
  629. * <code>String</code> object is computed as
  630. * <blockquote><pre>
  631. * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
  632. * </pre></blockquote>
  633. * using <code>int</code> arithmetic, where <code>s[i]</code> is the
  634. * <i>i</i>th character of the string, <code>n</code> is the length of
  635. * the string, and <code>^</code> indicates exponentiation.
  636. * (The hash value of the empty string is zero.)
  637. *
  638. * @return a hash code value for this object.
  639. */
  640. public int hashCode()
  641. {
  642. return str().hashCode();
  643. }
  644. /**
  645. * Returns the index within this string of the first occurrence of the
  646. * specified character. If a character with value <code>ch</code> occurs
  647. * in the character sequence represented by this <code>String</code>
  648. * object, then the index of the first such occurrence is returned --
  649. * that is, the smallest value <i>k</i> such that:
  650. * <blockquote><pre>
  651. * this.charAt(<i>k</i>) == ch
  652. * </pre></blockquote>
  653. * is <code>true</code>. If no such character occurs in this string,
  654. * then <code>-1</code> is returned.
  655. *
  656. * @param ch a character.
  657. * @return the index of the first occurrence of the character in the
  658. * character sequence represented by this object, or
  659. * <code>-1</code> if the character does not occur.
  660. */
  661. public int indexOf(int ch)
  662. {
  663. return str().indexOf(ch);
  664. }
  665. /**
  666. * Returns the index within this string of the first occurrence of the
  667. * specified character, starting the search at the specified index.
  668. * <p>
  669. * If a character with value <code>ch</code> occurs in the character
  670. * sequence represented by this <code>String</code> object at an index
  671. * no smaller than <code>fromIndex</code>, then the index of the first
  672. * such occurrence is returned--that is, the smallest value <i>k</i>
  673. * such that:
  674. * <blockquote><pre>
  675. * (this.charAt(<i>k</i>) == ch) && (<i>k</i> >= fromIndex)
  676. * </pre></blockquote>
  677. * is true. If no such character occurs in this string at or after
  678. * position <code>fromIndex</code>, then <code>-1</code> is returned.
  679. * <p>
  680. * There is no restriction on the value of <code>fromIndex</code>. If it
  681. * is negative, it has the same effect as if it were zero: this entire
  682. * string may be searched. If it is greater than the length of this
  683. * string, it has the same effect as if it were equal to the length of
  684. * this string: <code>-1</code> is returned.
  685. *
  686. * @param ch a character.
  687. * @param fromIndex the index to start the search from.
  688. * @return the index of the first occurrence of the character in the
  689. * character sequence represented by this object that is greater
  690. * than or equal to <code>fromIndex</code>, or <code>-1</code>
  691. * if the character does not occur.
  692. */
  693. public int indexOf(int ch, int fromIndex)
  694. {
  695. return str().indexOf(ch, fromIndex);
  696. }
  697. /**
  698. * Returns the index within this string of the last occurrence of the
  699. * specified character. That is, the index returned is the largest
  700. * value <i>k</i> such that:
  701. * <blockquote><pre>
  702. * this.charAt(<i>k</i>) == ch
  703. * </pre></blockquote>
  704. * is true.
  705. * The String is searched backwards starting at the last character.
  706. *
  707. * @param ch a character.
  708. * @return the index of the last occurrence of the character in the
  709. * character sequence represented by this object, or
  710. * <code>-1</code> if the character does not occur.
  711. */
  712. public int lastIndexOf(int ch)
  713. {
  714. return str().lastIndexOf(ch);
  715. }
  716. /**
  717. * Returns the index within this string of the last occurrence of the
  718. * specified character, searching backward starting at the specified
  719. * index. That is, the index returned is the largest value <i>k</i>
  720. * such that:
  721. * <blockquote><pre>
  722. * this.charAt(k) == ch) && (k <= fromIndex)
  723. * </pre></blockquote>
  724. * is true.
  725. *
  726. * @param ch a character.
  727. * @param fromIndex the index to start the search from. There is no
  728. * restriction on the value of <code>fromIndex</code>. If it is
  729. * greater than or equal to the length of this string, it has
  730. * the same effect as if it were equal to one less than the
  731. * length of this string: this entire string may be searched.
  732. * If it is negative, it has the same effect as if it were -1:
  733. * -1 is returned.
  734. * @return the index of the last occurrence of the character in the
  735. * character sequence represented by this object that is less
  736. * than or equal to <code>fromIndex</code>, or <code>-1</code>
  737. * if the character does not occur before that point.
  738. */
  739. public int lastIndexOf(int ch, int fromIndex)
  740. {
  741. return str().lastIndexOf(ch, fromIndex);
  742. }
  743. /**
  744. * Returns the index within this string of the first occurrence of the
  745. * specified substring. The integer returned is the smallest value
  746. * <i>k</i> such that:
  747. * <blockquote><pre>
  748. * this.startsWith(str, <i>k</i>)
  749. * </pre></blockquote>
  750. * is <code>true</code>.
  751. *
  752. * @param str any string.
  753. * @return if the string argument occurs as a substring within this
  754. * object, then the index of the first character of the first
  755. * such substring is returned; if it does not occur as a
  756. * substring, <code>-1</code> is returned.
  757. * @exception java.lang.NullPointerException if <code>str</code> is
  758. * <code>null</code>.
  759. */
  760. public int indexOf(String str)
  761. {
  762. return str().indexOf(str);
  763. }
  764. /**
  765. * Returns the index within this string of the first occurrence of the
  766. * specified substring. The integer returned is the smallest value
  767. * <i>k</i> such that:
  768. * <blockquote><pre>
  769. * this.startsWith(str, <i>k</i>)
  770. * </pre></blockquote>
  771. * is <code>true</code>.
  772. *
  773. * @param str any string.
  774. * @return if the string argument occurs as a substring within this
  775. * object, then the index of the first character of the first
  776. * such substring is returned; if it does not occur as a
  777. * substring, <code>-1</code> is returned.
  778. * @exception java.lang.NullPointerException if <code>str</code> is
  779. * <code>null</code>.
  780. */
  781. public int indexOf(XMLString str)
  782. {
  783. return str().indexOf(str.toString());
  784. }
  785. /**
  786. * Returns the index within this string of the first occurrence of the
  787. * specified substring, starting at the specified index. The integer
  788. * returned is the smallest value <i>k</i> such that:
  789. * <blockquote><pre>
  790. * this.startsWith(str, <i>k</i>) && (<i>k</i> >= fromIndex)
  791. * </pre></blockquote>
  792. * is <code>true</code>.
  793. * <p>
  794. * There is no restriction on the value of <code>fromIndex</code>. If
  795. * it is negative, it has the same effect as if it were zero: this entire
  796. * string may be searched. If it is greater than the length of this
  797. * string, it has the same effect as if it were equal to the length of
  798. * this string: <code>-1</code> is returned.
  799. *
  800. * @param str the substring to search for.
  801. * @param fromIndex the index to start the search from.
  802. * @return If the string argument occurs as a substring within this
  803. * object at a starting index no smaller than
  804. * <code>fromIndex</code>, then the index of the first character
  805. * of the first such substring is returned. If it does not occur
  806. * as a substring starting at <code>fromIndex</code> or beyond,
  807. * <code>-1</code> is returned.
  808. * @exception java.lang.NullPointerException if <code>str</code> is
  809. * <code>null</code>
  810. */
  811. public int indexOf(String str, int fromIndex)
  812. {
  813. return str().indexOf(str, fromIndex);
  814. }
  815. /**
  816. * Returns the index within this string of the rightmost occurrence
  817. * of the specified substring. The rightmost empty string "" is
  818. * considered to occur at the index value <code>this.length()</code>.
  819. * The returned index is the largest value <i>k</i> such that
  820. * <blockquote><pre>
  821. * this.startsWith(str, k)
  822. * </pre></blockquote>
  823. * is true.
  824. *
  825. * @param str the substring to search for.
  826. * @return if the string argument occurs one or more times as a substring
  827. * within this object, then the index of the first character of
  828. * the last such substring is returned. If it does not occur as
  829. * a substring, <code>-1</code> is returned.
  830. * @exception java.lang.NullPointerException if <code>str</code> is
  831. * <code>null</code>.
  832. */
  833. public int lastIndexOf(String str)
  834. {
  835. return str().lastIndexOf(str);
  836. }
  837. /**
  838. * Returns the index within this string of the last occurrence of
  839. * the specified substring.
  840. *
  841. * @param str the substring to search for.
  842. * @param fromIndex the index to start the search from. There is no
  843. * restriction on the value of fromIndex. If it is greater than
  844. * the length of this string, it has the same effect as if it
  845. * were equal to the length of this string: this entire string
  846. * may be searched. If it is negative, it has the same effect
  847. * as if it were -1: -1 is returned.
  848. * @return If the string argument occurs one or more times as a substring
  849. * within this object at a starting index no greater than
  850. * <code>fromIndex</code>, then the index of the first character of
  851. * the last such substring is returned. If it does not occur as a
  852. * substring starting at <code>fromIndex</code> or earlier,
  853. * <code>-1</code> is returned.
  854. * @exception java.lang.NullPointerException if <code>str</code> is
  855. * <code>null</code>.
  856. */
  857. public int lastIndexOf(String str, int fromIndex)
  858. {
  859. return str().lastIndexOf(str, fromIndex);
  860. }
  861. /**
  862. * Returns a new string that is a substring of this string. The
  863. * substring begins with the character at the specified index and
  864. * extends to the end of this string. <p>
  865. * Examples:
  866. * <blockquote><pre>
  867. * "unhappy".substring(2) returns "happy"
  868. * "Harbison".substring(3) returns "bison"
  869. * "emptiness".substring(9) returns "" (an empty string)
  870. * </pre></blockquote>
  871. *
  872. * @param beginIndex the beginning index, inclusive.
  873. * @return the specified substring.
  874. * @exception IndexOutOfBoundsException if
  875. * <code>beginIndex</code> is negative or larger than the
  876. * length of this <code>String</code> object.
  877. */
  878. public XMLString substring(int beginIndex)
  879. {
  880. return new XString(str().substring(beginIndex));
  881. }
  882. /**
  883. * Returns a new string that is a substring of this string. The
  884. * substring begins at the specified <code>beginIndex</code> and
  885. * extends to the character at index <code>endIndex - 1</code>.
  886. * Thus the length of the substring is <code>endIndex-beginIndex</code>.
  887. *
  888. * @param beginIndex the beginning index, inclusive.
  889. * @param endIndex the ending index, exclusive.
  890. * @return the specified substring.
  891. * @exception IndexOutOfBoundsException if the
  892. * <code>beginIndex</code> is negative, or
  893. * <code>endIndex</code> is larger than the length of
  894. * this <code>String</code> object, or
  895. * <code>beginIndex</code> is larger than
  896. * <code>endIndex</code>.
  897. */
  898. public XMLString substring(int beginIndex, int endIndex)
  899. {
  900. return new XString(str().substring(beginIndex, endIndex));
  901. }
  902. /**
  903. * Concatenates the specified string to the end of this string.
  904. *
  905. * @param str the <code>String</code> that is concatenated to the end
  906. * of this <code>String</code>.
  907. * @return a string that represents the concatenation of this object's
  908. * characters followed by the string argument's characters.
  909. * @exception java.lang.NullPointerException if <code>str</code> is
  910. * <code>null</code>.
  911. */
  912. public XMLString concat(String str)
  913. {
  914. // %REVIEW% Make an FSB here?
  915. return new XString(str().concat(str));
  916. }
  917. /**
  918. * Converts all of the characters in this <code>String</code> to lower
  919. * case using the rules of the given <code>Locale</code>.
  920. *
  921. * @param locale use the case transformation rules for this locale
  922. * @return the String, converted to lowercase.
  923. * @see java.lang.Character#toLowerCase(char)
  924. * @see java.lang.String#toUpperCase(Locale)
  925. */
  926. public XMLString toLowerCase(Locale locale)
  927. {
  928. return new XString(str().toLowerCase(locale));
  929. }
  930. /**
  931. * Converts all of the characters in this <code>String</code> to lower
  932. * case using the rules of the default locale, which is returned
  933. * by <code>Locale.getDefault</code>.
  934. * <p>
  935. *
  936. * @return the string, converted to lowercase.
  937. * @see java.lang.Character#toLowerCase(char)
  938. * @see java.lang.String#toLowerCase(Locale)
  939. */
  940. public XMLString toLowerCase()
  941. {
  942. return new XString(str().toLowerCase());
  943. }
  944. /**
  945. * Converts all of the characters in this <code>String</code> to upper
  946. * case using the rules of the given locale.
  947. * @param locale use the case transformation rules for this locale
  948. * @return the String, converted to uppercase.
  949. * @see java.lang.Character#toUpperCase(char)
  950. * @see java.lang.String#toLowerCase(Locale)
  951. */
  952. public XMLString toUpperCase(Locale locale)
  953. {
  954. return new XString(str().toUpperCase(locale));
  955. }
  956. /**
  957. * Converts all of the characters in this <code>String</code> to upper
  958. * case using the rules of the default locale, which is returned
  959. * by <code>Locale.getDefault</code>.
  960. *
  961. * <p>
  962. * If no character in this string has a different uppercase version,
  963. * based on calling the <code>toUpperCase</code> method defined by
  964. * <code>Character</code>, then the original string is returned.
  965. * <p>
  966. * Otherwise, this method creates a new <code>String</code> object
  967. * representing a character sequence identical in length to the
  968. * character sequence represented by this <code>String</code> object and
  969. * with every character equal to the result of applying the method
  970. * <code>Character.toUpperCase</code> to the corresponding character of
  971. * this <code>String</code> object. <p>
  972. * Examples:
  973. * <blockquote><pre>
  974. * "Fahrvergnügen".toUpperCase() returns "FAHRVERGNÜGEN"
  975. * "Visit Ljubinje!".toUpperCase() returns "VISIT LJUBINJE!"
  976. * </pre></blockquote>
  977. *
  978. * @return the string, converted to uppercase.
  979. * @see java.lang.Character#toUpperCase(char)
  980. * @see java.lang.String#toUpperCase(Locale)
  981. */
  982. public XMLString toUpperCase()
  983. {
  984. return new XString(str().toUpperCase());
  985. }
  986. /**
  987. * Removes white space from both ends of this string.
  988. *
  989. * @return this string, with white space removed from the front and end.
  990. */
  991. public XMLString trim()
  992. {
  993. return new XString(str().trim());
  994. }
  995. /**
  996. * Returns whether the specified <var>ch</var> conforms to the XML 1.0 definition
  997. * of whitespace. Refer to <A href="http://www.w3.org/TR/1998/REC-xml-19980210#NT-S">
  998. * the definition of <CODE>S</CODE></A> for details.
  999. * @param ch Character to check as XML whitespace.
  1000. * @return =true if <var>ch</var> is XML whitespace; otherwise =false.
  1001. */
  1002. private static boolean isSpace(char ch)
  1003. {
  1004. return XMLCharacterRecognizer.isWhiteSpace(ch); // Take the easy way out for now.
  1005. }
  1006. /**
  1007. * Conditionally trim all leading and trailing whitespace in the specified String.
  1008. * All strings of white space are
  1009. * replaced by a single space character (#x20), except spaces after punctuation which
  1010. * receive double spaces if doublePunctuationSpaces is true.
  1011. * This function may be useful to a formatter, but to get first class
  1012. * results, the formatter should probably do it's own white space handling
  1013. * based on the semantics of the formatting object.
  1014. *
  1015. * @param trimHead Trim leading whitespace?
  1016. * @param trimTail Trim trailing whitespace?
  1017. * @param doublePunctuationSpaces Use double spaces for punctuation?
  1018. * @return The trimmed string.
  1019. */
  1020. public XMLString fixWhiteSpace(boolean trimHead, boolean trimTail,
  1021. boolean doublePunctuationSpaces)
  1022. {
  1023. // %OPT% !!!!!!!
  1024. int len = this.length();
  1025. char[] buf = new char[len];
  1026. this.getChars(0, len, buf, 0);
  1027. boolean edit = false;
  1028. int s;
  1029. for (s = 0; s < len; s++)
  1030. {
  1031. if (isSpace(buf[s]))
  1032. {
  1033. break;
  1034. }
  1035. }
  1036. /* replace S to ' '. and ' '+ -> single ' '. */
  1037. int d = s;
  1038. boolean pres = false;
  1039. for (; s < len; s++)
  1040. {
  1041. char c = buf[s];
  1042. if (isSpace(c))
  1043. {
  1044. if (!pres)
  1045. {
  1046. if (' ' != c)
  1047. {
  1048. edit = true;
  1049. }
  1050. buf[d++] = ' ';
  1051. if (doublePunctuationSpaces && (s != 0))
  1052. {
  1053. char prevChar = buf[s - 1];
  1054. if (!((prevChar == '.') || (prevChar == '!')
  1055. || (prevChar == '?')))
  1056. {
  1057. pres = true;
  1058. }
  1059. }
  1060. else
  1061. {
  1062. pres = true;
  1063. }
  1064. }
  1065. else
  1066. {
  1067. edit = true;
  1068. pres = true;
  1069. }
  1070. }
  1071. else
  1072. {
  1073. buf[d++] = c;
  1074. pres = false;
  1075. }
  1076. }
  1077. if (trimTail && 1 <= d && ' ' == buf[d - 1])
  1078. {
  1079. edit = true;
  1080. d--;
  1081. }
  1082. int start = 0;
  1083. if (trimHead && 0 < d && ' ' == buf[0])
  1084. {
  1085. edit = true;
  1086. start++;
  1087. }
  1088. XMLStringFactory xsf = XMLStringFactoryImpl.getFactory();
  1089. return edit ? xsf.newstr(new String(buf, start, d - start)) : this;
  1090. }
  1091. /**
  1092. * @see XPathVisitable#callVisitors(ExpressionOwner, XPathVisitor)
  1093. */
  1094. public void callVisitors(ExpressionOwner owner, XPathVisitor visitor)
  1095. {
  1096. visitor.visitStringLiteral(owner, this);
  1097. }
  1098. }