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