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