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: XStringForFSB.java 468655 2006-10-28 07:12:06Z minchau $
020     */
021    package org.apache.xpath.objects;
022    
023    import org.apache.xalan.res.XSLMessages;
024    import org.apache.xml.utils.FastStringBuffer;
025    import org.apache.xml.utils.XMLCharacterRecognizer;
026    import org.apache.xml.utils.XMLString;
027    import org.apache.xml.utils.XMLStringFactory;
028    import org.apache.xpath.res.XPATHErrorResources;
029    
030    /**
031     * This class will wrap a FastStringBuffer and allow for
032     */
033    public class XStringForFSB extends XString
034    {
035        static final long serialVersionUID = -1533039186550674548L;
036    
037      /** The start position in the fsb. */
038      int m_start;
039    
040      /** The length of the string. */
041      int m_length;
042    
043      /** If the str() function is called, the string will be cached here. */
044      protected String m_strCache = null;
045    
046      /** cached hash code */
047      protected int m_hash = 0;
048    
049      /**
050       * Construct a XNodeSet object.
051       *
052       * @param val FastStringBuffer object this will wrap, must be non-null.
053       * @param start The start position in the array.
054       * @param length The number of characters to read from the array.
055       */
056      public XStringForFSB(FastStringBuffer val, int start, int length)
057      {
058    
059        super(val);
060    
061        m_start = start;
062        m_length = length;
063    
064        if (null == val)
065          throw new IllegalArgumentException(
066            XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FASTSTRINGBUFFER_CANNOT_BE_NULL, null));
067      }
068    
069      /**
070       * Construct a XNodeSet object.
071       *
072       * @param val String object this will wrap.
073       */
074      private XStringForFSB(String val)
075      {
076    
077        super(val);
078    
079        throw new IllegalArgumentException(
080          XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FSB_CANNOT_TAKE_STRING, null)); // "XStringForFSB can not take a string for an argument!");
081      }
082    
083      /**
084       * Cast result object to a string.
085       *
086       * @return The string this wraps or the empty string if null
087       */
088      public FastStringBuffer fsb()
089      {
090        return ((FastStringBuffer) m_obj);
091      }
092      
093      /**
094       * Cast result object to a string.
095       *
096       * @return The string this wraps or the empty string if null
097       */
098      public void appendToFsb(org.apache.xml.utils.FastStringBuffer fsb)
099      {
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    }