KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > quercus > lib > string > StringModule


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.quercus.lib.string;
31
32 import com.caucho.quercus.QuercusException;
33 import com.caucho.quercus.QuercusModuleException;
34 import com.caucho.quercus.annotation.NotNull;
35 import com.caucho.quercus.annotation.Optional;
36 import com.caucho.quercus.annotation.Reference;
37 import com.caucho.quercus.annotation.UsesSymbolTable;
38 import com.caucho.quercus.env.*;
39 import com.caucho.quercus.lib.ArrayModule;
40 import com.caucho.quercus.lib.file.BinaryOutput;
41 import com.caucho.quercus.lib.file.FileModule;
42 import com.caucho.quercus.module.AbstractQuercusModule;
43 import com.caucho.util.L10N;
44 import com.caucho.util.RandomUtil;
45 import com.caucho.vfs.ByteToChar;
46 import com.caucho.vfs.Path;
47
48 import java.io.IOException JavaDoc;
49 import java.io.InputStream JavaDoc;
50 import java.security.MessageDigest JavaDoc;
51 import java.text.DecimalFormat JavaDoc;
52 import java.text.DecimalFormatSymbols JavaDoc;
53 import java.text.NumberFormat JavaDoc;
54 import java.util.ArrayList JavaDoc;
55 import java.util.Iterator JavaDoc;
56 import java.util.Locale JavaDoc;
57 import java.util.Map JavaDoc;
58 import java.util.logging.Level JavaDoc;
59 import java.util.logging.Logger JavaDoc;
60 import java.util.zip.CRC32 JavaDoc;
61
62 /**
63  * PHP functions implemented from the string module
64  */

65 public class StringModule extends AbstractQuercusModule {
66   private static final Logger JavaDoc log =
67     Logger.getLogger(StringModule.class.getName());
68
69   private static final L10N L = new L10N(StringModule.class);
70
71   public static final int CRYPT_SALT_LENGTH = 2;
72   public static final int CRYPT_STD_DES = 0;
73   public static final int CRYPT_EXT_DES = 0;
74   public static final int CRYPT_MD5 = 0;
75   public static final int CRYPT_BLOWFISH = 0;
76
77   public static final int CHAR_MAX = 1;
78
79   public static final int LC_CTYPE = 1;
80   public static final int LC_NUMERIC = 2;
81   public static final int LC_TIME = 3;
82   public static final int LC_COLLATE = 4;
83   public static final int LC_MONETARY = 5;
84   public static final int LC_ALL = 6;
85   public static final int LC_MESSAGES = 7;
86
87   public static final int STR_PAD_LEFT = 1;
88   public static final int STR_PAD_RIGHT = 0;
89   public static final int STR_PAD_BOTH = 2;
90
91   private static final DecimalFormatSymbols JavaDoc DEFAULT_DECIMAL_FORMAT_SYMBOLS ;
92
93   /**
94    * Escapes a string using C syntax.
95    *
96    * @see #stripcslashes
97    *
98    * @param source the source string to convert
99    * @param characters the set of characters to convert
100    * @return the escaped string
101    */

102   public static StringValue addcslashes(StringValue source, String JavaDoc characters)
103   {
104     if (characters == null)
105       characters = "";
106     
107     boolean []bitmap = parseCharsetBitmap(characters);
108
109     int length = source.length();
110
111     StringBuilderValue sb = new StringBuilderValue(length * 5 / 4);
112
113     for (int i = 0; i < length; i++) {
114       char ch = source.charAt(i);
115
116       if (ch >= 256 || ! bitmap[ch]) {
117         sb.append(ch);
118         continue;
119       }
120
121       switch (ch) {
122       case 0x07:
123         sb.append("\\a");
124         break;
125       case '\b':
126         sb.append("\\b");
127         break;
128       case '\t':
129         sb.append("\\t");
130         break;
131       case '\n':
132         sb.append("\\n");
133         break;
134       case 0xb:
135         sb.append("\\v");
136         break;
137       case '\f':
138         sb.append("\\f");
139         break;
140       case '\r':
141         sb.append("\\r");
142         break;
143       default:
144         if (ch < 0x20 || ch >= 0x7f) {
145           // save as octal
146
sb.append("\\");
147           sb.append((char) ('0' + ((ch >> 6) & 7)));
148           sb.append((char) ('0' + ((ch >> 3) & 7)));
149           sb.append((char) ('0' + ((ch) & 7)));
150           break;
151         }
152         else {
153           sb.append("\\");
154           sb.append(ch);
155           break;
156         }
157       }
158     }
159
160     return sb;
161   }
162
163   /**
164    * Parses the cslashes bitmap returning an actual bitmap.
165    *
166    * @param charset the bitmap string
167    * @return the actual bitmap
168    */

169   private static boolean []parseCharsetBitmap(String JavaDoc charset)
170   {
171     boolean []bitmap = new boolean[256];
172
173     int length = charset.length();
174     for (int i = 0; i < length; i++) {
175       char ch = charset.charAt(i);
176
177       // XXX: the bitmap eventual might need to deal with unicode
178
if (ch >= 256)
179         continue;
180
181       bitmap[ch] = true;
182
183       if (length <= i + 3)
184         continue;
185
186       if (charset.charAt(i + 1) != '.' || charset.charAt(i + 2) != '.')
187         continue;
188
189       char last = charset.charAt(i + 3);
190
191       if (last < ch) {
192         // XXX: exception type
193
throw new RuntimeException JavaDoc(L.l("Invalid range."));
194       }
195
196       i += 3;
197       for (; ch <= last; ch++) {
198         bitmap[ch] = true;
199       }
200
201       // XXX: handling of '@'?
202
}
203
204     return bitmap;
205   }
206
207   /**
208    * Escapes a string for db characters.
209    *
210    * @param source the source string to convert
211    * @return the escaped string
212    */

213   public static StringValue addslashes(StringValue source)
214   {
215     StringBuilderValue sb = new StringBuilderValue();
216     int length = source.length();
217     for (int i = 0; i < length; i++) {
218       char ch = source.charAt(i);
219
220       switch (ch) {
221       case 0x0:
222         sb.append("\\0");
223         break;
224       case '\'':
225         sb.append("\\'");
226         break;
227       case '\"':
228         sb.append("\\\"");
229         break;
230       case '\\':
231         sb.append("\\\\");
232         break;
233       default:
234         sb.append(ch);
235         break;
236       }
237     }
238
239     return new StringValueImpl(sb.toString());
240   }
241
242   /**
243    * Converts a binary value to a hex value.
244    */

245   public static StringValue bin2hex(InputStream JavaDoc is)
246   {
247     try {
248       StringBuilderValue sb = new StringBuilderValue();
249
250       int ch;
251       while ((ch = is.read()) >= 0) {
252     int d = (ch >> 4) & 0xf;
253
254     if (d < 10)
255       sb.append((char) (d + '0'));
256     else
257       sb.append((char) (d + 'a' - 10));
258
259     d = (ch) & 0xf;
260
261     if (d < 10)
262       sb.append((char) (d + '0'));
263     else
264       sb.append((char) (d + 'a' - 10));
265       }
266
267       return sb;
268     } catch (IOException JavaDoc e) {
269       throw new QuercusModuleException(e);
270     }
271   }
272
273   /**
274    * Alias of rtrim. Removes trailing whitespace.
275 v *
276    * @param env the quercus environment
277    * @param str the string to be trimmed
278    * @param charset optional set of characters to trim
279    * @return the trimmed string
280    */

281   public static StringValue chop(Env env,
282                  StringValue str,
283                  @Optional String JavaDoc charset)
284   {
285     return rtrim(env, str, charset);
286   }
287
288   /**
289    * converts a number to its character equivalent
290    *
291    * @param value the integer value
292    *
293    * @return the string equivalent
294    */

295   public static String JavaDoc chr(long value)
296   {
297     return String.valueOf((char) value);
298   }
299
300   /**
301    * Splits a string into chunks
302    *
303    * @param body the body string
304    * @param chunkLen the optional chunk length, defaults to 76
305    * @param end the optional end value, defaults to "\r\n"
306    */

307   public static String JavaDoc chunk_split(String JavaDoc body,
308                                    @Optional("76") int chunkLen,
309                                    @Optional("\"\\r\\n\"") String JavaDoc end)
310   {
311     if (body == null)
312       body = "";
313     
314     if (end == null)
315       end = "";
316     
317     if (chunkLen < 1) // XXX: real exn
318
throw new IllegalArgumentException JavaDoc(L.l("bad value {0}", chunkLen));
319
320     StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
321
322     int i = 0;
323
324     for (; i + chunkLen <= body.length(); i += chunkLen) {
325       sb.append(body.substring(i, i + chunkLen));
326       sb.append(end);
327     }
328
329     if (i < body.length()) {
330       sb.append(body.substring(i));
331       sb.append(end);
332     }
333
334     return sb.toString();
335   }
336
337   /**
338    * Converts from one cyrillic set to another.
339    *
340    * This implementation does nothing, because quercus stores strings as
341    * 16 bit unicode.
342    */

343   public static String JavaDoc convert_cyr_string(Env env,
344                       String JavaDoc str,
345                       String JavaDoc from,
346                       String JavaDoc to)
347   {
348     env.stub("convert_cyr_string");
349     
350     return str;
351   }
352
353   public static Value convert_uudecode(Env env, String JavaDoc source)
354   {
355     try {
356       if (source == null || source.length() == 0)
357     return BooleanValue.FALSE;
358
359       ByteToChar byteToChar = env.getByteToChar();
360
361       int length = source.length();
362
363       int i = 0;
364       while (i < length) {
365     int ch1 = source.charAt(i++);
366
367     if (ch1 == 0x60 || ch1 == 0x20)
368       break;
369     else if (ch1 < 0x20 || 0x5f < ch1)
370       continue;
371
372     int sublen = ch1 - 0x20;
373
374     while (sublen > 0) {
375       int code;
376
377       code = ((source.charAt(i++) - 0x20) & 0x3f) << 18;
378       code += ((source.charAt(i++) - 0x20) & 0x3f) << 12;
379       code += ((source.charAt(i++) - 0x20) & 0x3f) << 6;
380       code += ((source.charAt(i++) - 0x20) & 0x3f);
381
382       byteToChar.addByte(code >> 16);
383
384       if (sublen > 1)
385         byteToChar.addByte(code >> 8);
386
387       if (sublen > 2)
388         byteToChar.addByte(code);
389
390       sublen -= 3;
391     }
392       }
393
394       return new StringValueImpl(byteToChar.getConvertedString());
395     } catch (IOException JavaDoc e) {
396       throw new QuercusModuleException(e);
397     }
398   }
399
400   /**
401    * uuencode a string.
402    */

403   public static Value convert_uuencode(String JavaDoc source)
404   {
405     if (source == null || source.length() == 0)
406       return BooleanValue.FALSE;
407
408     StringBuilderValue result = new StringBuilderValue();
409
410     int i = 0;
411     int length = source.length();
412     while (i < length) {
413       int sublen = length - i;
414
415       if (45 < sublen)
416         sublen = 45;
417
418       result.append((char) (sublen + 0x20));
419
420       int end = i + sublen;
421
422       while (i < end) {
423         int code = source.charAt(i++) << 16;
424
425         if (i < length)
426           code += source.charAt(i++) << 8;
427
428         if (i < length)
429           code += source.charAt(i++);
430
431         result.append(toUUChar(((code >> 18) & 0x3f)));
432         result.append(toUUChar(((code >> 12) & 0x3f)));
433         result.append(toUUChar(((code >> 6) & 0x3f)));
434         result.append(toUUChar(((code) & 0x3f)));
435       }
436
437       result.append('\n');
438     }
439
440     result.append((char) 0x60);
441     result.append('\n');
442
443     return result;
444   }
445   /**
446    * Returns an array of information about the characters.
447    */

448   public static Value count_chars(StringValue data,
449                                   @Optional("0") int mode)
450   {
451     if (data == null)
452       data = StringValue.EMPTY;
453
454     int []count = new int[256];
455
456     int length = data.length();
457
458     for (int i = 0; i < length; i++) {
459       count[data.charAt(i) & 0xff] += 1;
460     }
461
462     switch (mode) {
463     case 0:
464       {
465         ArrayValue result = new ArrayValueImpl();
466
467         for (int i = 0; i < count.length; i++) {
468           result.put(LongValue.create(i), LongValue.create(count[i]));
469         }
470
471         return result;
472       }
473
474     case 1:
475       {
476         ArrayValue result = new ArrayValueImpl();
477
478         for (int i = 0; i < count.length; i++) {
479           if (count[i] > 0)
480             result.put(LongValue.create(i), new LongValue(count[i]));
481         }
482
483         return result;
484       }
485
486     case 2:
487       {
488         ArrayValue result = new ArrayValueImpl();
489
490         for (int i = 0; i < count.length; i++) {
491           if (count[i] == 0)
492             result.put(new LongValue(i), new LongValue(count[i]));
493         }
494
495         return result;
496       }
497
498     case 3:
499       {
500         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
501
502         for (int i = 0; i < count.length; i++) {
503           if (count[i] > 0)
504             sb.append((char) i);
505         }
506
507         return new StringValueImpl(sb.toString());
508       }
509
510     case 4:
511       {
512         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
513
514         for (int i = 0; i < count.length; i++) {
515           if (count[i] == 0)
516             sb.append((char) i);
517         }
518
519         return new StringValueImpl(sb.toString());
520       }
521
522     default:
523       return BooleanValue.FALSE;
524     }
525   }
526
527   /**
528    * Calculates the crc32 value for a string
529    *
530    * @param str the string value
531    *
532    * @return the crc32 hash
533    */

534   public static long crc32(InputStream JavaDoc is)
535   {
536     try {
537       CRC32 JavaDoc crc = new CRC32 JavaDoc();
538
539       int ch;
540       while ((ch = is.read()) >= 0) {
541     crc.update((byte) ch);
542       }
543       
544       return crc.getValue() & 0xffffffff;
545     } catch (IOException JavaDoc e) {
546       throw new QuercusModuleException(e);
547     }
548   }
549
550   public static String JavaDoc crypt(String JavaDoc string, @Optional String JavaDoc salt)
551   {
552     if (string == null)
553       string = "";
554     
555     if (salt == null || salt.equals("")) {
556       salt = ("" + Crypt.resultToChar(RandomUtil.nextInt(0x40)) +
557               Crypt.resultToChar(RandomUtil.nextInt(0x40)));
558     }
559     
560     return Crypt.crypt(string, salt);
561   }
562
563   /**
564    * Explodes a string into an array
565    *
566    * @param separator the separator string
567    * @param string the string to be exploded
568    * @param limit the max number of elements
569    * @return an array of exploded values
570    */

571   public static Value explode(StringValue separator,
572                               StringValue string,
573                               @Optional("0x7fffffff") long limit)
574   {
575     if (separator.length() == 0)
576       return BooleanValue.FALSE;
577
578     ArrayValue array = new ArrayValueImpl();
579
580     int head = 0;
581     int tail;
582
583     int i = 0;
584     while ((tail = string.indexOf(separator, head)) >= 0) {
585       if (limit <= i + 1)
586         break;
587
588       LongValue key = LongValue.create(i++);
589
590       StringValue chunk = string.substring(head, tail);
591
592       array.put(key, chunk);
593
594       head = tail + separator.length();
595     }
596
597     LongValue key = LongValue.create(i);
598
599     StringValue chunk = string.substring(head);
600
601     array.put(key, chunk);
602
603     return array;
604   }
605
606   /**
607    * Use printf style formatting to write a string to a file.
608    * @param fd the file to write to
609    * @param format the format string
610    * @param args the valujes to apply to the format string
611    */

612   public static Value fprintf(Env env,
613                               @NotNull BinaryOutput os,
614                               String JavaDoc format,
615                               Value []args)
616   {
617     Value value = sprintf(format, args);
618
619     return FileModule.fwrite(env, os, value.toInputStream(),
620                  Integer.MAX_VALUE);
621   }
622
623   /**
624    * implodes an array into a string
625    *
626    * @param glueV the separator string
627    * @param piecesV the array to be imploded
628    *
629    * @return a string of imploded values
630    */

631   public static Value implode(Env env,
632                   Value glueV,
633                               Value piecesV)
634   {
635     StringValue glue;
636     ArrayValue pieces;
637
638     if (piecesV instanceof ArrayValue) {
639       pieces = (ArrayValue) piecesV;
640       glue = glueV.toStringValue();
641     }
642     else if (glueV instanceof ArrayValue) {
643       pieces = (ArrayValue) glueV;
644       glue = piecesV.toStringValue();
645     }
646     else {
647       env.error(L.l("neither argument to implode is an array: {0}, {1}",
648             glueV.getClass().getName(), piecesV.getClass().getName()));
649
650       return BooleanValue.FALSE;
651     }
652
653     StringBuilderValue sb = new StringBuilderValue();
654     boolean isFirst = true;
655
656     for (ArrayValue.Entry entry = pieces.getHead();
657      entry != null;
658      entry = entry.getNext()) {
659       if (! isFirst)
660         glue.appendTo(sb);
661
662       isFirst = false;
663
664       entry.getValue().appendTo(sb);
665     }
666
667     return sb;
668   }
669
670   /**
671    * implodes an array into a string
672    *
673    * @param glueV the separator string
674    * @param piecesV the array to be imploded
675    *
676    * @return a string of imploded values
677    */

678   public static Value join(Env env,
679                            Value glueV,
680                            Value piecesV)
681   {
682     return implode(env, glueV, piecesV);
683   }
684
685   /**
686    * returns the md5 hash
687    *
688    * @param source the string
689    * @param rawOutput if true, return the raw binary
690    *
691    * @return a string of imploded values
692    */

693   public static StringValue md5(InputStream JavaDoc is,
694                 @Optional boolean rawOutput)
695   {
696     try {
697       MessageDigest JavaDoc md = MessageDigest.getInstance("MD5");
698       
699       // XXX: iso-8859-1
700

701       int ch;
702       while ((ch = is.read()) >= 0) {
703     md.update((byte) ch);
704       }
705       
706       byte []digest = md.digest();
707       
708       StringBuilderValue sb = new StringBuilderValue();
709       for (int i = 0; i < digest.length; i++) {
710     int d1 = (digest[i] >> 4) & 0xf;
711     int d2 = (digest[i] & 0xf);
712     
713     sb.append(toHexChar(d1));
714     sb.append(toHexChar(d2));
715       }
716       
717       return sb;
718     } catch (Exception JavaDoc e) {
719       throw new QuercusModuleException(e);
720     }
721   }
722
723   /**
724    * returns the md5 hash
725    *
726    * @param source the string
727    * @param rawOutput if true, return the raw binary
728    *
729    * @return a string of imploded values
730    */

731   public static Value md5_file(Path source,
732                                @Optional boolean rawOutput)
733   {
734     try {
735       MessageDigest JavaDoc md = MessageDigest.getInstance("MD5");
736       InputStream JavaDoc is = null;
737       
738       try {
739     is = source.openRead();
740     int d;
741     
742     while ((d = is.read()) >= 0) {
743       md.update((byte) d);
744     }
745     
746     return digestToString(md.digest());
747       } catch (IOException JavaDoc e) {
748     log.log(Level.FINE, e.toString(), e);
749     
750     return BooleanValue.FALSE;
751       } finally {
752     try {
753       if (is != null)
754         is.close();
755     } catch (IOException JavaDoc e) {
756     }
757       }
758     } catch (Exception JavaDoc e) {
759       throw new QuercusModuleException(e);
760     }
761   }
762
763   private static StringValue digestToString(byte []digest)
764   {
765     StringBuilderValue sb = new StringBuilderValue();
766     for (int i = 0; i < digest.length; i++) {
767       int d1 = (digest[i] >> 4) & 0xf;
768       int d2 = (digest[i] & 0xf);
769
770       sb.append(toHexChar(d1));
771       sb.append(toHexChar(d2));
772     }
773
774     return sb;
775   }
776
777   /**
778    * Returns a formatted money value.
779    *
780    * @param format the format
781    * @param value the value
782    *
783    * @return a string of formatted values
784    */

785   public static String JavaDoc money_format(Env env, String JavaDoc format, double value)
786   {
787     Locale JavaDoc monetaryLocale = env.getLocaleInfo().getMonetary();
788
789     return NumberFormat.getCurrencyInstance(monetaryLocale).format(value);
790   }
791
792   /**
793    * Returns the metaphone of a string.
794    * This implentation produces identical results to the php version, which does contain some bugs.
795    */

796   public static String JavaDoc metaphone(String JavaDoc string)
797   {
798     if (string == null)
799       string = "";
800     
801     int length = string.length();
802     int index = 0;
803     char ch = 0;
804
805     // ignore everything up until first letter
806
for (; index < length; index++) {
807       ch = toUpperCase(string.charAt(index));
808
809       if ('A' <= ch && ch <= 'Z')
810         break;
811     }
812
813     if (index == length)
814       return "";
815
816     int lastIndex = length - 1;
817
818     StringBuilder JavaDoc result = new StringBuilder JavaDoc(length);
819
820     // special case first letter
821

822     char nextCh
823       = index < lastIndex
824       ? toUpperCase(string.charAt(index + 1))
825       : 0;
826
827     switch (ch) {
828       case 'A':
829         if (nextCh == 'E') {
830           result.append('E');
831           index += 2;
832         }
833         else {
834           result.append('A');
835           index += 1;
836         }
837
838         break;
839
840       case 'E':
841       case 'I':
842       case 'O':
843       case 'U':
844         result.append(ch);
845         index += 1;
846         break;
847
848       case 'G':
849       case 'K':
850       case 'P':
851         if (nextCh == 'N') {
852           result.append('N');
853           index += 2;
854         }
855
856         break;
857
858       case 'W':
859         if (nextCh == 'H' || nextCh == 'R') {
860           result.append(nextCh);
861           index += 2;
862         }
863         else {
864           switch (nextCh) {
865             case 'A':
866             case 'E':
867             case 'I':
868             case 'O':
869             case 'U':
870               result.append('W');
871               index += 2;
872               break;
873             default:
874               break;
875           }
876         }
877
878         break;
879
880       case 'X':
881         result.append('S');
882         index += 1;
883         break;
884
885       default:
886         break;
887     }
888
889     // the rest of the letters
890

891     char prevCh;
892
893     for (; index < length; index++) {
894
895       if (index > 0)
896         prevCh = toUpperCase(string.charAt(index - 1));
897       else
898         prevCh = 0;
899
900       ch = toUpperCase(string.charAt(index));
901
902       if (ch < 'A' || ch > 'Z')
903         continue;
904
905       if (ch == prevCh && ch != 'C')
906         continue;
907
908       if (index + 1 < length)
909         nextCh = toUpperCase(string.charAt(index + 1));
910       else
911         nextCh = 0;
912
913       char nextnextCh;
914
915       if (index + 2 < length)
916         nextnextCh = toUpperCase(string.charAt(index + 2));
917       else
918         nextnextCh = 0;
919
920
921       switch (ch) {
922         case 'B':
923           if (prevCh != 'M')
924             result.append('B');
925           break;
926
927         case 'C':
928             switch (nextCh) {
929               case 'E':
930               case 'I':
931               case 'Y':
932                 // makesoft
933
if (nextCh == 'I' && nextnextCh == 'A') {
934                   result.append('X');
935                 }
936                 else if (prevCh == 'S') {
937                 }
938                 else {
939                   result.append('S');
940                 }
941                 break;
942               default:
943                 if (nextCh == 'H') {
944                   result.append('X');
945                   index++;
946                 }
947                 else {
948                   result.append('K');
949                 }
950                 break;
951             }
952
953           break;
954
955         case 'D':
956           if (nextCh == 'G') {
957             switch (nextnextCh) {
958               case 'E':
959               case 'I':
960               case 'Y':
961                 // makesoft
962
result.append('J');
963                 index++;
964                 break;
965               default:
966                 result.append('T');
967                 break;
968             }
969           }
970           else
971             result.append('T');
972
973           break;
974
975         case 'G':
976           if (nextCh == 'H') {
977             boolean isSilent = false;
978
979             if (index - 3 >= 0) {
980               char prev3Ch = toUpperCase(string.charAt(index - 3));
981               switch (prev3Ch) {
982                 // noghtof
983
case 'B':
984                 case 'D':
985                 case 'H':
986                   isSilent = true;
987                   break;
988                 default:
989                   break;
990               }
991             }
992
993             if (!isSilent) {
994               if (index - 4 >= 0) {
995                 char prev4Ch = toUpperCase(string.charAt(index - 4));
996
997                 isSilent = (prev4Ch == 'H');
998               }
999             }
1000
1001            if (!isSilent) {
1002              result.append('F');
1003              index++;
1004            }
1005          }
1006          else if (nextCh == 'N') {
1007            char nextnextnextCh;
1008
1009            if (index + 3 < length)
1010              nextnextnextCh = toUpperCase(string.charAt(index + 3));
1011            else
1012              nextnextnextCh = 0;
1013
1014            if (nextnextCh < 'A' || nextnextCh > 'Z') {
1015            }
1016            else if (nextnextCh == 'E' && nextnextnextCh == 'D') {
1017            }
1018            else
1019              result.append('K');
1020          }
1021          else if (prevCh == 'G') {
1022            result.append('K');
1023          }
1024          else {
1025            switch (nextCh) {
1026              case 'E':
1027              case 'I':
1028              case 'Y':
1029                // makesoft
1030
result.append('J');
1031                break;
1032              default:
1033                result.append('K');
1034                break;
1035            }
1036          }
1037
1038          break;
1039
1040        case 'H':
1041        case 'W':
1042        case 'Y':
1043          switch (nextCh) {
1044            case 'A':
1045            case 'E':
1046            case 'I':
1047            case 'O':
1048            case 'U':
1049              // followed by a vowel
1050

1051              if (ch == 'H') {
1052                switch (prevCh) {
1053                  case 'C':
1054                  case 'G':
1055                  case 'P':
1056                  case 'S':
1057                  case 'T':
1058                    // affecth
1059
break;
1060                  default:
1061                    result.append('H');
1062                    break;
1063                }
1064              }
1065              else
1066                result.append(ch);
1067
1068              break;
1069            default:
1070              // not followed by a vowel
1071
break;
1072          }
1073
1074          break;
1075
1076        case 'K':
1077          if (prevCh != 'C')
1078            result.append('K');
1079
1080          break;
1081
1082        case 'P':
1083          if (nextCh == 'H')
1084            result.append('F');
1085          else
1086            result.append('P');
1087
1088          break;
1089
1090        case 'Q':
1091          result.append('K');
1092          break;
1093
1094        case 'S':
1095          if (nextCh == 'I' && (nextnextCh == 'O' || nextnextCh == 'A')) {
1096            result.append('X');
1097          }
1098          else if (nextCh == 'H') {
1099            result.append('X');
1100            index++;
1101          }
1102          else
1103            result.append('S');
1104
1105          break;
1106
1107        case 'T':
1108          if (nextCh == 'I' && (nextnextCh == 'O' || nextnextCh == 'A')) {
1109            result.append('X');
1110          }
1111          else if (nextCh == 'H') {
1112            result.append('0');
1113            index++;
1114          }
1115          else
1116            result.append('T');
1117
1118          break;
1119
1120        case 'V':
1121          result.append('F');
1122
1123          break;
1124
1125        case 'X':
1126          result.append('K');
1127          result.append('S');
1128          break;
1129
1130        case 'Z':
1131          result.append('S');
1132          break;
1133
1134        case 'F':
1135        case 'J':
1136        case 'L':
1137        case 'M':
1138        case 'N':
1139        case 'R':
1140          result.append(ch);
1141          break;
1142
1143        default:
1144          break;
1145      }
1146    }
1147
1148    return result.toString();
1149  }
1150
1151  /**
1152   * Returns a formatted number.
1153   *
1154   * @param value the value
1155   * @param decimals the number of decimals
1156   * @param pointValue the decimal point string
1157   * @param groupValue the thousands separator
1158   *
1159   * @return a string of the formatted number
1160   */

1161  public static String JavaDoc number_format(Env env,
1162                                     double value,
1163                                     @Optional int decimals,
1164                                     @Optional Value pointValue,
1165                                     @Optional Value groupValue)
1166  {
1167    boolean isGroupDefault = (groupValue instanceof DefaultValue);
1168    boolean isPointDefault = (pointValue instanceof DefaultValue);
1169
1170    if (!isPointDefault && isGroupDefault) {
1171      env.warning(L.l("wrong parameter count"));
1172      return null;
1173    }
1174
1175    String JavaDoc pattern;
1176
1177    char point = '.';
1178
1179    if (!pointValue.isNull()) {
1180      String JavaDoc pointString = pointValue.toString();
1181
1182      point = (pointString.length() == 0) ? 0 : pointString.charAt(0);
1183    }
1184
1185    char group = ',';
1186
1187    if (!groupValue.isNull()) {
1188      String JavaDoc groupString = groupValue.toString();
1189
1190      group = (groupString.length() == 0) ? 0 : groupString.charAt(0);
1191    }
1192
1193    if (decimals > 0) {
1194      StringBuilder JavaDoc patternBuilder = new StringBuilder JavaDoc(6 + decimals);
1195
1196      patternBuilder.append(group == 0 ? "###0." : "#,##0.");
1197
1198      for (int i = 0; i < decimals; i++)
1199        patternBuilder.append('0');
1200
1201      pattern = patternBuilder.toString();
1202    }
1203    else {
1204      pattern = group == 0 ? "###0" : "#,##0";
1205    }
1206
1207    DecimalFormatSymbols JavaDoc decimalFormatSymbols;
1208
1209    if (point == '.' && group == ',')
1210      decimalFormatSymbols = DEFAULT_DECIMAL_FORMAT_SYMBOLS;
1211    else {
1212      decimalFormatSymbols = new DecimalFormatSymbols JavaDoc();
1213      decimalFormatSymbols.setDecimalSeparator(point);
1214      decimalFormatSymbols.setGroupingSeparator(group);
1215      decimalFormatSymbols.setZeroDigit('0');
1216    }
1217
1218    DecimalFormat JavaDoc format = new DecimalFormat JavaDoc(pattern, decimalFormatSymbols);
1219
1220    String JavaDoc result = format.format(value);
1221
1222    if (point == 0 && decimals > 0) {
1223      // no way to get DecimalFormat to output nothing for the point,
1224
// so remove it here
1225
int i = result.lastIndexOf(point);
1226
1227      return result.substring(0, i) + result.substring(i + 1, result.length());
1228    }
1229    else
1230      return result;
1231  }
1232
1233 /**
1234   * Converts the first character to an integer.
1235   *
1236   * @param string the string to be converted
1237   *
1238   * @return the integer value
1239   */

1240  public static long ord(StringValue string)
1241  {
1242    if (string.length() == 0)
1243      return 0;
1244    else
1245      return string.charAt(0);
1246  }
1247
1248  /**
1249   * Parses the string as a query string.
1250   *
1251   * @param env the calling environment
1252   * @param str the query string
1253   * @param array the optional result array
1254   */

1255  @UsesSymbolTable
1256  public static Value parse_str(Env env, String JavaDoc str,
1257                                @Optional @Reference Value ref)
1258  {
1259    if (str == null)
1260      str = "";
1261    
1262    try {
1263      ByteToChar byteToChar = env.getByteToChar();
1264      int len = str.length();
1265
1266      boolean isRef = ref instanceof Var;
1267
1268      ArrayValue result = null;
1269
1270      if (isRef) {
1271    result = new ArrayValueImpl();
1272    ref.set(result);
1273      }
1274      else if (ref instanceof ArrayValue) {
1275    result = (ArrayValue) ref;
1276    isRef = true;
1277      }
1278      else
1279    result = new ArrayValueImpl();
1280
1281      for (int i = 0; i < len; i++) {
1282    int ch = 0;
1283    byteToChar.clear();
1284
1285    for (; i < len && (ch = str.charAt(i)) == '&'; i++) {
1286    }
1287      
1288    for (; i < len && (ch = str.charAt(i)) != '='; i++) {
1289      i = addQueryChar(byteToChar, str, len, i, ch);
1290    }
1291
1292    String JavaDoc key = byteToChar.getConvertedString();
1293
1294    byteToChar.clear();
1295
1296    String JavaDoc value;
1297    if (ch == '=') {
1298      for (i++; i < len && (ch = str.charAt(i)) != '&'; i++) {
1299        i = addQueryChar(byteToChar, str, len, i, ch);
1300      }
1301
1302      value = byteToChar.getConvertedString();
1303    }
1304    else
1305      value = "";
1306
1307    if (isRef) {
1308      Post.addFormValue(result, key, new String JavaDoc[] { value }, env.getIniBoolean("magic_quotes_gpc"));
1309    } else {
1310      // If key is an exsiting array, then append this value to existing array
1311
// Only use extract(EXTR_OVERWRITE) on non-array variables or
1312
// non-existing arrays
1313
int openBracketIndex = key.indexOf('[');
1314      int closeBracketIndex = key.indexOf(']');
1315      if (openBracketIndex > 0) {
1316        Value v = env.getVar(key.substring(0,openBracketIndex)).getRawValue();
1317        if (v instanceof ArrayValue) {
1318          //Check to make sure valid string (ie: foo[...])
1319
if (closeBracketIndex < 0) {
1320        env.warning(L.l("invalid array " + key));
1321        return NullValue.NULL;
1322          }
1323          if (closeBracketIndex > openBracketIndex + 1) {
1324        String JavaDoc index = key.substring(key.indexOf('[') + 1,key.indexOf(']'));
1325        v.put(new StringValueImpl(index), new StringValueImpl(value));
1326          } else {
1327        v.put(new StringValueImpl(value));
1328          }
1329        } else {
1330          Post.addFormValue(result, key, new String JavaDoc[] { value }, env.getIniBoolean("magic_quotes_gpc"));
1331        }
1332      } else {
1333        Post.addFormValue(result, key, new String JavaDoc[] { value }, env.getIniBoolean("magic_quotes_gpc"));
1334      }
1335    }
1336      }
1337
1338      if (! isRef) {
1339    ArrayModule.extract(env, result,
1340                ArrayModule.EXTR_OVERWRITE,
1341                null);
1342      }
1343
1344      return NullValue.NULL;
1345    } catch (IOException JavaDoc e) {
1346      throw new QuercusModuleException(e);
1347    }
1348  }
1349
1350  private static int addQueryChar(ByteToChar byteToChar, String JavaDoc str, int len,
1351                                  int i, int ch)
1352    throws IOException JavaDoc
1353  {
1354    if (str == null)
1355      str = "";
1356    
1357    switch (ch) {
1358    case '+':
1359      byteToChar.addChar(' ');
1360      return i;
1361
1362    case '%':
1363      if (i + 2 < len) {
1364        int d1 = hexToDigit(str.charAt(i + 1));
1365        int d2 = hexToDigit(str.charAt(i + 2));
1366
1367        // XXX: d1 and d2 may be -1 if not valid hex chars
1368
byteToChar.addByte(d1 * 16 + d2);
1369
1370        return i + 2;
1371      }
1372      else {
1373        byteToChar.addByte((byte) ch);
1374        return i;
1375      }
1376
1377    default:
1378      byteToChar.addByte((byte) ch);
1379      return i;
1380    }
1381  }
1382
1383  public static void addQueryValue(Env env, ArrayValue array,
1384                                   String JavaDoc key, String JavaDoc valueStr)
1385  {
1386    if (key == null)
1387      key = "";
1388    
1389    if (valueStr == null)
1390      valueStr = "";
1391    
1392    int p;
1393
1394    Value value = new StringValueImpl(valueStr);
1395
1396    if ((p = key.indexOf('[')) > 0 && key.endsWith("]")) {
1397      String JavaDoc index = key.substring(p + 1, key.length() - 1);
1398      key = key.substring(0, p);
1399
1400      Value keyValue = new StringValueImpl(key);
1401
1402      Value part;
1403
1404      if (array != null)
1405        part = array.get(keyValue);
1406      else
1407        part = env.getVar(key);
1408
1409      if (! part.isArray())
1410        part = new ArrayValueImpl();
1411
1412      if (index.equals(""))
1413        part.put(value);
1414      else
1415        part.put(new StringValueImpl(index), value);
1416
1417      if (array != null)
1418        array.put(keyValue, part);
1419      else
1420        env.setVar(key, part);
1421    }
1422    else {
1423      if (array != null)
1424        array.put(new StringValueImpl(key), value);
1425      else
1426        env.setVar(key, value);
1427    }
1428  }
1429
1430  /**
1431   * Prints the string.
1432   *
1433   * @param env the quercus environment
1434   * @param value the string to print
1435   */

1436  public static long print(Env env, Value value)
1437  {
1438    value.print(env);
1439
1440    return 1;
1441  }
1442
1443  /**
1444   * Escapes meta characters.
1445   *
1446   * @param string the string to be quoted
1447   *
1448   * @return the quoted
1449   */

1450  public static Value quotemeta(StringValue string)
1451  {
1452    StringBuilderValue sb = new StringBuilderValue();
1453
1454    int len = string.length();
1455    for (int i = 0; i < len; i++) {
1456      char ch = string.charAt(i);
1457
1458      switch (ch) {
1459      case '.': case '\\': case '+': case '*': case '?':
1460      case '[': case '^': case ']': case '(': case ')': case '$':
1461        sb.append("\\");
1462        sb.append(ch);
1463        break;
1464      default:
1465        sb.append(ch);
1466        break;
1467      }
1468    }
1469
1470    return sb;
1471  }
1472
1473  /**
1474   * Converts a RFC2045 quoted printable string to a string.
1475   */

1476  // XXX: i18n
1477
public static String JavaDoc quoted_printable_decode(String JavaDoc str)
1478  {
1479    if (str == null)
1480      str = "";
1481    
1482    StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
1483
1484    int length = str.length();
1485
1486    for (int i = 0; i < length; i++) {
1487      char ch = str.charAt(i);
1488
1489      if (33 <= ch && ch <= 60)
1490    sb.append(ch);
1491      else if (62 <= ch && ch <= 126)
1492    sb.append(ch);
1493      else if (ch == ' ' || ch == '\t') {
1494    if (i + 1 < str.length() &&
1495        (str.charAt(i + 1) == '\r' || str.charAt(i + 1) == '\n')) {
1496      sb.append('=');
1497      sb.append(toUpperHexChar(ch >> 4));
1498      sb.append(toUpperHexChar(ch));
1499    }
1500    else
1501      sb.append(ch);
1502      }
1503      else if (ch == '\r' || ch == '\n') {
1504    sb.append(ch);
1505      }
1506      else {
1507    sb.append('=');
1508    sb.append(toUpperHexChar(ch >> 4));
1509    sb.append(toUpperHexChar(ch));
1510      }
1511    }
1512
1513    return sb.toString();
1514  }
1515
1516  private static final boolean[]TRIM_WHITESPACE = new boolean[256];
1517
1518  static {
1519    TRIM_WHITESPACE['\0'] = true;
1520    TRIM_WHITESPACE['\b'] = true;
1521    TRIM_WHITESPACE[' '] = true;
1522    TRIM_WHITESPACE['\t'] = true;
1523    TRIM_WHITESPACE['\r'] = true;
1524    TRIM_WHITESPACE['\n'] = true;
1525  }
1526
1527  /**
1528   * Removes leading whitespace.
1529   *
1530   * @param string the string to be trimmed
1531   * @param characters optional set of characters to trim
1532   * @return the trimmed string
1533   */

1534  public static StringValue ltrim(Env env,
1535                             StringValue string,
1536                             @Optional String JavaDoc characters)
1537  {
1538    if (characters == null)
1539      characters = "";
1540
1541    boolean []trim;
1542
1543    if (characters.equals(""))
1544      trim = TRIM_WHITESPACE;
1545    else
1546      trim = parseCharsetBitmap(characters);
1547
1548    for (int i = 0; i < string.length(); i++) {
1549      char ch = string.charAt(i);
1550
1551      if (ch >= 256 || ! trim[ch]) {
1552        if (i == 0)
1553          return string;
1554        else
1555          return string.substring(i);
1556      }
1557    }
1558
1559    return StringValue.EMPTY;
1560  }
1561
1562  /**
1563   * Removes trailing whitespace.
1564   *
1565   * @param env the quercus environment
1566   * @param string the string to be trimmed
1567   * @param characters optional set of characters to trim
1568   * @return the trimmed string
1569   */

1570  public static StringValue rtrim(Env env,
1571                  StringValue string,
1572                  @Optional String JavaDoc characters)
1573  {
1574    if (characters == null)
1575      characters = "";
1576    
1577    boolean []trim;
1578
1579    if (characters.equals(""))
1580      trim = TRIM_WHITESPACE;
1581    else
1582      trim = parseCharsetBitmap(characters);
1583
1584    for (int i = string.length() - 1; i >= 0; i--) {
1585      char ch = string.charAt(i);
1586
1587      if (ch >= 256 || ! trim[ch]) {
1588        if (i == string.length())
1589          return string;
1590        else
1591          return (StringValue) string.subSequence(0, i + 1);
1592      }
1593    }
1594
1595    return StringValue.EMPTY;
1596  }
1597
1598  /**
1599   * Sets locale configuration.
1600   */

1601  public static Value setlocale(Env env,
1602                                int category,
1603                                Value localeArg,
1604                                Value []fallback)
1605  {
1606    LocaleInfo localeInfo = env.getLocaleInfo();
1607
1608    if (localeArg instanceof ArrayValue) {
1609      for (Value value : ((ArrayValue) localeArg).values()) {
1610        Locale JavaDoc locale = setLocale(localeInfo, category, value.toString());
1611
1612        if (locale != null)
1613          return new StringValueImpl(locale.toString());
1614      }
1615    }
1616    else {
1617      Locale JavaDoc locale = setLocale(localeInfo, category, localeArg.toString());
1618
1619      if (locale != null)
1620        return new StringValueImpl(locale.toString());
1621    }
1622
1623    for (int i = 0; i < fallback.length; i++) {
1624      Locale JavaDoc locale = setLocale(localeInfo, category, fallback[i].toString());
1625
1626      if (locale != null)
1627        return new StringValueImpl(locale.toString());
1628    }
1629
1630    return BooleanValue.FALSE;
1631  }
1632
1633  /**
1634   * Sets locale configuration.
1635   */

1636  private static Locale JavaDoc setLocale(LocaleInfo localeInfo,
1637                                  int category,
1638                                  String JavaDoc localeName)
1639  {
1640    String JavaDoc language;
1641    String JavaDoc country;
1642    String JavaDoc variant;
1643
1644    int p = localeName.indexOf('_');
1645    int p1 = localeName.indexOf('-');
1646
1647    if (p1 > 0 && (p1 < p || p < 0))
1648      p = p1;
1649
1650    Locale JavaDoc locale;
1651
1652    if (p > 0) {
1653      language = localeName.substring(0, p);
1654
1655      int q = localeName.indexOf('-', p + 1);
1656      int q1 = localeName.indexOf('.', p + 1);
1657      // XXX: '.' should be charset?
1658

1659      if (q1 > 0 && (q1 < q || q < 0))
1660        q = q1;
1661
1662      q1 = localeName.indexOf('@', p + 1);
1663      // XXX: '@' is ??
1664

1665      if (q1 > 0 && (q1 < q || q < 0))
1666        q = q1;
1667
1668      q1 = localeName.indexOf('_', p + 1);
1669
1670      if (q1 > 0 && (q1 < q || q < 0))
1671        q = q1;
1672
1673      if (q > 0) {
1674        country = localeName.substring(p + 1, q);
1675        variant = localeName.substring(q + 1);
1676
1677        locale = new Locale JavaDoc(language, country, variant);
1678      }
1679      else {
1680        country = localeName.substring(p + 1);
1681
1682        locale = new Locale JavaDoc(language, country);
1683      }
1684    }
1685    else
1686      locale = new Locale JavaDoc(localeName);
1687
1688    if (! isValidLocale(locale))
1689      return null;
1690
1691    switch (category) {
1692    case LC_ALL:
1693      localeInfo.setAll(locale);
1694      return localeInfo.getMessages();
1695    case LC_COLLATE:
1696      localeInfo.setCollate(locale);
1697      return localeInfo.getCollate();
1698    case LC_CTYPE:
1699      localeInfo.setCtype(locale);
1700      return localeInfo.getCtype();
1701    case LC_MONETARY:
1702      localeInfo.setMonetary(locale);
1703      return localeInfo.getMonetary();
1704    case LC_NUMERIC:
1705      localeInfo.setNumeric(locale);
1706      return localeInfo.getNumeric();
1707    case LC_TIME:
1708      localeInfo.setTime(locale);
1709      return localeInfo.getTime();
1710    case LC_MESSAGES:
1711      localeInfo.setMessages(locale);
1712      return localeInfo.getMessages();
1713    default:
1714      return null;
1715    }
1716  }
1717
1718  /**
1719   * Returns true if the locale is supported.
1720   */

1721  private static boolean isValidLocale(Locale JavaDoc locale)
1722  {
1723    Locale JavaDoc []validLocales = Locale.getAvailableLocales();
1724
1725    for (int i = 0; i < validLocales.length; i++) {
1726      if (validLocales[i].equals(locale)) {
1727        return true;
1728      }
1729    }
1730
1731    return false;
1732  }
1733
1734  /**
1735   * returns the md5 hash
1736   *
1737   * @param source the string
1738   * @param rawOutput if true, return the raw binary
1739   *
1740   * @return a string of imploded values
1741   */

1742  public static String JavaDoc sha1(String JavaDoc source,
1743                            @Optional boolean rawOutput)
1744  {
1745    if (source == null)
1746      source = "";
1747    
1748    try {
1749      MessageDigest JavaDoc md = MessageDigest.getInstance("SHA1");
1750      
1751      // XXX: iso-8859-1
1752

1753      for (int i = 0; i < source.length(); i++) {
1754    char ch = source.charAt(i);
1755    
1756    md.update((byte) ch);
1757      }
1758      
1759      byte []digest = md.digest();
1760      
1761      StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
1762      for (int i = 0; i < digest.length; i++) {
1763    int d1 = (digest[i] >> 4) & 0xf;
1764    int d2 = (digest[i] & 0xf);
1765    
1766    sb.append(toHexChar(d1));
1767    sb.append(toHexChar(d2));
1768      }
1769      
1770      return sb.toString();
1771    } catch (Exception JavaDoc e) {
1772      throw new QuercusException(e);
1773    }
1774  }
1775
1776  /**
1777   * returns the md5 hash
1778   *
1779   * @param source the string
1780   * @param rawOutput if true, return the raw binary
1781   *
1782   * @return a string of imploded values
1783   */

1784  public static Value sha1_file(Path source,
1785                                @Optional boolean rawOutput)
1786  {
1787    try {
1788      MessageDigest JavaDoc md = MessageDigest.getInstance("SHA1");
1789      InputStream JavaDoc is = null;
1790      
1791      try {
1792    is = source.openRead();
1793    int d;
1794    
1795    while ((d = is.read()) >= 0) {
1796      md.update((byte) d);
1797    }
1798    
1799    return digestToString(md.digest());
1800      } catch (IOException JavaDoc e) {
1801    log.log(Level.FINE, e.toString(), e);
1802    
1803    return BooleanValue.FALSE;
1804      } finally {
1805    try {
1806      if (is != null)
1807        is.close();
1808    } catch (IOException JavaDoc e) {
1809    }
1810      }
1811    } catch (Exception JavaDoc e) {
1812      throw new QuercusException(e);
1813    }
1814  }
1815
1816  /**
1817   * scans a string
1818   *
1819   * @param format the format string
1820   * @param args the format arguments
1821   *
1822   * @return the formatted string
1823   */

1824  public static Value sscanf(StringValue string,
1825                             StringValue format,
1826                             @Optional Value []args)
1827  {
1828    // quercus/113-
1829

1830    int fmtLen = format.length();
1831    int strlen = string.length();
1832
1833    int sIndex = 0;
1834    int fIndex = 0;
1835
1836    ArrayValue array = new ArrayValueImpl();
1837
1838    while (fIndex < fmtLen && sIndex < strlen) {
1839      char ch = format.charAt(fIndex++);
1840
1841      if (isWhitespace(ch)) {
1842        for (;
1843             (fIndex < fmtLen &&
1844              isWhitespace(ch = format.charAt(fIndex)));
1845             fIndex++) {
1846        }
1847
1848        ch = string.charAt(sIndex);
1849        if (! isWhitespace(ch)) {
1850          return array; // XXX: return false?
1851
}
1852
1853        for (sIndex++;
1854             sIndex < strlen && isWhitespace(string.charAt(sIndex));
1855             sIndex++) {
1856        }
1857      }
1858      else if (ch == '%') {
1859        int maxLen = -1;
1860        boolean suppressAssignment = false;
1861
1862        loop:
1863        while (fIndex < fmtLen) {
1864          ch = format.charAt(fIndex++);
1865
1866          switch (ch) {
1867          case '%':
1868            if (string.charAt(sIndex) != '%')
1869              return array;
1870            else
1871              break loop;
1872
1873          case '0': case '1': case '2': case '3': case '4':
1874          case '5': case '6': case '7': case '8': case '9':
1875            if (maxLen < 0)
1876              maxLen = 0;
1877
1878            maxLen = 10 * maxLen + ch - '0';
1879            break;
1880
1881          case 's':
1882            sIndex = sscanfString(string, sIndex, maxLen, array);
1883            break loop;
1884
1885          default:
1886            log.fine(L.l("'{0}' is a bad sscanf string.", format));
1887            return array;
1888          }
1889        }
1890      }
1891      else if (ch == string.charAt(sIndex)) {
1892        sIndex++;
1893      }
1894      else
1895        return array;
1896    }
1897
1898    return array;
1899  }
1900
1901  /**
1902   * Scans a string with a given length.
1903   */

1904  private static int sscanfString(StringValue string, int sIndex, int maxLen,
1905                                  ArrayValue array)
1906  {
1907    // quercus/1131
1908
int strlen = string.length();
1909
1910    if (maxLen < 0)
1911      maxLen = Integer.MAX_VALUE;
1912
1913    StringBuilderValue sb = new StringBuilderValue();
1914
1915    for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
1916      char ch = string.charAt(sIndex);
1917
1918      if (isWhitespace(ch)) {
1919        array.append(sb);
1920        return sIndex;
1921      }
1922      else
1923        sb.append(ch);
1924    }
1925
1926    array.append(sb);
1927
1928    return sIndex;
1929  }
1930
1931  /**
1932   * print to the output with a formatter
1933   *
1934   * @param env the quercus environment
1935   * @param format the format string
1936   * @param args the format arguments
1937   *
1938   * @return the formatted string
1939   */

1940  public static int printf(Env env, String JavaDoc format, Value []args)
1941  {
1942    Value str = sprintf(format, args);
1943
1944    str.print(env);
1945
1946    return str.length();
1947  }
1948
1949  private static final char[] SOUNDEX_VALUES = "01230120022455012623010202".toCharArray();
1950
1951  public static Value soundex(String JavaDoc string)
1952  {
1953    if (string == null)
1954      string = "";
1955    
1956    int length = string.length();
1957
1958    if (length == 0)
1959      return BooleanValue.FALSE;
1960
1961    StringBuilder JavaDoc result = new StringBuilder JavaDoc(4);
1962
1963    int count = 0;
1964    char lastCode = 0;
1965
1966
1967    for (int i = 0; i < length && count < 4; i++) {
1968      char ch = toUpperCase(string.charAt(i));
1969
1970      if ('A' <= ch && ch <= 'Z') {
1971        char code = SOUNDEX_VALUES[ch - 'A'];
1972
1973        if (count == 0) {
1974          result.append(ch);
1975          count++;
1976        }
1977        else if (code != '0' && code != lastCode) {
1978          result.append(code);
1979          count++;
1980        }
1981
1982        lastCode = code;
1983      }
1984    }
1985
1986    for (; count < 4; count++) {
1987      result.append('0');
1988    }
1989
1990    return new StringValueImpl(result.toString());
1991  }
1992
1993  /**
1994   * Print to a string with a formatter
1995   *
1996   * @param format the format string
1997   * @param args the format arguments
1998   *
1999   * @return the formatted string
2000   */

2001  public static Value sprintf(String JavaDoc format, Value []args)
2002  {
2003    if (format == null)
2004      format = "";
2005    
2006    ArrayList JavaDoc<PrintfSegment> segments = parsePrintfFormat(format);
2007
2008    StringBuilderValue sb = new StringBuilderValue();
2009
2010    for (PrintfSegment segment : segments)
2011      segment.apply(sb, args);
2012
2013    return sb;
2014  }
2015
2016  private static ArrayList JavaDoc<PrintfSegment> parsePrintfFormat(String JavaDoc format)
2017  {
2018    ArrayList JavaDoc<PrintfSegment> segments = new ArrayList JavaDoc<PrintfSegment>();
2019
2020    StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
2021    StringBuilder JavaDoc flags = new StringBuilder JavaDoc();
2022
2023    int length = format.length();
2024    int index = 0;
2025
2026    for (int i = 0; i < length; i++) {
2027      char ch = format.charAt(i);
2028
2029      if (i + 1 < length && ch == '%') {
2030        // The C printf silently ignores invalid flags, so we need to
2031
// remove them if present.
2032

2033        sb.append(ch);
2034
2035        boolean isLeft = false;
2036        boolean isAlt = false;
2037        boolean isZero = false;
2038
2039        flags.setLength(0);
2040
2041        int j = i + 1;
2042
2043        loop:
2044        for (; j < length; j++) {
2045          ch = format.charAt(j);
2046
2047          switch (ch) {
2048          case '-':
2049            isLeft = true;
2050            break;
2051          case '#':
2052            isAlt = true;
2053            break;
2054          case '0':
2055            isZero = true;
2056            flags.append(ch);
2057            break;
2058          case '+': case ' ': case ',': case '(':
2059            flags.append(ch);
2060            break;
2061          default:
2062            break loop;
2063          }
2064        }
2065
2066        int head = j;
2067        loop:
2068        for (; j < length; j++) {
2069          ch = format.charAt(j);
2070
2071          switch (ch) {
2072          case '%':
2073            i = j;
2074            segments.add(new TextPrintfSegment(sb));
2075            sb.setLength(0);
2076            break loop;
2077
2078          case '0': case '1': case '2': case '3': case '4':
2079          case '5': case '6': case '7': case '8': case '9':
2080          case '.': case '$':
2081            break;
2082
2083          case 'b': case 'B':
2084            if (isLeft)
2085              sb.append('-');
2086            if (isAlt)
2087              sb.append('#');
2088            sb.append(format, head, j);
2089            sb.append(ch);
2090            i = j;
2091            break loop;
2092
2093          case 's': case 'S':
2094            sb.setLength(sb.length() - 1);
2095            segments.add(new StringPrintfSegment(sb,
2096                                                 isLeft || isAlt,
2097                                                 isZero,
2098                                                 ch == 'S',
2099                                                 format.substring(head, j),
2100                                                 index++));
2101            sb.setLength(0);
2102            i = j;
2103            break loop;
2104
2105          case 'c': case 'C':
2106            sb.setLength(sb.length() - 1);
2107            segments.add(new CharPrintfSegment(sb,
2108                                               isLeft || isAlt,
2109                                               isZero,
2110                                               ch == 'C',
2111                                               format.substring(head, j),
2112                                               index++));
2113            sb.setLength(0);
2114            i = j;
2115            break loop;
2116
2117      case 'i': case 'u':
2118        ch = 'd';
2119          case 'd': case 'x': case 'o': case 'X':
2120            if (isLeft)
2121              sb.append('-');
2122            if (isAlt)
2123              sb.append('#');
2124            sb.append(flags);
2125            sb.append(format, head, j);
2126            sb.append(ch);
2127
2128            segments.add(new LongPrintfSegment(sb.toString(), index++));
2129            sb.setLength(0);
2130            i = j;
2131            break loop;
2132
2133          case 'e': case 'E': case 'f': case 'g': case 'G':
2134            if (isLeft)
2135              sb.append('-');
2136            if (isAlt)
2137              sb.append('#');
2138            sb.append(flags);
2139            sb.append(format, head, j);
2140            sb.append(ch);
2141
2142            segments.add(new DoublePrintfSegment(sb.toString(), index++));
2143            sb.setLength(0);
2144            i = j;
2145            break loop;
2146
2147          default:
2148            if (isLeft)
2149              sb.append('-');
2150            if (isAlt)
2151              sb.append('#');
2152            sb.append(flags);
2153            sb.append(format, head, j);
2154            sb.append(ch);
2155            i = j;
2156            break loop;
2157          }
2158        }
2159      } else
2160        sb.append(ch);
2161    }
2162
2163    if (sb.length() > 0)
2164      segments.add(new TextPrintfSegment(sb));
2165
2166    return segments;
2167  }
2168
2169  /**
2170   * replaces substrings.
2171   *
2172   * @param search search string
2173   * @param replace replacement string
2174   * @param subject replacement
2175   * @param count return value
2176   */

2177  public static Value str_ireplace(Env env,
2178                                  Value search,
2179                                  Value replace,
2180                                  Value subject,
2181                                  @Reference @Optional Value count)
2182  {
2183    return strReplace(env, search, replace, subject, count, true);
2184  }
2185
2186  /**
2187   * Pads strings
2188   *
2189   * @param string string
2190   * @param length length
2191   * @param pad padding string
2192   * @param type padding type
2193   */

2194  public static StringValue str_pad(StringValue string,
2195                    int length,
2196                    @Optional("' '") String JavaDoc pad,
2197                    @Optional("STR_PAD_RIGHT") int type)
2198  {
2199    int strLen = string.length();
2200    int padLen = length - strLen;
2201
2202    if (padLen <= 0)
2203      return string;
2204
2205    if (pad == null || pad.length() == 0)
2206      pad = " ";
2207
2208    int leftPad = 0;
2209    int rightPad = 0;
2210
2211    switch (type) {
2212    case STR_PAD_LEFT:
2213      leftPad = padLen;
2214      break;
2215    case STR_PAD_RIGHT:
2216    default:
2217      rightPad = padLen;
2218      break;
2219    case STR_PAD_BOTH:
2220      leftPad = padLen / 2;
2221      rightPad = padLen - leftPad;
2222      break;
2223    }
2224
2225    StringBuilderValue sb = new StringBuilderValue();
2226
2227    int padStringLen = pad.length();
2228
2229    for (int i = 0; i < leftPad; i++)
2230      sb.append(pad.charAt(i % padStringLen));
2231
2232    sb.append(string);
2233
2234    for (int i = 0; i < rightPad; i++)
2235      sb.append(pad.charAt(i % padStringLen));
2236
2237    return sb;
2238  }
2239
2240  /**
2241   * repeats a string
2242   *
2243   * @param string string to repeat
2244   * @param count number of times to repeat
2245   */

2246  public static Value str_repeat(StringValue string, int count)
2247  {
2248    StringBuilderValue sb = new StringBuilderValue();
2249
2250    for (int i = 0; i < count; i++)
2251      sb.append(string);
2252
2253    return sb;
2254  }
2255
2256  /**
2257   * replaces substrings.
2258   *
2259   * @param search search string
2260   * @param replace replacement string
2261   * @param subject replacement
2262   * @param count return value
2263   */

2264  public static Value str_replace(Env env,
2265                                  Value search,
2266                                  Value replace,
2267                                  Value subject,
2268                                  @Reference @Optional Value count)
2269  {
2270    return strReplace(env, search, replace, subject, count, false);
2271  }
2272
2273  /**
2274   * replaces substrings.
2275   *
2276   * @param search search string
2277   * @param replace replacement string
2278   * @param subject replacement
2279   * @param count return value
2280   */

2281  private static Value strReplace(Env env,
2282                  Value search,
2283                  Value replace,
2284                  Value subject,
2285                  @Reference @Optional Value count,
2286                  boolean isInsensitive)
2287  {
2288    count.set(LongValue.ZERO);
2289
2290    if (subject.isNull())
2291      return StringValue.EMPTY;
2292
2293    if (search.isNull())
2294      return subject;
2295
2296    if (subject instanceof ArrayValue) {
2297      ArrayValue subjectArray = (ArrayValue) subject;
2298      ArrayValue resultArray = new ArrayValueImpl();
2299
2300      for (Value value : subjectArray.values()) {
2301        Value result = strReplaceImpl(env,
2302                      search,
2303                      replace,
2304                      value.toStringValue(),
2305                      count,
2306                      isInsensitive);
2307
2308        resultArray.append(result);
2309      }
2310
2311      return resultArray;
2312    }
2313    else {
2314      StringValue subjectString = subject.toStringValue();
2315
2316      if (subjectString.length() == 0)
2317        return StringValue.EMPTY;
2318
2319      return strReplaceImpl(env,
2320                search,
2321                replace,
2322                subjectString,
2323                count,
2324                isInsensitive);
2325    }
2326  }
2327
2328  /**
2329   * replaces substrings.
2330   *
2331   * @param search search string
2332   * @param replace replacement string
2333   * @param subject replacement
2334   * @param count return value
2335   */

2336  private static Value strReplaceImpl(Env env,
2337                      Value search,
2338                      Value replace,
2339                      StringValue subject,
2340                      Value count,
2341                      boolean isInsensitive)
2342  {
2343    if (! search.isArray()) {
2344      StringValue searchString = search.toStringValue();
2345
2346      if (searchString.length() == 0)
2347        return subject;
2348
2349      if (replace instanceof ArrayValue) {
2350        env.warning(L.l("Array to string conversion"));
2351      }
2352
2353      subject = strReplaceImpl(env,
2354                   searchString,
2355                   replace.toStringValue(),
2356                   subject,
2357                   count,
2358                   isInsensitive);
2359    }
2360    else if (replace instanceof ArrayValue) {
2361      ArrayValue searchArray = (ArrayValue) search;
2362      ArrayValue replaceArray = (ArrayValue) replace;
2363
2364      Iterator JavaDoc<Value> searchIter = searchArray.values().iterator();
2365      Iterator JavaDoc<Value> replaceIter = replaceArray.values().iterator();
2366
2367      while (searchIter.hasNext()) {
2368        Value searchItem = searchIter.next();
2369        Value replaceItem = replaceIter.next();
2370
2371        if (replaceItem == null)
2372          replaceItem = NullValue.NULL;
2373
2374        subject = strReplaceImpl(env,
2375                 searchItem.toStringValue(),
2376                 replaceItem.toStringValue(),
2377                 subject,
2378                 count,
2379                 isInsensitive);
2380      }
2381    }
2382    else {
2383      ArrayValue searchArray = (ArrayValue) search;
2384
2385      Iterator JavaDoc<Value> searchIter = searchArray.values().iterator();
2386
2387      while (searchIter.hasNext()) {
2388        Value searchItem = searchIter.next();
2389
2390        subject = strReplaceImpl(env,
2391                 searchItem.toStringValue(),
2392                 replace.toStringValue(),
2393                 subject,
2394                 count,
2395                 isInsensitive);
2396      }
2397    }
2398
2399    return subject;
2400  }
2401
2402  /**
2403   * replaces substrings.
2404   *
2405   * @param search search string
2406   * @param replace replacement string
2407   * @param subject replacement
2408   * @param countV return value
2409   */

2410  private static StringValue strReplaceImpl(Env env,
2411                        StringValue search,
2412                        StringValue replace,
2413                        StringValue subject,
2414                        Value countV,
2415                        boolean isInsensitive)
2416  {
2417    long count = countV.toLong();
2418
2419    int head = 0;
2420    int next;
2421
2422    int searchLen = search.length();
2423
2424    StringBuilderValue result = null;
2425
2426    while ((next = indexOf(subject, search, head, isInsensitive)) >= head) {
2427      if (result == null)
2428    result = new StringBuilderValue();
2429    
2430      result.append(subject, head, next);
2431      result.append(replace);
2432
2433      if (head < next + searchLen)
2434        head = next + searchLen;
2435      else
2436        head += 1;
2437
2438      count++;
2439    }
2440
2441    if (count != 0) {
2442      countV.set(LongValue.create(count));
2443
2444      if (head > 0 && head < subject.length())
2445        result.append(subject, head, subject.length());
2446
2447      return result;
2448    }
2449    else
2450      return subject;
2451  }
2452
2453  /**
2454   * Returns the next index.
2455   */

2456  private static int indexOf(StringValue subject,
2457                 StringValue match,
2458                 int head,
2459                 boolean isInsensitive)
2460  {
2461    if (! isInsensitive)
2462      return subject.indexOf(match, head);
2463    else {
2464      int length = subject.length();
2465      int matchLen = match.length();
2466
2467      if (matchLen <= 0)
2468    return -1;
2469
2470      char ch = Character.toLowerCase(match.charAt(0));
2471      loop:
2472      for (; head + matchLen <= length; head++) {
2473    if (ch == Character.toLowerCase(subject.charAt(head))) {
2474      for (int i = 1; i < matchLen; i++) {
2475        if (Character.toLowerCase(subject.charAt(head + i)) !=
2476        Character.toLowerCase(match.charAt(i)))
2477          continue loop;
2478      }
2479
2480      return head;
2481    }
2482      }
2483
2484      return -1;
2485    }
2486  }
2487
2488  /**
2489   * rot13 conversion
2490   *
2491   * @param string string to convert
2492   */

2493  public static Value str_rot13(String JavaDoc string)
2494  {
2495    if (string == null)
2496      string = "";
2497    
2498    StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
2499
2500    int len = string.length();
2501    for (int i = 0; i < len; i++) {
2502      char ch = string.charAt(i);
2503
2504      if ('a' <= ch && ch <= 'z') {
2505        int off = ch - 'a';
2506
2507        sb.append((char) ('a' + (off + 13) % 26));
2508      }
2509      else if ('A' <= ch && ch <= 'Z') {
2510        int off = ch - 'A';
2511
2512        sb.append((char) ('A' + (off + 13) % 26));
2513      }
2514      else {
2515        sb.append(ch);
2516      }
2517    }
2518
2519    return new StringValueImpl(sb.toString());
2520  }
2521
2522  /**
2523   * shuffles a string
2524   */

2525  public static String JavaDoc str_shuffle(String JavaDoc string)
2526  {
2527    if (string == null)
2528      string = "";
2529    
2530    char []chars = string.toCharArray();
2531
2532    int length = chars.length;
2533
2534    for (int i = 0; i < length; i++) {
2535      int rand = RandomUtil.nextInt(length);
2536
2537      char temp = chars[rand];
2538      chars[rand] = chars[i];
2539      chars[i] = temp;
2540    }
2541
2542    return new String JavaDoc(chars);
2543  }
2544
2545  /**
2546   * split into an array
2547   *
2548   * @param string string to split
2549   * @param chunk chunk size
2550   */

2551  public static Value str_split(String JavaDoc string,
2552                                @Optional("1") int chunk)
2553  {
2554    if (string == null)
2555      string = "";
2556    
2557    ArrayValue array = new ArrayValueImpl();
2558
2559    int strLen = string.length();
2560
2561    for (int i = 0; i < strLen; i += chunk) {
2562      Value value;
2563
2564      if (i + chunk <= strLen) {
2565        value = new StringValueImpl(string.substring(i, i + chunk));
2566      } else {
2567        value = new StringValueImpl(string.substring(i));
2568      }
2569
2570      array.put(new LongValue(i), value);
2571    }
2572
2573    return array;
2574  }
2575
2576  public static Value str_word_count(String JavaDoc string,
2577                                     @Optional int format,
2578                                     @Optional String JavaDoc additionalWordCharacters)
2579  {
2580    if (string == null)
2581      string = "";
2582    
2583    if (format < 0 || format > 2)
2584      return NullValue.NULL;
2585
2586    int strlen = string.length();
2587    boolean isAdditionalWordCharacters = additionalWordCharacters.length() > 0;
2588
2589    ArrayValueImpl resultArray = null;
2590
2591    if (format > 0)
2592      resultArray = new ArrayValueImpl();
2593
2594    boolean isBetweenWords = true;
2595
2596    int wordCount = 0;
2597
2598    int lastWordStart = 0;
2599
2600    for (int i = 0; i <= strlen; i++) {
2601      boolean isWordCharacter;
2602
2603      if (i < strlen) {
2604        int ch = string.charAt(i);
2605
2606        isWordCharacter = Character.isLetter(ch)
2607                          || ch == '-'
2608                          || ch == '\''
2609                          || (isAdditionalWordCharacters
2610                              && additionalWordCharacters.indexOf(ch) > -1);
2611      }
2612      else
2613        isWordCharacter = false;
2614
2615      if (isWordCharacter) {
2616        if (isBetweenWords) {
2617          // starting a word
2618
isBetweenWords = false;
2619
2620          lastWordStart = i;
2621          wordCount++;
2622        }
2623      }
2624      else {
2625        if (!isBetweenWords) {
2626          // finished a word
2627
isBetweenWords = true;
2628
2629          if (format > 0) {
2630            StringValue word = new StringValueImpl(string.substring(lastWordStart, i));
2631
2632            if (format == 1)
2633              resultArray.append(word);
2634            else if (format == 2)
2635              resultArray.put(new LongValue(lastWordStart), word);
2636          }
2637        }
2638      }
2639    }
2640
2641    if (resultArray == null)
2642      return LongValue.create(wordCount);
2643    else
2644      return resultArray;
2645  }
2646
2647  /**
2648   * Case-insensitive comparison
2649   *
2650   * @param a left value
2651   * @param b right value
2652   * @return -1, 0, or 1
2653   */

2654  public static int strcasecmp(StringValue a, StringValue b)
2655  {
2656    int aLen = a.length();
2657    int bLen = b.length();
2658
2659    for (int i = 0; i < aLen && i < bLen; i++) {
2660      char chA = a.charAt(i);
2661      char chB = b.charAt(i);
2662
2663      if (chA == chB)
2664        continue;
2665
2666      if (Character.isUpperCase(chA))
2667        chA = Character.toLowerCase(chA);
2668
2669      if (Character.isUpperCase(chB))
2670        chB = Character.toLowerCase(chB);
2671
2672      if (chA == chB)
2673        continue;
2674      else if (chA < chB)
2675        return -1;
2676      else
2677        return 1;
2678    }
2679
2680    if (aLen == bLen)
2681      return 0;
2682    else if (aLen < bLen)
2683      return -1;
2684    else
2685      return 1;
2686  }
2687
2688  /**
2689   * Case-sensitive comparison
2690   *
2691   * @param a left value
2692   * @param b right value
2693   * @return -1, 0, or 1
2694   */

2695  public static int strcmp(String JavaDoc a, String JavaDoc b)
2696  {
2697    if (a == null)
2698      a = "";
2699    
2700    if (b == null)
2701      b = "";
2702    
2703    int cmp = a.compareTo(b);
2704
2705    if (cmp == 0)
2706      return 0;
2707    else if (cmp < 0)
2708      return -1;
2709    else
2710      return 1;
2711  }
2712
2713  /**
2714   * Finds the index of a substring
2715   *
2716   * @param env the calling environment
2717   */

2718  public static Value strchr(Env env, String JavaDoc haystack, Value needle)
2719  {
2720    return strstr(env, haystack, needle);
2721  }
2722
2723  /**
2724   * Locale-based comparison
2725   * XXX: i18n
2726   *
2727   * @param a left value
2728   * @param b right value
2729   * @return -1, 0, or 1
2730   */

2731  public static Value strcoll(String JavaDoc a, String JavaDoc b)
2732  {
2733    if (a == null)
2734      a = "";
2735    
2736    if (b == null)
2737      b = "";
2738    
2739    int cmp = a.compareTo(b);
2740
2741    if (cmp == 0)
2742      return LongValue.ZERO;
2743    else if (cmp < 0)
2744      return LongValue.MINUS_ONE;
2745    else
2746      return LongValue.ONE;
2747  }
2748
2749  /**
2750   * Finds the number of initial characters in <i>string</i> that do not match
2751   * one of the characters in <i>characters</i>
2752   *
2753   * @param string the string to search in
2754   * @param characters the character set
2755   * @param offset the starting offset
2756   * @param length the length
2757   *
2758   * @return the length of the match or FALSE if the offset or length are invalid
2759   */

2760  public static Value strcspn(StringValue string,
2761                              StringValue characters,
2762                              @Optional("0") int offset,
2763                              @Optional("-2147483648") int length)
2764  {
2765    return strspnImpl(string, characters, offset, length, false);
2766  }
2767
2768
2769  /**
2770   * Removes tags from a string.
2771   *
2772   * @param string the string to remove
2773   * @param allowTags the allowable tags
2774   */

2775  public static StringValue strip_tags(StringValue string,
2776                       @Optional String JavaDoc allowTags)
2777  {
2778    // XXX: allowTags is stubbed
2779

2780    StringBuilderValue result = new StringBuilderValue();
2781
2782    int len = string.length();
2783
2784    for (int i = 0; i < len; i++) {
2785      char ch = string.charAt(i);
2786
2787      if (ch != '<') {
2788        result.append(ch);
2789        continue;
2790      }
2791
2792      for (i++; i < len; i++) {
2793        ch = string.charAt(i);
2794
2795        if (ch == '>')
2796          break;
2797      }
2798    }
2799
2800    return result;
2801  }
2802
2803  /**
2804   * Returns the length of a string.
2805   *
2806   * @param value the argument value
2807   */

2808  public static long strlen(Value value)
2809  {
2810    return value.length();
2811  }
2812
2813  /**
2814   * Case-insensitive comparison
2815   *
2816   * @param a left value
2817   * @param b right value
2818   * @return -1, 0, or 1
2819   */

2820  public static int strnatcasecmp(UnicodeValue a, UnicodeValue b)
2821  {
2822    return naturalOrderCompare(a, b, true);
2823  }
2824  
2825  /**
2826   * Case-sensitive comparison
2827   *
2828   * @param a left value
2829   * @param b right value
2830   * @return -1, 0, or 1
2831   */

2832  public static int strnatcmp(UnicodeValue a, UnicodeValue b)
2833  {
2834    return naturalOrderCompare(a, b, false);
2835  }
2836
2837  /**
2838   * http://sourcefrog.net/projects/natsort/
2839   */

2840  private static int naturalOrderCompare(UnicodeValue a,
2841                                         UnicodeValue b,
2842                                         boolean ignoreCase)
2843  {
2844    SimpleStringReader aIn = new SimpleStringReader(a);
2845    SimpleStringReader bIn = new SimpleStringReader(b);
2846    
2847    int aChar = aIn.read();
2848    int bChar = bIn.read();
2849    
2850    if (aChar == -1 && bChar >= 0)
2851      return -1;
2852    else if (aChar >= 0 && bChar == -1)
2853      return 1;
2854
2855    while (true) {
2856      while (Character.isWhitespace(aChar)) {
2857        aChar = aIn.read();
2858      }
2859
2860      while (Character.isWhitespace(bChar)) {
2861        bChar = bIn.read();
2862      }
2863
2864      if (aChar == -1 && bChar == -1) {
2865        return 0;
2866      }
2867
2868      // leading zeros
2869
// '01' < '2'
2870
// '0a' > 'a'
2871
if (aChar == '0' && bChar == '0') {
2872        while (true) {
2873          aChar = aIn.read();
2874          bChar = bIn.read();
2875            
2876          if (aChar == '0' && bChar == '0') {
2877            continue;
2878          }
2879          else if (aChar == '0') {
2880            if ('1' <= bChar && bChar <= '9')
2881              return -1;
2882            else
2883              return 1;
2884          }
2885          else if (bChar == 0) {
2886            if ('1' <= aChar && aChar <= '9')
2887              return 1;
2888            else
2889              return -1;
2890          }
2891          else {
2892            break;
2893          }
2894        }
2895      }
2896      else if ('0' < aChar && aChar <= '9' &&
2897               '0' < bChar && bChar <= '9')
2898      {
2899        int aInteger = aIn.readInt(aChar);
2900        int bInteger = bIn.readInt(bChar);
2901        
2902        if (aInteger > bInteger)
2903          return 1;
2904        else if (aInteger < bInteger)
2905          return -1;
2906        else {
2907          aChar = aIn.read();
2908          bChar = bIn.read();
2909        }
2910      }
2911
2912      if (ignoreCase) {
2913        aChar = Character.toUpperCase(aChar);
2914        bChar = Character.toUpperCase(bChar);
2915      }
2916
2917      if (aChar > bChar)
2918        return 1;
2919      else if (aChar < bChar)
2920        return -1;
2921
2922      aChar = aIn.read();
2923      bChar = bIn.read();
2924
2925      // trailing spaces
2926
// "abc " > "abc"
2927
if (aChar >= 0 && bChar == -1)
2928        return 1;
2929      else if (aChar == -1 && bChar >= 0)
2930        return -1;
2931    }
2932  }
2933
2934  /**
2935   * Case-insensitive comparison
2936   *
2937   * @param a left value
2938   * @param b right value
2939   * @return -1, 0, or 1
2940   */

2941  public static int strncasecmp(StringValue a, StringValue b, int length)
2942  {
2943    int aLen = a.length();
2944    int bLen = b.length();
2945
2946    for (int i = 0; i < length; i++) {
2947      if (aLen <= i)
2948    return -1;
2949      else if (bLen <= i)
2950    return 1;
2951
2952      char aChar = Character.toUpperCase(a.charAt(i));
2953      char bChar = Character.toUpperCase(b.charAt(i));
2954
2955      if (aChar < bChar)
2956    return -1;
2957      else if (bChar < aChar)
2958    return 1;
2959    }
2960
2961    return 0;
2962  }
2963
2964  /**
2965   * Case-sensitive comparison
2966   *
2967   * @param a left value
2968   * @param b right value
2969   * @return -1, 0, or 1
2970   */

2971  public static int strncmp(String JavaDoc a, String JavaDoc b, int length)
2972  {
2973    if (a == null)
2974      a = "";
2975    
2976    if (b == null)
2977      b = "";
2978    
2979    if (length < a.length())
2980      a = a.substring(0, length);
2981
2982    if (length < b.length())
2983      b = b.substring(0, length);
2984
2985    int cmp = a.compareTo(b);
2986
2987    if (cmp == 0)
2988      return 0;
2989    else if (cmp < 0)
2990      return -1;
2991    else
2992      return 1;
2993  }
2994
2995  /**
2996   * Returns a substring of <i>haystack</i> starting from the earliest
2997   * occurence of any char in <i>charList</i>
2998   *
2999   * @param haystack the string to search in
3000   * @param charList list of chars that would trigger match
3001   * @return substring, else FALSE
3002   */

3003  public static Value strpbrk(StringValue haystack,
3004                              StringValue charList)
3005  {
3006    int len = haystack.length();
3007    int sublen = charList.length();
3008    
3009    for (int i = 0; i < len; i++) {
3010      for (int j = 0; j < sublen; j++) {
3011        if (haystack.charAt(i) == charList.charAt(j))
3012          return haystack.substring(i);
3013      }
3014    }
3015
3016    return BooleanValue.FALSE;
3017  }
3018
3019  /**
3020   * Returns the position of a substring.
3021   *
3022   * @param haystack the string to search in
3023   * @param needleV the string to search for
3024   */

3025  public static Value strpos(StringValue haystack,
3026                             Value needleV,
3027                             @Optional int offset)
3028  {
3029    StringValue needle;
3030
3031    if (needleV instanceof StringValue)
3032      needle = (StringValue) needleV;
3033    else
3034      needle = StringValue.create((char) needleV.toInt());
3035
3036    int pos = haystack.indexOf(needle, offset);
3037
3038    if (pos < 0)
3039      return BooleanValue.FALSE;
3040    else
3041      return LongValue.create(pos);
3042  }
3043
3044  /**
3045   * Returns the position of a substring, testing case insensitive.
3046   *
3047   * @param haystack the full argument to check
3048   * @param needleV the substring argument to check
3049   * @param offsetV optional starting position
3050   */

3051  public static Value stripos(StringValue haystack,
3052                  Value needleV,
3053                  @Optional int offset)
3054  {
3055    StringValue needle;
3056
3057    if (needleV instanceof StringValue)
3058      needle = (StringValue) needleV;
3059    else
3060      needle = StringValue.create((char) needleV.toInt());
3061
3062    haystack = haystack.toLowerCase();
3063    needle = needle.toLowerCase();
3064
3065    int pos = haystack.indexOf(needle, offset);
3066
3067    if (pos < 0)
3068      return BooleanValue.FALSE;
3069    else
3070      return LongValue.create(pos);
3071  }
3072
3073  /**
3074   * Strip out the backslashes, recognizing the escape sequences, octal,
3075   * and hexadecimal representations.
3076   *
3077   * @param source the string to clean
3078   * @see #addcslashes
3079   */

3080  public static String JavaDoc stripcslashes(String JavaDoc source)
3081  {
3082    if (source == null)
3083      source = "";
3084    
3085    StringBuilder JavaDoc result = new StringBuilder JavaDoc(source.length());
3086
3087    int length = source.length();
3088
3089    for (int i = 0; i < length; i++) {
3090      int ch = source.charAt(i);
3091
3092      if (ch == '\\') {
3093        i++;
3094
3095        if (i == length)
3096          ch = '\\';
3097        else {
3098          ch = source.charAt(i);
3099
3100          switch (ch) {
3101          case 'a':
3102            ch = 0x07;
3103            break;
3104          case 'b':
3105            ch = '\b';
3106            break;
3107          case 't':
3108            ch = '\t';
3109            break;
3110          case 'n':
3111            ch = '\n';
3112            break;
3113          case 'v':
3114            ch = 0xb;
3115            break;
3116          case 'f':
3117            ch = '\f';
3118            break;
3119          case 'r':
3120            ch = '\r';
3121            break;
3122          case 'x':
3123            // up to two digits for a hex number
3124
if (i + 1 == length)
3125              break;
3126
3127            int digitValue = hexToDigit(source.charAt(i + 1));
3128
3129            if (digitValue < 0)
3130              break;
3131
3132            ch = digitValue;
3133            i++;
3134
3135            if (i + 1 == length)
3136              break;
3137
3138            digitValue = hexToDigit(source.charAt(i + 1));
3139
3140            if (digitValue < 0)
3141              break;
3142
3143            ch = ((ch << 4) | digitValue);
3144            i++;
3145
3146            break;
3147          default:
3148            // up to three digits from 0 to 7 for an octal number
3149
digitValue = octToDigit((char) ch);
3150
3151            if (digitValue < 0)
3152              break;
3153
3154            ch = digitValue;
3155
3156            if (i + 1 == length)
3157              break;
3158
3159            digitValue = octToDigit(source.charAt(i + 1));
3160
3161            if (digitValue < 0)
3162              break;
3163
3164            ch = ((ch << 3) | digitValue);
3165            i++;
3166
3167            if (i + 1 == length)
3168              break;
3169
3170            digitValue = octToDigit(source.charAt(i + 1));
3171
3172            if (digitValue < 0)
3173              break;
3174
3175            ch = ((ch << 3) | digitValue);
3176            i++;
3177          }
3178        }
3179      } // if ch == '/'
3180

3181      result.append((char) ch);
3182    }
3183
3184    return result.toString();
3185  }
3186
3187  /**
3188   * Strips out the backslashes.
3189   *
3190   * @param string the string to clean
3191   */

3192  public static StringValue stripslashes(StringValue string)
3193  {
3194    StringBuilderValue sb = new StringBuilderValue();
3195    int len = string.length();
3196
3197    for (int i = 0; i < len; i++) {
3198      char ch = string.charAt(i);
3199
3200      if (ch == '\\' && i + 1 < len) {
3201        sb.append(string.charAt(i + 1));
3202        i++;
3203      }
3204      else
3205        sb.append(ch);
3206    }
3207
3208    return sb;
3209  }
3210
3211  /**
3212   * Finds the first instance of a substring, testing case insensitively
3213   *
3214   * @param haystack the string to search in
3215   * @param needleV the string to search for
3216   * @return the trailing match or FALSE
3217   */

3218  public static Value stristr(String JavaDoc haystack,
3219                              Value needleV)
3220  {
3221    if (haystack == null)
3222      haystack = "";
3223    
3224    String JavaDoc needle;
3225
3226    if (needleV instanceof StringValue) {
3227      needle = needleV.toString();
3228    }
3229    else {
3230      needle = String.valueOf((char) needleV.toLong());
3231    }
3232
3233    String JavaDoc haystackLower = haystack.toLowerCase();
3234    String JavaDoc needleLower = needle.toLowerCase();
3235
3236    int i = haystackLower.indexOf(needleLower);
3237
3238    if (i >= 0)
3239      return new StringValueImpl(haystack.substring(i));
3240    else
3241      return BooleanValue.FALSE;
3242  }
3243
3244  /**
3245   * Finds the last instance of a substring
3246   *
3247   * @param haystack the string to search in
3248   * @param needleV the string to search for
3249   * @return the trailing match or FALSE
3250   */

3251  public static Value strrchr(String JavaDoc haystack,
3252                              Value needleV)
3253  {
3254    if (haystack == null)
3255      haystack = "";
3256    
3257    String JavaDoc needle;
3258
3259    if (needleV instanceof StringValue) {
3260      needle = needleV.toString();
3261    }
3262    else {
3263      needle = String.valueOf((char) needleV.toLong());
3264    }
3265
3266    int i = haystack.lastIndexOf(needle);
3267
3268    if (i > 0)
3269      return new StringValueImpl(haystack.substring(i));
3270    else
3271      return BooleanValue.FALSE;
3272  }
3273
3274  /**
3275   * Reverses a string.
3276   *
3277   */

3278  public static Value strrev(StringValue string)
3279  {
3280    StringBuilderValue sb = new StringBuilderValue();
3281
3282    for (int i = string.length() - 1; i >= 0; i--) {
3283      sb.append(string.charAt(i));
3284    }
3285
3286    return sb;
3287  }
3288
3289  /**
3290   * Returns the position of a substring.
3291   *
3292   * @param haystack the string to search in
3293   * @param needleV the string to search for
3294   */

3295  public static Value strrpos(StringValue haystack,
3296                              Value needleV,
3297                              @Optional Value offsetV)
3298  {
3299    StringValue needle;
3300
3301    if (needleV instanceof StringValue)
3302      needle = needleV.toStringValue();
3303    else
3304      needle = StringValue.create((char) needleV.toInt());
3305
3306    int offset;
3307
3308    if (offsetV instanceof DefaultValue)
3309      offset = haystack.length();
3310    else
3311      offset = offsetV.toInt();
3312
3313    int pos = haystack.lastIndexOf(needle, offset);
3314
3315    if (pos < 0)
3316      return BooleanValue.FALSE;
3317    else
3318      return new LongValue(pos);
3319  }
3320
3321  /**
3322   * Returns the position of a substring, testing case-insensitive.
3323   *
3324   * @param haystack the full string to test
3325   * @param needleV the substring string to test
3326   * @param offsetV the optional offset to start searching
3327   */

3328  public static Value strripos(String JavaDoc haystack,
3329                               Value needleV,
3330                               @Optional Value offsetV)
3331  {
3332    if (haystack == null)
3333      haystack = "";
3334    
3335    String JavaDoc needle;
3336
3337    if (needleV instanceof StringValue)
3338      needle = needleV.toString();
3339    else
3340      needle = String.valueOf((char) needleV.toInt());
3341
3342    int offset;
3343
3344    if (offsetV instanceof DefaultValue)
3345      offset = haystack.length();
3346    else
3347      offset = offsetV.toInt();
3348
3349    haystack = haystack.toLowerCase();
3350    needle = needle.toLowerCase();
3351
3352    int pos = haystack.lastIndexOf(needle, offset);
3353
3354    if (pos < 0)
3355      return BooleanValue.FALSE;
3356    else
3357      return new LongValue(pos);
3358  }
3359
3360  /**
3361   * Finds the number of initial characters in <i>string</i> that match one of
3362   * the characters in <i>characters</i>
3363   *
3364   * @param string the string to search in
3365   * @param characters the character set
3366   * @param offset the starting offset
3367   * @param length the length
3368   *
3369   * @return the length of the match or FALSE if the offset or length are invalid
3370   */

3371  public static Value strspn(StringValue string,
3372                             StringValue characters,
3373                             @Optional int offset,
3374                             @Optional("-2147483648") int length)
3375  {
3376    return strspnImpl(string, characters, offset, length, true);
3377  }
3378
3379  private static Value strspnImpl(StringValue string,
3380                                  StringValue characters,
3381                                  int offset,
3382                                  int length,
3383                                  boolean isMatch)
3384  {
3385    int strlen = string.length();
3386
3387    // see also strcspn which uses the same procedure for determining
3388
// effective offset and length
3389
if (offset < 0) {
3390      offset += strlen;
3391
3392      if (offset < 0)
3393        offset = 0;
3394    }
3395
3396    if (offset > strlen)
3397      return BooleanValue.FALSE;
3398
3399    if (length == -2147483648)
3400      length = strlen;
3401    else if (length < 0) {
3402      length += (strlen - offset);
3403
3404      if (length < 0)
3405        length = 0;
3406    }
3407
3408    int end = offset + length;
3409
3410    if (strlen < end)
3411      end = strlen;
3412
3413    int count = 0;
3414
3415    for (; offset < end; offset++) {
3416      char ch = string.charAt(offset);
3417
3418      boolean isPresent = characters.indexOf(ch) > -1;
3419
3420      if (isPresent == isMatch)
3421        count++;
3422      else
3423        return LongValue.create(count);
3424    }
3425
3426    return LongValue.create(count);
3427  }
3428
3429  /**
3430   * Finds the first instance of a needle in haystack and returns
3431   * the portion of haystack from the beginning of needle to the end of haystack.
3432   *
3433   * @param env the calling environment
3434   * @param haystack the string to search in
3435   * @param needleV the string to search for, or the oridinal value of a character
3436   * @return the trailing match or FALSE if needle is not found
3437   */

3438  public static Value strstr(Env env,
3439                             String JavaDoc haystack,
3440                             Value needleV)
3441  {
3442    if (haystack == null)
3443      haystack = "";
3444    
3445    String JavaDoc needle;
3446
3447    if (needleV instanceof StringValue) {
3448      needle = needleV.toString();
3449    }
3450    else {
3451      needle = String.valueOf((char) needleV.toLong());
3452    }
3453
3454    if (needle.length() == 0) {
3455      env.warning("empty needle");
3456      return BooleanValue.FALSE;
3457    }
3458
3459    int i = haystack.indexOf(needle);
3460
3461    if (i >= 0)
3462      return new StringValueImpl(haystack.substring(i));
3463    else
3464      return BooleanValue.FALSE;
3465  }
3466
3467  /**
3468   * Split a string into tokens using any character in another string as a delimiter.
3469   *
3470   * The first call establishes the string to search and the characters to use as tokens,
3471   * the first token is returned:
3472   * <pre>
3473   * strtok("hello, world", ", ")
3474   * => "hello"
3475   * </pre>
3476   *
3477   * Subsequent calls pass only the token characters, the next token is returned:
3478   * <pre>
3479   * strtok("hello, world", ", ")
3480   * => "hello"
3481   * strtok(", ")
3482   * => "world"
3483   * </pre>
3484   *
3485   * False is returned if there are no more tokens:
3486   * <pre>
3487   * strtok("hello, world", ", ")
3488   * => "hello"
3489   * strtok(", ")
3490   * => "world"
3491   * strtok(", ")
3492   * => false
3493   * </pre>
3494   *
3495   * Calls that pass two arguments reset the search string:
3496   * <pre>
3497   * strtok("hello, world", ", ")
3498   * => "hello"
3499   * strtok("goodbye, world", ", ")
3500   * => "goodbye"
3501   * strtok("world")
3502   * => false
3503   * strtok(", ")
3504   * => false
3505   * </pre>
3506   */

3507  public static Value strtok(Env env, String JavaDoc string1, @Optional Value string2)
3508  {
3509    if (string1 == null)
3510      string1 = "";
3511    
3512    String JavaDoc string;
3513    String JavaDoc characters;
3514    int offset;
3515
3516    if (string2.isNull()) {
3517      String JavaDoc savedString = (String JavaDoc) env.getSpecialValue("caucho.strtok_string");
3518      Integer JavaDoc savedOffset = (Integer JavaDoc) env.getSpecialValue("caucho.strtok_offset");
3519
3520      string = savedString == null ? "" : savedString;
3521      offset = savedOffset == null ? 0 : savedOffset;
3522      characters = string1;
3523    }
3524    else {
3525      string = string1;
3526      offset = 0;
3527      characters = string2.toString();
3528
3529      env.setSpecialValue("caucho.strtok_string", string);
3530    }
3531
3532    int strlen = string.length();
3533
3534    // skip any at beginning
3535
for (; offset < strlen; offset++) {
3536      char ch = string.charAt(offset);
3537
3538      if (characters.indexOf(ch) < 0)
3539        break;
3540    }
3541
3542    Value result;
3543
3544    if (offset == strlen)
3545      result = BooleanValue.FALSE;
3546    else {
3547      int start = offset;
3548
3549      offset++;
3550
3551      // find end
3552
for (; offset < strlen; offset++) {
3553        char ch = string.charAt(offset);
3554
3555        if (characters.indexOf(ch) > -1)
3556          break;
3557      }
3558
3559      result = new StringValueImpl(string.substring(start, offset));
3560    }
3561
3562    env.setSpecialValue("caucho.strtok_offset", offset);
3563
3564    return result;
3565  }
3566
3567  /**
3568   * Converts to lower case.
3569   *
3570   * @param string the input string
3571   */

3572  public static StringValue strtolower(StringValue string)
3573  {
3574    return string.toLowerCase();
3575  }
3576
3577  /**
3578   * Converts to upper case.
3579   *
3580   * @param string the input string
3581   */

3582  public static StringValue strtoupper(StringValue string)
3583  {
3584    return string.toUpperCase();
3585  }
3586
3587  /**
3588   * Translates characters in a string to target values.
3589   *
3590   * @param string the source string
3591   * @param fromV the from characters
3592   * @param to the to character map
3593   */

3594  public static StringValue strtr(Env env,
3595                             StringValue string,
3596                             Value fromV,
3597                             @Optional StringValue to)
3598  {
3599    if (fromV instanceof ArrayValue)
3600      return strtrArray(string, (ArrayValue) fromV);
3601
3602    StringValue from = fromV.toStringValue();
3603
3604    int len = from.length();
3605
3606    if (to.length() < len)
3607      len = to.length();
3608
3609    char []map = new char[256];
3610    for (int i = len - 1; i >= 0; i--)
3611      map[from.charAt(i)] = to.charAt(i);
3612
3613    StringBuilderValue sb = new StringBuilderValue();
3614
3615    len = string.length();
3616    for (int i = 0; i < len; i++) {
3617      char ch = string.charAt(i);
3618
3619      if (map[ch] != 0)
3620        sb.append(map[ch]);
3621      else
3622        sb.append(ch);
3623    }
3624
3625    return sb;
3626  }
3627
3628  /**
3629   * Translates characters in a string to target values.
3630   *
3631   * @param string the source string
3632   * @param map the character map
3633   */

3634  private static StringValue strtrArray(StringValue string, ArrayValue map)
3635  {
3636    int size = map.getSize();
3637
3638    StringValue []from = new StringValue[size];
3639    StringValue []to = new StringValue[size];
3640    int k = 0;
3641
3642    for (Map.Entry JavaDoc<Value,Value> entry : map.entrySet()) {
3643      from[k] = entry.getKey().toStringValue();
3644      to[k] = entry.getValue().toStringValue();
3645
3646      k++;
3647    }
3648
3649    StringBuilderValue result = new StringBuilderValue();
3650    int len = string.length();
3651    int head = 0;
3652
3653    while (head < len) {
3654      int bestHead = len;
3655      int bestI = -1;
3656      int bestLength = 0;
3657
3658      for (int i = 0; i < from.length; i++) {
3659        int p = string.indexOf(from[i], head);
3660
3661        if (p >= 0 && (p < bestHead ||
3662                       p == bestHead && bestLength < from[i].length())) {
3663          bestHead = p;
3664          bestI = i;
3665          bestLength = from[i].length();
3666        }
3667      }
3668
3669      if (head != bestHead)
3670        result.append(string.substring(head, bestHead));
3671
3672      if (bestI >= 0)
3673        result.append(to[bestI]);
3674
3675      head = bestHead + bestLength;
3676    }
3677
3678    return result;
3679  }
3680
3681  /**
3682   * Returns a substring
3683   *
3684   * @param env the calling environment
3685   * @param string the string
3686   * @param start the start offset
3687   * @param lenV the optional length
3688   */

3689  public static Value substr(Env env,
3690                             StringValue string,
3691                             int start,
3692                             @Optional Value lenV)
3693  {
3694    int strLen = string.length();
3695    if (start < 0)
3696      start = strLen + start;
3697
3698    if (start < 0 || strLen < start)
3699      return BooleanValue.FALSE;
3700
3701    if (lenV instanceof DefaultValue) {
3702      return string.substring(start);
3703    }
3704    else {
3705      int len = lenV.toInt();
3706      int end;
3707
3708      if (len < 0)
3709        end = strLen + len;
3710      else
3711        end = start + len;
3712
3713      if (end <= start)
3714        return StringValue.EMPTY;
3715      else if (strLen <= end)
3716        return string.substring(start);
3717      else
3718        return string.substring(start, end);
3719    }
3720  }
3721
3722  public static Value substr_count(Env env,
3723                                   StringValue haystackV,
3724                                   StringValue needleV,
3725                                   @Optional("0") int offset,
3726                                   @Optional("-1") int length)
3727  {
3728    String JavaDoc haystack = haystackV.toString();
3729    
3730    String JavaDoc needle = needleV.toString();
3731    
3732    if (needle.length() == 0) {
3733      env.warning(L.l("empty substr"));
3734      return BooleanValue.FALSE;
3735    }
3736
3737    int haystackLength = haystack.length();
3738
3739    if (offset < 0 || offset > haystackLength) {
3740      env.warning(L.l("offset `{0}' out of range", offset));
3741      return BooleanValue.FALSE;
3742    }
3743
3744    if (length > -1) {
3745      if (offset + length > haystackLength) {
3746        env.warning(L.l("length `{0}' out of range", length));
3747        return BooleanValue.FALSE;
3748      }
3749      else
3750        haystackLength = offset + length;
3751    }
3752
3753    int needleLength = needle.length();
3754
3755    int count = 0;
3756
3757    int end = haystackLength - needleLength + 1;
3758
3759    for (int i = offset; i < end; i++) {
3760      if (haystack.startsWith(needle, i)) {
3761        count++;
3762        i += needleLength;
3763      }
3764    }
3765
3766    return new LongValue(count);
3767  }
3768
3769  /**
3770   * Replaces a substring with a replacement
3771   *
3772   * @param subjectV a string to modify, or an array of strings to modify
3773   * @param replacement the replacement string
3774   * @param startV the start offset
3775   * @param lengthV the optional length
3776   */

3777  public static Value substr_replace(Value subjectV,
3778                                     StringValue replacement,
3779                                     Value startV,
3780                                     @Optional Value lengthV)
3781  {
3782    int start = 0;
3783    int length = Integer.MAX_VALUE / 2;
3784
3785    if ( !(lengthV.isNull() || lengthV.isArray()) )
3786      length = lengthV.toInt();
3787
3788    if ( !(startV.isNull() || startV.isArray()) )
3789      start = startV.toInt();
3790
3791    Iterator JavaDoc<Value> startIterator =
3792      startV.isArray()
3793      ? ((ArrayValue) startV).values().iterator()
3794      : null;
3795
3796    Iterator JavaDoc<Value> lengthIterator =
3797      lengthV.isArray()
3798      ? ((ArrayValue) lengthV).values().iterator()
3799      : null;
3800
3801    if (subjectV.isArray()) {
3802      ArrayValue resultArray = new ArrayValueImpl();
3803
3804      ArrayValue subjectArray = (ArrayValue) subjectV;
3805
3806      for (Value value : subjectArray.values()) {
3807
3808        if (lengthIterator != null && lengthIterator.hasNext())
3809          length = lengthIterator.next().toInt();
3810
3811        if (startIterator != null && startIterator.hasNext())
3812          start = startIterator.next().toInt();
3813
3814        Value result = substrReplaceImpl(value.toStringValue(), replacement, start, length);
3815
3816        resultArray.append(result);
3817      }
3818
3819      return resultArray;
3820    }
3821    else {
3822      if (lengthIterator != null && lengthIterator.hasNext())
3823        length = lengthIterator.next().toInt();
3824
3825      if (startIterator != null && startIterator.hasNext())
3826        start = startIterator.next().toInt();
3827
3828      return substrReplaceImpl(subjectV.toStringValue(), replacement, start, length);
3829    }
3830  }
3831
3832  private static Value substrReplaceImpl(StringValue string,
3833                                         StringValue replacement,
3834                                         int start,
3835                                         int len)
3836  {
3837    int strLen = string.length();
3838
3839    if (start > strLen)
3840      start = strLen;
3841    else if (start < 0)
3842      start = Math.max(strLen + start, 0);
3843
3844    int end;
3845
3846    if (len < 0)
3847      end = Math.max(strLen + len, start);
3848    else
3849      end = Math.min(start + len, strLen);
3850
3851    StringBuilderValue result = new StringBuilderValue();
3852
3853    result.append(string.substring(0, start));
3854    result.append(replacement);
3855    result.append(string.substring(end));
3856    
3857    return result;
3858  }
3859
3860  /**
3861   * Removes leading and trailing whitespace.
3862   *
3863   * @param string the string to be trimmed
3864   * @param characters optional set of characters to trim
3865   * @return the trimmed string
3866   */

3867  public static Value trim(StringValue string, @Optional String JavaDoc characters)
3868  {
3869    boolean []trim;
3870
3871    if (characters == null || characters.equals(""))
3872      trim = TRIM_WHITESPACE;
3873    else
3874      trim = parseCharsetBitmap(characters.toString());
3875
3876    int len = string.length();
3877
3878    int head = 0;
3879    for (; head < len; head++) {
3880      char ch = string.charAt(head);
3881
3882      if (ch >= 256 || ! trim[ch]) {
3883        break;
3884      }
3885    }
3886
3887    int tail = len - 1;
3888    for (; tail >= 0; tail--) {
3889      char ch = string.charAt(tail);
3890
3891      if (ch >= 256 || ! trim[ch]) {
3892        break;
3893      }
3894    }
3895
3896    if (tail < head)
3897      return StringValue.EMPTY;
3898    else {
3899      return (StringValue) string.subSequence(head, tail + 1);
3900    }
3901  }
3902
3903  /**
3904   * Uppercases the first character
3905   *
3906   * @param string the input string
3907   */

3908  public static String JavaDoc ucfirst(String JavaDoc string)
3909  {
3910    if (string == null)
3911      string = "";
3912    
3913    if (string.length() == 0)
3914      return string;
3915
3916    return Character.toUpperCase(string.charAt(0)) + string.substring(1);
3917  }
3918
3919  /**
3920   * Uppercases the first character of each word
3921   *
3922   * @param string the input string
3923   */

3924  public static String JavaDoc ucwords(String JavaDoc string)
3925  {
3926    if (string == null)
3927      string = "";
3928    
3929    int strLen = string.length();
3930
3931    boolean isStart = true;
3932    StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
3933
3934    for (int i = 0; i < strLen; i++) {
3935      char ch = string.charAt(i);
3936
3937      switch (ch) {
3938      case ' ': case '\t': case '\r': case '\n':
3939        isStart = true;
3940        sb.append(ch);
3941        break;
3942      default:
3943        if (isStart)
3944          sb.append(Character.toUpperCase(ch));
3945        else
3946          sb.append(ch);
3947        isStart = false;
3948        break;
3949      }
3950    }
3951
3952    return sb.toString();
3953  }
3954
3955  /**
3956   * Formatted strings with array arguments
3957   *
3958   * @param format the format string
3959   * @param array the arguments to apply to the format string
3960   */

3961  public static int vprintf(Env env,
3962                            String JavaDoc format,
3963                            @NotNull ArrayValue array)
3964  {
3965    Value []args;
3966
3967    if (array != null) {
3968      args = new Value[array.getSize()];
3969      int i = 0;
3970      for (Value value : array.values())
3971        args[i++] = value;
3972    }
3973    else
3974      args = new Value[0];
3975
3976    return printf(env, format, args);
3977  }
3978
3979  /**
3980   * Formatted strings with array arguments
3981   *
3982   * @param format the format string
3983   * @param array the arguments to apply to the format string
3984   */

3985  public static Value vsprintf(String JavaDoc format,
3986                   @NotNull ArrayValue array)
3987  {
3988    Value []args;
3989
3990    if (array != null) {
3991      args = new Value[array.getSize()];
3992      int i = 0;
3993      for (Value value : array.values())
3994        args[i++] = value;
3995    }
3996    else
3997      args = new Value[0];
3998
3999    return sprintf(format, args);
4000  }
4001
4002  /**
4003   * Wraps a string to the given number of characters.
4004   *
4005   * @param string the input string
4006   * @param width the width
4007   * @param breakString the break string
4008   * @param cut if true, break on exact match
4009   */

4010  public static String JavaDoc wordwrap(String JavaDoc string,
4011                                @Optional("75") int width,
4012                                @Optional("'\n'") String JavaDoc breakString,
4013                                @Optional boolean cut)
4014  {
4015    if (string == null)
4016      string = "";
4017    
4018    if (breakString == null)
4019      breakString = "";
4020
4021    int len = string.length();
4022    int head = 0;
4023
4024    StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
4025    while (head + width < len) {
4026      int tail = head + width;
4027
4028      if (! cut) {
4029        for (;
4030             head < tail && ! Character.isWhitespace(string.charAt(tail));
4031             tail--) {
4032        }
4033
4034        if (head == tail)
4035          tail = head + width;
4036      }
4037
4038      if (sb.length() > 0)
4039        sb.append(breakString);
4040
4041      sb.append(string.substring(head, tail));
4042
4043      head = tail;
4044
4045      if (! cut && head < len && Character.isWhitespace(string.charAt(head)))
4046        head++;
4047    }
4048
4049    if (head < len) {
4050      if (sb.length() > 0)
4051        sb.append(breakString);
4052
4053      sb.append(string.substring(head));
4054    }
4055
4056    return sb.toString();
4057  }
4058
4059  /**
4060   * Returns true if the character is a whitespace character.
4061   */

4062  private static boolean isWhitespace(char ch)
4063  {
4064    return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
4065  }
4066
4067  /**
4068   * Returns the uppercase equivalent of the caharacter
4069   */

4070  private static char toUpperCase(char ch)
4071  {
4072    if (ch >= 'a' && ch <= 'z')
4073      return (char) ('A' + (ch - 'a'));
4074    else
4075      return ch;
4076  }
4077
4078  /**
4079   * Converts an integer digit to a uuencoded char.
4080   */

4081  private static char toUUChar(int d)
4082  {
4083    if (d == 0)
4084      return (char) 0x60;
4085    else
4086      return (char) (0x20 + (d & 0x3f));
4087  }
4088
4089  private static char toHexChar(int d)
4090  {
4091    d &= 0xf;
4092    
4093    if (d < 10)
4094      return (char) (d + '0');
4095    else
4096      return (char) (d - 10 + 'a');
4097  }
4098
4099  private static char toUpperHexChar(int d)
4100  {
4101    d &= 0xf;
4102    
4103    if (d < 10)
4104      return (char) (d + '0');
4105    else
4106      return (char) (d - 10 + 'A');
4107  }
4108
4109  private static int hexToDigit(char ch)
4110  {
4111    if ('0' <= ch && ch <= '9')
4112      return ch - '0';
4113    else if ('a' <= ch && ch <= 'f')
4114      return ch - 'a' + 10;
4115    else if ('A' <= ch && ch <= 'F')
4116      return ch - 'A' + 10;
4117    else
4118      return -1;
4119  }
4120
4121  private static int octToDigit(char ch)
4122  {
4123    if ('0' <= ch && ch <= '7')
4124      return ch - '0';
4125    else
4126      return -1;
4127  }
4128
4129  abstract static class PrintfSegment {
4130    abstract public void apply(StringBuilderValue sb, Value []args);
4131    
4132    static boolean hasIndex(String JavaDoc format)
4133    {
4134      return format.indexOf('$') >= 0;
4135    }
4136
4137    static int getIndex(String JavaDoc format)
4138    {
4139      int value = 0;
4140
4141      for (int i = 0; i < format.length(); i++) {
4142    char ch;
4143    
4144    if ('0' <= (ch = format.charAt(i)) && ch <= '9')
4145      value = 10 * value + ch - '0';
4146    else
4147      break;
4148      }
4149
4150      return value - 1;
4151    }
4152
4153    static String JavaDoc getIndexFormat(String JavaDoc format)
4154    {
4155      int p = format.indexOf('$');
4156
4157      return format.substring(p + 1);
4158    }
4159  }
4160
4161  static class TextPrintfSegment extends PrintfSegment {
4162    private final char []_text;
4163
4164    TextPrintfSegment(StringBuilder JavaDoc text)
4165    {
4166      _text = new char[text.length()];
4167
4168      text.getChars(0, _text.length, _text, 0);
4169    }
4170
4171    public void apply(StringBuilderValue sb, Value []args)
4172    {
4173      sb.append(_text, 0, _text.length);
4174    }
4175  }
4176
4177  static class LongPrintfSegment extends PrintfSegment {
4178    private final String JavaDoc _format;
4179    private final int _index;
4180
4181    LongPrintfSegment(String JavaDoc format, int index)
4182    {
4183      if (hasIndex(format)) {
4184    _index = getIndex(format);
4185    _format = getIndexFormat(format);
4186      }
4187      else {
4188    _format = format;
4189    _index = index;
4190      }
4191    }
4192
4193    public void apply(StringBuilderValue sb, Value []args)
4194    {
4195      long value;
4196
4197      if (_index < args.length)
4198        value = args[_index].toLong();
4199      else
4200        value = 0;
4201
4202      sb.append(String.format(_format, value));
4203    }
4204  }
4205
4206  static class DoublePrintfSegment extends PrintfSegment {
4207    private final String JavaDoc _format;
4208    private final int _index;
4209
4210    DoublePrintfSegment(String JavaDoc format, int index)
4211    {
4212      if (hasIndex(format)) {
4213    _index = getIndex(format);
4214    _format = getIndexFormat(format);
4215      }
4216      else {
4217    _format = format;
4218    _index = index;
4219      }
4220    }
4221
4222    public void apply(StringBuilderValue sb, Value []args)
4223    {
4224      double value;
4225
4226      if (_index < args.length)
4227        value = args[_index].toDouble();
4228      else
4229        value = 0;
4230
4231      sb.append(String.format(_format, value));
4232    }
4233  }
4234
4235  static class StringPrintfSegment extends PrintfSegment {
4236    private final char []_prefix;
4237    private final int _min;
4238    private final int _max;
4239    private final boolean _isLeft;
4240    private final boolean _isUpper;
4241    private final char _pad;
4242    protected final int _index;
4243
4244    StringPrintfSegment(StringBuilder JavaDoc prefix,
4245                        boolean isLeft, boolean isZero, boolean isUpper,
4246                        String JavaDoc format, int index)
4247    {
4248      _prefix = new char[prefix.length()];
4249
4250      _isLeft = isLeft;
4251      _isUpper = isUpper;
4252
4253      _pad = isZero ? '0' : ' ';
4254
4255      prefix.getChars(0, _prefix.length, _prefix, 0);
4256      
4257      if (hasIndex(format)) {
4258    index = getIndex(format);
4259    format = getIndexFormat(format);
4260      }
4261
4262      int i = 0;
4263      int len = format.length();
4264
4265      int min = 0;
4266      int max = Integer.MAX_VALUE;
4267      char ch = ' ';
4268
4269      for (; i < len && '0' <= (ch = format.charAt(i)) && ch <= '9'; i++) {
4270        min = 10 * min + ch - '0';
4271      }
4272
4273      if (ch == '.') {
4274        max = 0;
4275
4276        for (i++; i < len && '0' <= (ch = format.charAt(i)) && ch <= '9'; i++) {
4277          max = 10 * max + ch - '0';
4278        }
4279      }
4280
4281      _min = min;
4282      _max = max;
4283
4284      _index = index;
4285    }
4286
4287    public void apply(StringBuilderValue sb, Value []args)
4288    {
4289      sb.append(_prefix, 0, _prefix.length);
4290
4291      String JavaDoc value = toValue(args);
4292
4293      int len = value.length();
4294
4295      if (_max < len) {
4296        value = value.substring(0, _max);
4297        len = _max;
4298      }
4299
4300      if (_isUpper)
4301        value = value.toUpperCase();
4302
4303      if (! _isLeft) {
4304        for (int i = len; i < _min; i++) {
4305          sb.append(_pad);
4306        }
4307      }
4308
4309      sb.append(value);
4310
4311      if (_isLeft) {
4312        for (int i = len; i < _min; i++) {
4313          sb.append(_pad);
4314        }
4315      }
4316    }
4317
4318    String JavaDoc toValue(Value []args)
4319    {
4320      if (_index < args.length)
4321        return args[_index].toString();
4322      else
4323        return "";
4324    }
4325  }
4326
4327  static class CharPrintfSegment extends StringPrintfSegment {
4328    CharPrintfSegment(StringBuilder JavaDoc prefix,
4329                      boolean isLeft, boolean isZero, boolean isUpper,
4330                      String JavaDoc format, int index)
4331    {
4332      super(prefix, isLeft, isZero, isUpper, format, index);
4333    }
4334
4335    String JavaDoc toValue(Value []args)
4336    {
4337      if (args.length <= _index)
4338        return "";
4339
4340      Value v = args[_index];
4341
4342      if (v.isLongConvertible())
4343        return String.valueOf((char) v.toLong());
4344      else
4345        return v.charValueAt(0).toString();
4346    }
4347  }
4348
4349  static class SimpleStringReader {
4350    UnicodeValue _str;
4351
4352    int _length;
4353    int _index;
4354    
4355    SimpleStringReader(UnicodeValue str)
4356    {
4357      _str = str;
4358      _length = str.length();
4359      _index = 0;
4360    }
4361    
4362    int read()
4363    {
4364      if (_index < _length)
4365        return _str.charAt(_index++);
4366      else
4367        return -1;
4368    }
4369    
4370    int peek()
4371    {
4372      if (_index < _length)
4373        return _str.charAt(_index);
4374      else
4375        return -1;
4376        
4377    }
4378
4379    int readInt(int currChar)
4380    {
4381      int number = currChar - '0';
4382      
4383      while (true) {
4384        currChar = peek();
4385        
4386        if ('0' <= currChar && currChar <= '9') {
4387          number = number * 10 + currChar - '0';
4388          _index++;
4389        }
4390        else {
4391          break;
4392        }
4393      }
4394      
4395      return number;
4396    }
4397  }
4398
4399  static {
4400    DEFAULT_DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols JavaDoc();
4401    DEFAULT_DECIMAL_FORMAT_SYMBOLS.setDecimalSeparator('.');
4402    DEFAULT_DECIMAL_FORMAT_SYMBOLS.setGroupingSeparator(',');
4403    DEFAULT_DECIMAL_FORMAT_SYMBOLS.setZeroDigit('0');
4404  }
4405
4406}
4407
4408
Popular Tags