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