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 }