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