0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: *
0023: * Free Software Foundation, Inc.
0024: * 59 Temple Place, Suite 330
0025: * Boston, MA 02111-1307 USA
0026: *
0027: * @author Scott Ferguson
0028: */
0029:
0030: package com.caucho.quercus.lib.string;
0031:
0032: import com.caucho.quercus.QuercusException;
0033: import com.caucho.quercus.QuercusModuleException;
0034: import com.caucho.quercus.annotation.NotNull;
0035: import com.caucho.quercus.annotation.Optional;
0036: import com.caucho.quercus.annotation.Reference;
0037: import com.caucho.quercus.annotation.UsesSymbolTable;
0038: import com.caucho.quercus.env.*;
0039: import com.caucho.quercus.lib.file.BinaryOutput;
0040: import com.caucho.quercus.lib.file.FileModule;
0041: import com.caucho.quercus.module.AbstractQuercusModule;
0042: import com.caucho.util.L10N;
0043: import com.caucho.util.RandomUtil;
0044: import com.caucho.vfs.ByteToChar;
0045: import com.caucho.vfs.Path;
0046:
0047: import java.io.IOException;
0048: import java.io.InputStream;
0049: import java.security.MessageDigest;
0050: import java.text.DecimalFormat;
0051: import java.text.DecimalFormatSymbols;
0052: import java.text.NumberFormat;
0053: import java.util.ArrayList;
0054: import java.util.Arrays;
0055: import java.util.Comparator;
0056: import java.util.Currency;
0057: import java.util.Iterator;
0058: import java.util.Locale;
0059: import java.util.Map;
0060: import java.util.logging.Level;
0061: import java.util.logging.Logger;
0062: import java.util.zip.CRC32;
0063:
0064: /**
0065: * PHP functions implemented from the string module
0066: */
0067: public class StringModule extends AbstractQuercusModule {
0068: private static final Logger log = Logger
0069: .getLogger(StringModule.class.getName());
0070:
0071: private static final L10N L = new L10N(StringModule.class);
0072:
0073: public static final int CRYPT_SALT_LENGTH = 2;
0074: public static final int CRYPT_STD_DES = 0;
0075: public static final int CRYPT_EXT_DES = 0;
0076: public static final int CRYPT_MD5 = 0;
0077: public static final int CRYPT_BLOWFISH = 0;
0078:
0079: public static final int CHAR_MAX = 1;
0080:
0081: public static final int LC_CTYPE = 1;
0082: public static final int LC_NUMERIC = 2;
0083: public static final int LC_TIME = 3;
0084: public static final int LC_COLLATE = 4;
0085: public static final int LC_MONETARY = 5;
0086: public static final int LC_ALL = 6;
0087: public static final int LC_MESSAGES = 7;
0088:
0089: public static final int STR_PAD_LEFT = 1;
0090: public static final int STR_PAD_RIGHT = 0;
0091: public static final int STR_PAD_BOTH = 2;
0092:
0093: private static final DecimalFormatSymbols DEFAULT_DECIMAL_FORMAT_SYMBOLS;
0094:
0095: /**
0096: * Escapes a string using C syntax.
0097: *
0098: * @see #stripcslashes
0099: *
0100: * @param source the source string to convert
0101: * @param characters the set of characters to convert
0102: * @return the escaped string
0103: */
0104: public static StringValue addcslashes(StringValue source,
0105: String characters) {
0106: if (characters == null)
0107: characters = "";
0108:
0109: boolean[] bitmap = parseCharsetBitmap(characters);
0110:
0111: int length = source.length();
0112:
0113: StringValue sb = source.createStringBuilder(length * 5 / 4);
0114:
0115: for (int i = 0; i < length; i++) {
0116: char ch = source.charAt(i);
0117:
0118: if (ch >= 256 || !bitmap[ch]) {
0119: sb.append(ch);
0120: continue;
0121: }
0122:
0123: switch (ch) {
0124: case 0x07:
0125: sb.append("\\a");
0126: break;
0127: case '\b':
0128: sb.append("\\b");
0129: break;
0130: case '\t':
0131: sb.append("\\t");
0132: break;
0133: case '\n':
0134: sb.append("\\n");
0135: break;
0136: case 0xb:
0137: sb.append("\\v");
0138: break;
0139: case '\f':
0140: sb.append("\\f");
0141: break;
0142: case '\r':
0143: sb.append("\\r");
0144: break;
0145: default:
0146: if (ch < 0x20 || ch >= 0x7f) {
0147: // save as octal
0148: sb.append("\\");
0149: sb.append((char) ('0' + ((ch >> 6) & 7)));
0150: sb.append((char) ('0' + ((ch >> 3) & 7)));
0151: sb.append((char) ('0' + ((ch) & 7)));
0152: break;
0153: } else {
0154: sb.append("\\");
0155: sb.append(ch);
0156: break;
0157: }
0158: }
0159: }
0160:
0161: return sb;
0162: }
0163:
0164: /**
0165: * Parses the cslashes bitmap returning an actual bitmap.
0166: *
0167: * @param charset the bitmap string
0168: * @return the actual bitmap
0169: */
0170: private static boolean[] parseCharsetBitmap(String charset) {
0171: boolean[] bitmap = new boolean[256];
0172:
0173: int length = charset.length();
0174: for (int i = 0; i < length; i++) {
0175: char ch = charset.charAt(i);
0176:
0177: // XXX: the bitmap eventual might need to deal with unicode
0178: if (ch >= 256)
0179: continue;
0180:
0181: bitmap[ch] = true;
0182:
0183: if (length <= i + 3)
0184: continue;
0185:
0186: if (charset.charAt(i + 1) != '.'
0187: || charset.charAt(i + 2) != '.')
0188: continue;
0189:
0190: char last = charset.charAt(i + 3);
0191:
0192: if (last < ch) {
0193: // XXX: exception type
0194: throw new RuntimeException(L.l("Invalid range."));
0195: }
0196:
0197: i += 3;
0198: for (; ch <= last; ch++) {
0199: bitmap[ch] = true;
0200: }
0201:
0202: // XXX: handling of '@'?
0203: }
0204:
0205: return bitmap;
0206: }
0207:
0208: /**
0209: * Escapes a string for db characters.
0210: *
0211: * @param source the source string to convert
0212: * @return the escaped string
0213: */
0214: public static StringValue addslashes(StringValue source) {
0215: StringValue sb = source
0216: .createStringBuilder(source.length() * 5 / 4);
0217:
0218: int length = source.length();
0219: for (int i = 0; i < length; i++) {
0220: char ch = source.charAt(i);
0221:
0222: switch (ch) {
0223: case 0x0:
0224: sb.append("\\0");
0225: break;
0226: case '\'':
0227: sb.append("\\'");
0228: break;
0229: case '\"':
0230: sb.append("\\\"");
0231: break;
0232: case '\\':
0233: sb.append("\\\\");
0234: break;
0235: default:
0236: sb.append(ch);
0237: break;
0238: }
0239: }
0240:
0241: return sb;
0242: }
0243:
0244: /**
0245: * Converts a binary value to a hex value.
0246: */
0247: public static StringValue bin2hex(Env env, InputStream is) {
0248: try {
0249: StringValue sb = env.createUnicodeBuilder();
0250:
0251: int ch;
0252: while ((ch = is.read()) >= 0) {
0253: int d = (ch >> 4) & 0xf;
0254:
0255: if (d < 10)
0256: sb.append((char) (d + '0'));
0257: else
0258: sb.append((char) (d + 'a' - 10));
0259:
0260: d = (ch) & 0xf;
0261:
0262: if (d < 10)
0263: sb.append((char) (d + '0'));
0264: else
0265: sb.append((char) (d + 'a' - 10));
0266: }
0267:
0268: return sb;
0269: } catch (IOException e) {
0270: throw new QuercusModuleException(e);
0271: }
0272: }
0273:
0274: /**
0275: * Alias of rtrim. Removes trailing whitespace.
0276: *
0277: * @param env the quercus environment
0278: * @param str the string to be trimmed
0279: * @param charset optional set of characters to trim
0280: * @return the trimmed string
0281: */
0282: public static StringValue chop(Env env, StringValue str, @Optional
0283: String charset) {
0284: return rtrim(env, str, charset);
0285: }
0286:
0287: /**
0288: * converts a number to its character equivalent
0289: *
0290: * @param value the integer value
0291: *
0292: * @return the string equivalent
0293: */
0294: public static String chr(long value) {
0295: return String.valueOf((char) value);
0296: }
0297:
0298: /**
0299: * Splits a string into chunks
0300: *
0301: * @param body the body string
0302: * @param chunkLen the optional chunk length, defaults to 76
0303: * @param end the optional end value, defaults to "\r\n"
0304: */
0305: public static String chunk_split(String body, @Optional("76")
0306: int chunkLen, @Optional("\"\\r\\n\"")
0307: String end) {
0308: if (body == null)
0309: body = "";
0310:
0311: if (end == null)
0312: end = "";
0313:
0314: if (chunkLen < 1) // XXX: real exn
0315: throw new IllegalArgumentException(L.l("bad value {0}",
0316: chunkLen));
0317:
0318: StringBuilder sb = new StringBuilder();
0319:
0320: int i = 0;
0321:
0322: for (; i + chunkLen <= body.length(); i += chunkLen) {
0323: sb.append(body.substring(i, i + chunkLen));
0324: sb.append(end);
0325: }
0326:
0327: if (i < body.length()) {
0328: sb.append(body.substring(i));
0329: sb.append(end);
0330: }
0331:
0332: return sb.toString();
0333: }
0334:
0335: /**
0336: * Converts from one cyrillic set to another.
0337: *
0338: * This implementation does nothing, because quercus stores strings as
0339: * 16 bit unicode.
0340: */
0341: public static String convert_cyr_string(Env env, String str,
0342: String from, String to) {
0343: env.stub("convert_cyr_string");
0344:
0345: return str;
0346: }
0347:
0348: public static Value convert_uudecode(Env env, String source) {
0349: try {
0350: if (source == null || source.length() == 0)
0351: return BooleanValue.FALSE;
0352:
0353: ByteToChar byteToChar = env.getByteToChar();
0354:
0355: int length = source.length();
0356:
0357: int i = 0;
0358: while (i < length) {
0359: int ch1 = source.charAt(i++);
0360:
0361: if (ch1 == 0x60 || ch1 == 0x20)
0362: break;
0363: else if (ch1 < 0x20 || 0x5f < ch1)
0364: continue;
0365:
0366: int sublen = ch1 - 0x20;
0367:
0368: while (sublen > 0) {
0369: int code;
0370:
0371: code = ((source.charAt(i++) - 0x20) & 0x3f) << 18;
0372: code += ((source.charAt(i++) - 0x20) & 0x3f) << 12;
0373: code += ((source.charAt(i++) - 0x20) & 0x3f) << 6;
0374: code += ((source.charAt(i++) - 0x20) & 0x3f);
0375:
0376: byteToChar.addByte(code >> 16);
0377:
0378: if (sublen > 1)
0379: byteToChar.addByte(code >> 8);
0380:
0381: if (sublen > 2)
0382: byteToChar.addByte(code);
0383:
0384: sublen -= 3;
0385: }
0386: }
0387:
0388: return env.createString(byteToChar.getConvertedString());
0389: } catch (IOException e) {
0390: throw new QuercusModuleException(e);
0391: }
0392: }
0393:
0394: /**
0395: * uuencode a string.
0396: */
0397: public static Value convert_uuencode(StringValue source) {
0398: if (source == null || source.length() == 0)
0399: return BooleanValue.FALSE;
0400:
0401: StringValue result = source.createStringBuilder();
0402:
0403: int i = 0;
0404: int length = source.length();
0405: while (i < length) {
0406: int sublen = length - i;
0407:
0408: if (45 < sublen)
0409: sublen = 45;
0410:
0411: result.append((char) (sublen + 0x20));
0412:
0413: int end = i + sublen;
0414:
0415: while (i < end) {
0416: int code = source.charAt(i++) << 16;
0417:
0418: if (i < length)
0419: code += source.charAt(i++) << 8;
0420:
0421: if (i < length)
0422: code += source.charAt(i++);
0423:
0424: result.append(toUUChar(((code >> 18) & 0x3f)));
0425: result.append(toUUChar(((code >> 12) & 0x3f)));
0426: result.append(toUUChar(((code >> 6) & 0x3f)));
0427: result.append(toUUChar(((code) & 0x3f)));
0428: }
0429:
0430: result.append('\n');
0431: }
0432:
0433: result.append((char) 0x60);
0434: result.append('\n');
0435:
0436: return result;
0437: }
0438:
0439: /**
0440: * Returns an array of information about the characters.
0441: */
0442: public static Value count_chars(StringValue data, @Optional("0")
0443: int mode) {
0444: if (data == null)
0445: data = StringValue.EMPTY;
0446:
0447: int[] count = new int[256];
0448:
0449: int length = data.length();
0450:
0451: for (int i = 0; i < length; i++) {
0452: count[data.charAt(i) & 0xff] += 1;
0453: }
0454:
0455: switch (mode) {
0456: case 0: {
0457: ArrayValue result = new ArrayValueImpl();
0458:
0459: for (int i = 0; i < count.length; i++) {
0460: result.put(LongValue.create(i), LongValue
0461: .create(count[i]));
0462: }
0463:
0464: return result;
0465: }
0466:
0467: case 1: {
0468: ArrayValue result = new ArrayValueImpl();
0469:
0470: for (int i = 0; i < count.length; i++) {
0471: if (count[i] > 0)
0472: result.put(LongValue.create(i), new LongValue(
0473: count[i]));
0474: }
0475:
0476: return result;
0477: }
0478:
0479: case 2: {
0480: ArrayValue result = new ArrayValueImpl();
0481:
0482: for (int i = 0; i < count.length; i++) {
0483: if (count[i] == 0)
0484: result.put(new LongValue(i),
0485: new LongValue(count[i]));
0486: }
0487:
0488: return result;
0489: }
0490:
0491: case 3: {
0492: StringValue sb = data.createStringBuilder();
0493:
0494: for (int i = 0; i < count.length; i++) {
0495: if (count[i] > 0)
0496: sb.append((char) i);
0497: }
0498:
0499: return sb;
0500: }
0501:
0502: case 4: {
0503: StringValue sb = data.createStringBuilder();
0504:
0505: for (int i = 0; i < count.length; i++) {
0506: if (count[i] == 0)
0507: sb.append((char) i);
0508: }
0509:
0510: return sb;
0511: }
0512:
0513: default:
0514: return BooleanValue.FALSE;
0515: }
0516: }
0517:
0518: /**
0519: * Calculates the crc32 value for a string
0520: *
0521: * @param str the string value
0522: *
0523: * @return the crc32 hash
0524: */
0525: public static long crc32(InputStream is) {
0526: try {
0527: CRC32 crc = new CRC32();
0528:
0529: int ch;
0530: while ((ch = is.read()) >= 0) {
0531: crc.update((byte) ch);
0532: }
0533:
0534: return crc.getValue() & 0xffffffff;
0535: } catch (IOException e) {
0536: throw new QuercusModuleException(e);
0537: }
0538: }
0539:
0540: public static String crypt(String string, @Optional
0541: String salt) {
0542: if (string == null)
0543: string = "";
0544:
0545: if (salt == null || salt.equals("")) {
0546: salt = ("" + Crypt.resultToChar(RandomUtil.nextInt(0x40)) + Crypt
0547: .resultToChar(RandomUtil.nextInt(0x40)));
0548: }
0549:
0550: return Crypt.crypt(string, salt);
0551: }
0552:
0553: /**
0554: * Explodes a string into an array
0555: *
0556: * @param separator the separator string
0557: * @param string the string to be exploded
0558: * @param limit the max number of elements
0559: * @return an array of exploded values
0560: */
0561: public static Value explode(StringValue separator,
0562: StringValue string, @Optional("0x7fffffff")
0563: long limit) {
0564: if (separator.length() == 0)
0565: return BooleanValue.FALSE;
0566:
0567: ArrayValue array = new ArrayValueImpl();
0568:
0569: int head = 0;
0570: int tail;
0571:
0572: int i = 0;
0573: while ((tail = string.indexOf(separator, head)) >= 0) {
0574: if (limit <= i + 1)
0575: break;
0576:
0577: LongValue key = LongValue.create(i++);
0578:
0579: StringValue chunk = string.substring(head, tail);
0580:
0581: array.put(key, chunk);
0582:
0583: head = tail + separator.length();
0584: }
0585:
0586: LongValue key = LongValue.create(i);
0587:
0588: StringValue chunk = string.substring(head);
0589:
0590: array.put(key, chunk);
0591:
0592: return array;
0593: }
0594:
0595: /**
0596: * Use printf style formatting to write a string to a file.
0597: * @param fd the file to write to
0598: * @param format the format string
0599: * @param args the valujes to apply to the format string
0600: */
0601: public static Value fprintf(Env env, @NotNull
0602: BinaryOutput os, StringValue format, Value[] args) {
0603: Value value = sprintf(format, args);
0604:
0605: return FileModule.fwrite(env, os, value.toInputStream(),
0606: Integer.MAX_VALUE);
0607: }
0608:
0609: /**
0610: * implodes an array into a string
0611: *
0612: * @param glueV the separator string
0613: * @param piecesV the array to be imploded
0614: *
0615: * @return a string of imploded values
0616: */
0617: public static Value implode(Env env, Value glueV, @Optional
0618: Value piecesV) {
0619: StringValue glue;
0620: ArrayValue pieces;
0621:
0622: if (piecesV.isArray()) {
0623: pieces = piecesV.toArrayValue(env);
0624: glue = glueV.toStringValue();
0625: } else if (glueV.isArray()) {
0626: pieces = glueV.toArrayValue(env);
0627: glue = piecesV.toStringValue();
0628: } else {
0629: env
0630: .warning(L
0631: .l(
0632: "neither argument to implode is an array: {0}, {1}",
0633: glueV.getClass().getName(), piecesV
0634: .getClass().getName()));
0635:
0636: return NullValue.NULL;
0637: }
0638:
0639: StringValue sb = glue.createStringBuilder();
0640: boolean isFirst = true;
0641:
0642: for (ArrayValue.Entry entry = pieces.getHead(); entry != null; entry = entry
0643: .getNext()) {
0644: if (!isFirst)
0645: sb = sb.append(glue);
0646:
0647: isFirst = false;
0648:
0649: sb = sb.append(entry.getValue());
0650: }
0651:
0652: return sb;
0653: }
0654:
0655: /**
0656: * implodes an array into a string
0657: *
0658: * @param glueV the separator string
0659: * @param piecesV the array to be imploded
0660: *
0661: * @return a string of imploded values
0662: */
0663: public static Value join(Env env, Value glueV, Value piecesV) {
0664: return implode(env, glueV, piecesV);
0665: }
0666:
0667: /**
0668: * returns the md5 hash
0669: *
0670: * @param source the string
0671: * @param rawOutput if true, return the raw binary
0672: *
0673: * @return a string of imploded values
0674: */
0675: public static StringValue md5(Env env, InputStream is, @Optional
0676: boolean rawOutput) {
0677: try {
0678: MessageDigest md = MessageDigest.getInstance("MD5");
0679:
0680: // XXX: iso-8859-1
0681:
0682: int ch;
0683: while ((ch = is.read()) >= 0) {
0684: md.update((byte) ch);
0685: }
0686:
0687: byte[] digest = md.digest();
0688:
0689: StringValue sb = env.createUnicodeBuilder();
0690: for (int i = 0; i < digest.length; i++) {
0691: int d1 = (digest[i] >> 4) & 0xf;
0692: int d2 = (digest[i] & 0xf);
0693:
0694: sb.append(toHexChar(d1));
0695: sb.append(toHexChar(d2));
0696: }
0697:
0698: return sb;
0699: } catch (Exception e) {
0700: throw new QuercusModuleException(e);
0701: }
0702: }
0703:
0704: /**
0705: * returns the md5 hash
0706: *
0707: * @param source the string
0708: * @param rawOutput if true, return the raw binary
0709: *
0710: * @return a string of imploded values
0711: */
0712: public static Value md5_file(Env env, Path source, @Optional
0713: boolean rawOutput) {
0714: try {
0715: MessageDigest md = MessageDigest.getInstance("MD5");
0716: InputStream is = null;
0717:
0718: try {
0719: is = source.openRead();
0720: int d;
0721:
0722: while ((d = is.read()) >= 0) {
0723: md.update((byte) d);
0724: }
0725:
0726: return digestToString(env, md.digest());
0727: } catch (IOException e) {
0728: log.log(Level.FINE, e.toString(), e);
0729:
0730: return BooleanValue.FALSE;
0731: } finally {
0732: try {
0733: if (is != null)
0734: is.close();
0735: } catch (IOException e) {
0736: }
0737: }
0738: } catch (Exception e) {
0739: throw new QuercusModuleException(e);
0740: }
0741: }
0742:
0743: private static StringValue digestToString(Env env, byte[] digest) {
0744: StringValue sb = env.createUnicodeBuilder();
0745: for (int i = 0; i < digest.length; i++) {
0746: int d1 = (digest[i] >> 4) & 0xf;
0747: int d2 = (digest[i] & 0xf);
0748:
0749: sb.append(toHexChar(d1));
0750: sb.append(toHexChar(d2));
0751: }
0752:
0753: return sb;
0754: }
0755:
0756: /**
0757: * Returns a formatted money value.
0758: *
0759: * @param format the format
0760: * @param value the value
0761: *
0762: * @return a string of formatted values
0763: */
0764: public static String money_format(Env env, String format,
0765: double value) {
0766: Locale monetaryLocale = env.getLocaleInfo().getMonetary();
0767:
0768: return NumberFormat.getCurrencyInstance(monetaryLocale).format(
0769: value);
0770: }
0771:
0772: /**
0773: * Returns the metaphone of a string.
0774: * This implentation produces identical results to the php version, which does contain some bugs.
0775: */
0776: public static String metaphone(String string) {
0777: if (string == null)
0778: string = "";
0779:
0780: int length = string.length();
0781: int index = 0;
0782: char ch = 0;
0783:
0784: // ignore everything up until first letter
0785: for (; index < length; index++) {
0786: ch = toUpperCase(string.charAt(index));
0787:
0788: if ('A' <= ch && ch <= 'Z')
0789: break;
0790: }
0791:
0792: if (index == length)
0793: return "";
0794:
0795: int lastIndex = length - 1;
0796:
0797: StringBuilder result = new StringBuilder(length);
0798:
0799: // special case first letter
0800:
0801: char nextCh = index < lastIndex ? toUpperCase(string
0802: .charAt(index + 1)) : 0;
0803:
0804: switch (ch) {
0805: case 'A':
0806: if (nextCh == 'E') {
0807: result.append('E');
0808: index += 2;
0809: } else {
0810: result.append('A');
0811: index += 1;
0812: }
0813:
0814: break;
0815:
0816: case 'E':
0817: case 'I':
0818: case 'O':
0819: case 'U':
0820: result.append(ch);
0821: index += 1;
0822: break;
0823:
0824: case 'G':
0825: case 'K':
0826: case 'P':
0827: if (nextCh == 'N') {
0828: result.append('N');
0829: index += 2;
0830: }
0831:
0832: break;
0833:
0834: case 'W':
0835: if (nextCh == 'H' || nextCh == 'R') {
0836: result.append(nextCh);
0837: index += 2;
0838: } else {
0839: switch (nextCh) {
0840: case 'A':
0841: case 'E':
0842: case 'I':
0843: case 'O':
0844: case 'U':
0845: result.append('W');
0846: index += 2;
0847: break;
0848: default:
0849: break;
0850: }
0851: }
0852:
0853: break;
0854:
0855: case 'X':
0856: result.append('S');
0857: index += 1;
0858: break;
0859:
0860: default:
0861: break;
0862: }
0863:
0864: // the rest of the letters
0865:
0866: char prevCh;
0867:
0868: for (; index < length; index++) {
0869:
0870: if (index > 0)
0871: prevCh = toUpperCase(string.charAt(index - 1));
0872: else
0873: prevCh = 0;
0874:
0875: ch = toUpperCase(string.charAt(index));
0876:
0877: if (ch < 'A' || ch > 'Z')
0878: continue;
0879:
0880: if (ch == prevCh && ch != 'C')
0881: continue;
0882:
0883: if (index + 1 < length)
0884: nextCh = toUpperCase(string.charAt(index + 1));
0885: else
0886: nextCh = 0;
0887:
0888: char nextnextCh;
0889:
0890: if (index + 2 < length)
0891: nextnextCh = toUpperCase(string.charAt(index + 2));
0892: else
0893: nextnextCh = 0;
0894:
0895: switch (ch) {
0896: case 'B':
0897: if (prevCh != 'M')
0898: result.append('B');
0899: break;
0900:
0901: case 'C':
0902: switch (nextCh) {
0903: case 'E':
0904: case 'I':
0905: case 'Y':
0906: // makesoft
0907: if (nextCh == 'I' && nextnextCh == 'A') {
0908: result.append('X');
0909: } else if (prevCh == 'S') {
0910: } else {
0911: result.append('S');
0912: }
0913: break;
0914: default:
0915: if (nextCh == 'H') {
0916: result.append('X');
0917: index++;
0918: } else {
0919: result.append('K');
0920: }
0921: break;
0922: }
0923:
0924: break;
0925:
0926: case 'D':
0927: if (nextCh == 'G') {
0928: switch (nextnextCh) {
0929: case 'E':
0930: case 'I':
0931: case 'Y':
0932: // makesoft
0933: result.append('J');
0934: index++;
0935: break;
0936: default:
0937: result.append('T');
0938: break;
0939: }
0940: } else
0941: result.append('T');
0942:
0943: break;
0944:
0945: case 'G':
0946: if (nextCh == 'H') {
0947: boolean isSilent = false;
0948:
0949: if (index - 3 >= 0) {
0950: char prev3Ch = toUpperCase(string
0951: .charAt(index - 3));
0952: switch (prev3Ch) {
0953: // noghtof
0954: case 'B':
0955: case 'D':
0956: case 'H':
0957: isSilent = true;
0958: break;
0959: default:
0960: break;
0961: }
0962: }
0963:
0964: if (!isSilent) {
0965: if (index - 4 >= 0) {
0966: char prev4Ch = toUpperCase(string
0967: .charAt(index - 4));
0968:
0969: isSilent = (prev4Ch == 'H');
0970: }
0971: }
0972:
0973: if (!isSilent) {
0974: result.append('F');
0975: index++;
0976: }
0977: } else if (nextCh == 'N') {
0978: char nextnextnextCh;
0979:
0980: if (index + 3 < length)
0981: nextnextnextCh = toUpperCase(string
0982: .charAt(index + 3));
0983: else
0984: nextnextnextCh = 0;
0985:
0986: if (nextnextCh < 'A' || nextnextCh > 'Z') {
0987: } else if (nextnextCh == 'E'
0988: && nextnextnextCh == 'D') {
0989: } else
0990: result.append('K');
0991: } else if (prevCh == 'G') {
0992: result.append('K');
0993: } else {
0994: switch (nextCh) {
0995: case 'E':
0996: case 'I':
0997: case 'Y':
0998: // makesoft
0999: result.append('J');
1000: break;
1001: default:
1002: result.append('K');
1003: break;
1004: }
1005: }
1006:
1007: break;
1008:
1009: case 'H':
1010: case 'W':
1011: case 'Y':
1012: switch (nextCh) {
1013: case 'A':
1014: case 'E':
1015: case 'I':
1016: case 'O':
1017: case 'U':
1018: // followed by a vowel
1019:
1020: if (ch == 'H') {
1021: switch (prevCh) {
1022: case 'C':
1023: case 'G':
1024: case 'P':
1025: case 'S':
1026: case 'T':
1027: // affecth
1028: break;
1029: default:
1030: result.append('H');
1031: break;
1032: }
1033: } else
1034: result.append(ch);
1035:
1036: break;
1037: default:
1038: // not followed by a vowel
1039: break;
1040: }
1041:
1042: break;
1043:
1044: case 'K':
1045: if (prevCh != 'C')
1046: result.append('K');
1047:
1048: break;
1049:
1050: case 'P':
1051: if (nextCh == 'H')
1052: result.append('F');
1053: else
1054: result.append('P');
1055:
1056: break;
1057:
1058: case 'Q':
1059: result.append('K');
1060: break;
1061:
1062: case 'S':
1063: if (nextCh == 'I'
1064: && (nextnextCh == 'O' || nextnextCh == 'A')) {
1065: result.append('X');
1066: } else if (nextCh == 'H') {
1067: result.append('X');
1068: index++;
1069: } else
1070: result.append('S');
1071:
1072: break;
1073:
1074: case 'T':
1075: if (nextCh == 'I'
1076: && (nextnextCh == 'O' || nextnextCh == 'A')) {
1077: result.append('X');
1078: } else if (nextCh == 'H') {
1079: result.append('0');
1080: index++;
1081: } else
1082: result.append('T');
1083:
1084: break;
1085:
1086: case 'V':
1087: result.append('F');
1088:
1089: break;
1090:
1091: case 'X':
1092: result.append('K');
1093: result.append('S');
1094: break;
1095:
1096: case 'Z':
1097: result.append('S');
1098: break;
1099:
1100: case 'F':
1101: case 'J':
1102: case 'L':
1103: case 'M':
1104: case 'N':
1105: case 'R':
1106: result.append(ch);
1107: break;
1108:
1109: default:
1110: break;
1111: }
1112: }
1113:
1114: return result.toString();
1115: }
1116:
1117: /**
1118: * Returns a formatted number.
1119: *
1120: * @param value the value
1121: * @param decimals the number of decimals
1122: * @param pointValue the decimal point string
1123: * @param groupValue the thousands separator
1124: *
1125: * @return a string of the formatted number
1126: */
1127: public static String number_format(Env env, double value, @Optional
1128: int decimals, @Optional
1129: Value pointValue, @Optional
1130: Value groupValue) {
1131: boolean isGroupDefault = (groupValue instanceof DefaultValue);
1132: boolean isPointDefault = (pointValue instanceof DefaultValue);
1133:
1134: if (!isPointDefault && isGroupDefault) {
1135: env.warning(L.l("wrong parameter count"));
1136: return null;
1137: }
1138:
1139: String pattern;
1140:
1141: char point = '.';
1142:
1143: if (!pointValue.isNull()) {
1144: String pointString = pointValue.toString();
1145:
1146: point = (pointString.length() == 0) ? 0 : pointString
1147: .charAt(0);
1148: }
1149:
1150: char group = ',';
1151:
1152: if (!groupValue.isNull()) {
1153: String groupString = groupValue.toString();
1154:
1155: group = (groupString.length() == 0) ? 0 : groupString
1156: .charAt(0);
1157: }
1158:
1159: if (decimals > 0) {
1160: StringBuilder patternBuilder = new StringBuilder(
1161: 6 + decimals);
1162:
1163: patternBuilder.append(group == 0 ? "###0." : "#,##0.");
1164:
1165: for (int i = 0; i < decimals; i++)
1166: patternBuilder.append('0');
1167:
1168: pattern = patternBuilder.toString();
1169: } else {
1170: pattern = group == 0 ? "###0" : "#,##0";
1171: }
1172:
1173: DecimalFormatSymbols decimalFormatSymbols;
1174:
1175: if (point == '.' && group == ',')
1176: decimalFormatSymbols = DEFAULT_DECIMAL_FORMAT_SYMBOLS;
1177: else {
1178: decimalFormatSymbols = new DecimalFormatSymbols();
1179: decimalFormatSymbols.setDecimalSeparator(point);
1180: decimalFormatSymbols.setGroupingSeparator(group);
1181: decimalFormatSymbols.setZeroDigit('0');
1182: }
1183:
1184: DecimalFormat format = new DecimalFormat(pattern,
1185: decimalFormatSymbols);
1186:
1187: String result = format.format(value);
1188:
1189: if (point == 0 && decimals > 0) {
1190: // no way to get DecimalFormat to output nothing for the point,
1191: // so remove it here
1192: int i = result.lastIndexOf(point);
1193:
1194: return result.substring(0, i)
1195: + result.substring(i + 1, result.length());
1196: } else
1197: return result;
1198: }
1199:
1200: /**
1201: * Converts the first character to an integer.
1202: *
1203: * @param string the string to be converted
1204: *
1205: * @return the integer value
1206: */
1207: public static long ord(StringValue string) {
1208: if (string.length() == 0)
1209: return 0;
1210: else
1211: return string.charAt(0);
1212: }
1213:
1214: /**
1215: * Parses the string as a query string.
1216: *
1217: * @param env the calling environment
1218: * @param str the query string
1219: * @param array the optional result array
1220: */
1221: @UsesSymbolTable
1222: public static Value parse_str(Env env, String str, @Optional
1223: @Reference
1224: Value ref) {
1225: if (str == null)
1226: str = "";
1227:
1228: boolean isRef = ref instanceof Var;
1229:
1230: ArrayValue result = null;
1231:
1232: if (isRef) {
1233: result = new ArrayValueImpl();
1234: ref.set(result);
1235: } else if (ref instanceof ArrayValue) {
1236: result = (ArrayValue) ref;
1237: isRef = true;
1238: } else
1239: result = new ArrayValueImpl();
1240:
1241: return StringUtility.parseStr(env, str, result, isRef, env
1242: .getHttpInputEncoding());
1243: }
1244:
1245: /**
1246: * Prints the string.
1247: *
1248: * @param env the quercus environment
1249: * @param value the string to print
1250: */
1251: public static long print(Env env, Value value) {
1252: value.print(env);
1253:
1254: return 1;
1255: }
1256:
1257: /**
1258: * Escapes meta characters.
1259: *
1260: * @param string the string to be quoted
1261: *
1262: * @return the quoted
1263: */
1264: public static Value quotemeta(StringValue string) {
1265: int len = string.length();
1266:
1267: StringValue sb = string.createStringBuilder(len * 5 / 4);
1268:
1269: for (int i = 0; i < len; i++) {
1270: char ch = string.charAt(i);
1271:
1272: switch (ch) {
1273: case '.':
1274: case '\\':
1275: case '+':
1276: case '*':
1277: case '?':
1278: case '[':
1279: case '^':
1280: case ']':
1281: case '(':
1282: case ')':
1283: case '$':
1284: sb.append("\\");
1285: sb.append(ch);
1286: break;
1287: default:
1288: sb.append(ch);
1289: break;
1290: }
1291: }
1292:
1293: return sb;
1294: }
1295:
1296: /**
1297: * Converts a RFC2045 quoted printable string to a string.
1298: */
1299: // XXX: i18n
1300: public static String quoted_printable_decode(String str) {
1301: if (str == null)
1302: str = "";
1303:
1304: StringBuilder sb = new StringBuilder();
1305:
1306: int length = str.length();
1307:
1308: for (int i = 0; i < length; i++) {
1309: char ch = str.charAt(i);
1310:
1311: if (33 <= ch && ch <= 60)
1312: sb.append(ch);
1313: else if (62 <= ch && ch <= 126)
1314: sb.append(ch);
1315: else if (ch == ' ' || ch == '\t') {
1316: if (i + 1 < str.length()
1317: && (str.charAt(i + 1) == '\r' || str
1318: .charAt(i + 1) == '\n')) {
1319: sb.append('=');
1320: sb.append(toUpperHexChar(ch >> 4));
1321: sb.append(toUpperHexChar(ch));
1322: } else
1323: sb.append(ch);
1324: } else if (ch == '\r' || ch == '\n') {
1325: sb.append(ch);
1326: } else {
1327: sb.append('=');
1328: sb.append(toUpperHexChar(ch >> 4));
1329: sb.append(toUpperHexChar(ch));
1330: }
1331: }
1332:
1333: return sb.toString();
1334: }
1335:
1336: private static final boolean[] TRIM_WHITESPACE = new boolean[256];
1337:
1338: static {
1339: TRIM_WHITESPACE['\0'] = true;
1340: TRIM_WHITESPACE['\b'] = true;
1341: TRIM_WHITESPACE[' '] = true;
1342: TRIM_WHITESPACE['\t'] = true;
1343: TRIM_WHITESPACE['\r'] = true;
1344: TRIM_WHITESPACE['\n'] = true;
1345: }
1346:
1347: /**
1348: * Removes leading whitespace.
1349: *
1350: * @param string the string to be trimmed
1351: * @param characters optional set of characters to trim
1352: * @return the trimmed string
1353: */
1354: public static StringValue ltrim(Env env, StringValue string,
1355: @Optional
1356: String characters) {
1357: if (characters == null)
1358: characters = "";
1359:
1360: boolean[] trim;
1361:
1362: if (characters.equals(""))
1363: trim = TRIM_WHITESPACE;
1364: else
1365: trim = parseCharsetBitmap(characters);
1366:
1367: for (int i = 0; i < string.length(); i++) {
1368: char ch = string.charAt(i);
1369:
1370: if (ch >= 256 || !trim[ch]) {
1371: if (i == 0)
1372: return string;
1373: else
1374: return string.substring(i);
1375: }
1376: }
1377:
1378: return env.createEmptyString();
1379: }
1380:
1381: /**
1382: * Removes trailing whitespace.
1383: *
1384: * @param env the quercus environment
1385: * @param string the string to be trimmed
1386: * @param characters optional set of characters to trim
1387: * @return the trimmed string
1388: */
1389: public static StringValue rtrim(Env env, StringValue string,
1390: @Optional
1391: String characters) {
1392: if (characters == null)
1393: characters = "";
1394:
1395: boolean[] trim;
1396:
1397: if (characters.equals(""))
1398: trim = TRIM_WHITESPACE;
1399: else
1400: trim = parseCharsetBitmap(characters);
1401:
1402: for (int i = string.length() - 1; i >= 0; i--) {
1403: char ch = string.charAt(i);
1404:
1405: if (ch >= 256 || !trim[ch]) {
1406: if (i == string.length())
1407: return string;
1408: else
1409: return (StringValue) string.subSequence(0, i + 1);
1410: }
1411: }
1412:
1413: return env.createEmptyString();
1414: }
1415:
1416: /**
1417: * Sets locale configuration.
1418: */
1419: public static Value setlocale(Env env, int category,
1420: Value localeArg, Value[] fallback) {
1421: LocaleInfo localeInfo = env.getLocaleInfo();
1422:
1423: if (localeArg instanceof ArrayValue) {
1424: for (Value value : ((ArrayValue) localeArg).values()) {
1425: Locale locale = setLocale(localeInfo, category, value
1426: .toString());
1427:
1428: if (locale != null)
1429: return env.createString(locale.toString());
1430: }
1431: } else {
1432: Locale locale = setLocale(localeInfo, category, localeArg
1433: .toString());
1434:
1435: if (locale != null)
1436: return env.createString(locale.toString());
1437: }
1438:
1439: for (int i = 0; i < fallback.length; i++) {
1440: Locale locale = setLocale(localeInfo, category, fallback[i]
1441: .toString());
1442:
1443: if (locale != null)
1444: return env.createString(locale.toString());
1445: }
1446:
1447: return BooleanValue.FALSE;
1448: }
1449:
1450: /**
1451: * Sets locale configuration.
1452: */
1453: private static Locale setLocale(LocaleInfo localeInfo,
1454: int category, String localeName) {
1455: String language;
1456: String country;
1457: String variant;
1458:
1459: int p = localeName.indexOf('_');
1460: int p1 = localeName.indexOf('-');
1461:
1462: if (p1 > 0 && (p1 < p || p < 0))
1463: p = p1;
1464:
1465: Locale locale;
1466:
1467: if (p > 0) {
1468: language = localeName.substring(0, p);
1469:
1470: int q = localeName.indexOf('-', p + 1);
1471: int q1 = localeName.indexOf('.', p + 1);
1472: // XXX: '.' should be charset?
1473:
1474: if (q1 > 0 && (q1 < q || q < 0))
1475: q = q1;
1476:
1477: q1 = localeName.indexOf('@', p + 1);
1478: // XXX: '@' is ??
1479:
1480: if (q1 > 0 && (q1 < q || q < 0))
1481: q = q1;
1482:
1483: q1 = localeName.indexOf('_', p + 1);
1484:
1485: if (q1 > 0 && (q1 < q || q < 0))
1486: q = q1;
1487:
1488: if (q > 0) {
1489: country = localeName.substring(p + 1, q);
1490: variant = localeName.substring(q + 1);
1491:
1492: locale = new Locale(language, country, variant);
1493: } else {
1494: country = localeName.substring(p + 1);
1495:
1496: locale = new Locale(language, country);
1497: }
1498: } else
1499: locale = new Locale(localeName);
1500:
1501: if (!isValidLocale(locale))
1502: return null;
1503:
1504: switch (category) {
1505: case LC_ALL:
1506: localeInfo.setAll(locale);
1507: return localeInfo.getMessages();
1508: case LC_COLLATE:
1509: localeInfo.setCollate(locale);
1510: return localeInfo.getCollate();
1511: case LC_CTYPE:
1512: localeInfo.setCtype(locale);
1513: return localeInfo.getCtype();
1514: case LC_MONETARY:
1515: localeInfo.setMonetary(locale);
1516: return localeInfo.getMonetary();
1517: case LC_NUMERIC:
1518: localeInfo.setNumeric(locale);
1519: return localeInfo.getNumeric();
1520: case LC_TIME:
1521: localeInfo.setTime(locale);
1522: return localeInfo.getTime();
1523: case LC_MESSAGES:
1524: localeInfo.setMessages(locale);
1525: return localeInfo.getMessages();
1526: default:
1527: return null;
1528: }
1529: }
1530:
1531: /**
1532: * Returns true if the locale is supported.
1533: */
1534: private static boolean isValidLocale(Locale locale) {
1535: Locale[] validLocales = Locale.getAvailableLocales();
1536:
1537: for (int i = 0; i < validLocales.length; i++) {
1538: if (validLocales[i].equals(locale)) {
1539: return true;
1540: }
1541: }
1542:
1543: return false;
1544: }
1545:
1546: /**
1547: * Gets locale-specific symbols.
1548: */
1549: public static ArrayValue localeconv(Env env) {
1550: ArrayValueImpl array = new ArrayValueImpl();
1551:
1552: Locale money = env.getLocaleInfo().getMonetary();
1553:
1554: DecimalFormatSymbols decimal = new DecimalFormatSymbols(money);
1555: Currency currency = NumberFormat.getInstance(money)
1556: .getCurrency();
1557:
1558: array.put(env.createString("decimal_point"), env
1559: .createString(decimal.getDecimalSeparator()));
1560: array.put(env.createString("thousands_sep"), env
1561: .createString(decimal.getGroupingSeparator()));
1562: //array.put("grouping", "");
1563: array
1564: .put(env.createString("int_curr_symbol"), env
1565: .createString(decimal
1566: .getInternationalCurrencySymbol()));
1567: array.put(env.createString("currency_symbol"), env
1568: .createString(decimal.getCurrencySymbol()));
1569: array.put(env.createString("mon_decimal_point"), env
1570: .createString(decimal.getMonetaryDecimalSeparator()));
1571: array.put(env.createString("mon_thousands_sep"), env
1572: .createString(decimal.getGroupingSeparator()));
1573: //array.put("mon_grouping", "");
1574: array.put(env.createString("positive_sign"), env
1575: .createEmptyString());
1576: array.put(env.createString("negative_sign"), env
1577: .createString(decimal.getMinusSign()));
1578: array.put(env.createString("int_frac_digits"), LongValue
1579: .create(currency.getDefaultFractionDigits()));
1580: array.put(env.createString("frac_digits"), LongValue
1581: .create(currency.getDefaultFractionDigits()));
1582: //array.put("p_cs_precedes", "");
1583: //array.put("p_sep_by_space", "");
1584: //array.put("n_cs_precedes", "");
1585: //array.put("n_sep_by_space", "");
1586: //array.put("p_sign_posn", "");
1587: //array.put("n_sign_posn", "");
1588:
1589: return array;
1590: }
1591:
1592: /**
1593: * returns the md5 hash
1594: *
1595: * @param source the string
1596: * @param rawOutput if true, return the raw binary
1597: *
1598: * @return a string of imploded values
1599: */
1600: public static String sha1(String source, @Optional
1601: boolean rawOutput) {
1602: if (source == null)
1603: source = "";
1604:
1605: try {
1606: MessageDigest md = MessageDigest.getInstance("SHA1");
1607:
1608: // XXX: iso-8859-1
1609:
1610: for (int i = 0; i < source.length(); i++) {
1611: char ch = source.charAt(i);
1612:
1613: md.update((byte) ch);
1614: }
1615:
1616: byte[] digest = md.digest();
1617:
1618: StringBuilder sb = new StringBuilder();
1619: for (int i = 0; i < digest.length; i++) {
1620: int d1 = (digest[i] >> 4) & 0xf;
1621: int d2 = (digest[i] & 0xf);
1622:
1623: sb.append(toHexChar(d1));
1624: sb.append(toHexChar(d2));
1625: }
1626:
1627: return sb.toString();
1628: } catch (Exception e) {
1629: throw new QuercusException(e);
1630: }
1631: }
1632:
1633: /**
1634: * returns the md5 hash
1635: *
1636: * @param source the string
1637: * @param rawOutput if true, return the raw binary
1638: *
1639: * @return a string of imploded values
1640: */
1641: public static Value sha1_file(Env env, Path source, @Optional
1642: boolean rawOutput) {
1643: try {
1644: MessageDigest md = MessageDigest.getInstance("SHA1");
1645: InputStream is = null;
1646:
1647: try {
1648: is = source.openRead();
1649: int d;
1650:
1651: while ((d = is.read()) >= 0) {
1652: md.update((byte) d);
1653: }
1654:
1655: return digestToString(env, md.digest());
1656: } catch (IOException e) {
1657: log.log(Level.FINE, e.toString(), e);
1658:
1659: return BooleanValue.FALSE;
1660: } finally {
1661: try {
1662: if (is != null)
1663: is.close();
1664: } catch (IOException e) {
1665: }
1666: }
1667: } catch (Exception e) {
1668: throw new QuercusException(e);
1669: }
1670: }
1671:
1672: /**
1673: * scans a string
1674: *
1675: * @param format the format string
1676: * @param args the format arguments
1677: *
1678: * @return the formatted string
1679: */
1680: public static Value sscanf(Env env, StringValue string,
1681: StringValue format, @Optional
1682: @Reference
1683: Value[] args) {
1684: int fmtLen = format.length();
1685: int strlen = string.length();
1686:
1687: int sIndex = 0;
1688: int fIndex = 0;
1689:
1690: boolean isAssign = args.length != 0;
1691: int argIndex = 0;
1692:
1693: ArrayValue array = new ArrayValueImpl();
1694:
1695: while (fIndex < fmtLen) {
1696: char ch = format.charAt(fIndex++);
1697:
1698: if (isWhitespace(ch)) {
1699: for (; (fIndex < fmtLen && isWhitespace(ch = format
1700: .charAt(fIndex))); fIndex++) {
1701: }
1702:
1703: ch = string.charAt(sIndex);
1704: if (!isWhitespace(ch)) {
1705: // XXX: return false?
1706: return sscanfReturn(env, array, args, argIndex,
1707: isAssign);
1708: }
1709:
1710: for (sIndex++; sIndex < strlen
1711: && isWhitespace(string.charAt(sIndex)); sIndex++) {
1712: }
1713: } else if (ch == '%') {
1714: int maxLen = -1;
1715:
1716: loop: while (fIndex < fmtLen) {
1717: ch = format.charAt(fIndex++);
1718:
1719: if (sIndex >= strlen) {
1720: array.append(NullValue.NULL);
1721: break loop;
1722: }
1723:
1724: Value obj;
1725:
1726: if (isAssign) {
1727: if (argIndex < args.length)
1728: obj = args[argIndex++];
1729: else {
1730: env.warning(L
1731: .l("not enough vars passed in"));
1732: break loop;
1733: }
1734: } else
1735: obj = array;
1736:
1737: switch (ch) {
1738: case '%':
1739: if (string.charAt(sIndex) != '%')
1740: return sscanfReturn(env, array, args,
1741: argIndex, isAssign);
1742: else
1743: break loop;
1744:
1745: case '0':
1746: case '1':
1747: case '2':
1748: case '3':
1749: case '4':
1750: case '5':
1751: case '6':
1752: case '7':
1753: case '8':
1754: case '9':
1755: if (maxLen < 0)
1756: maxLen = 0;
1757:
1758: maxLen = 10 * maxLen + ch - '0';
1759: break;
1760:
1761: case 's':
1762: sIndex = sscanfString(string, sIndex, maxLen,
1763: obj, isAssign);
1764: break loop;
1765:
1766: case 'c':
1767: if (maxLen < 0)
1768: maxLen = 1;
1769:
1770: sIndex = sscanfString(string, sIndex, maxLen,
1771: obj, isAssign);
1772: break loop;
1773:
1774: case 'd':
1775: sIndex = sscanfInteger(string, sIndex, maxLen,
1776: obj, isAssign, 10, false);
1777: break loop;
1778:
1779: case 'u':
1780: sIndex = sscanfInteger(string, sIndex, maxLen,
1781: obj, isAssign, 10, true);
1782: break loop;
1783:
1784: case 'o':
1785: sIndex = sscanfInteger(string, sIndex, maxLen,
1786: obj, isAssign, 8, false);
1787: break loop;
1788:
1789: case 'x':
1790: case 'X':
1791: sIndex = sscanfHex(string, sIndex, maxLen, obj,
1792: isAssign);
1793: break loop;
1794:
1795: case 'e':
1796: case 'f':
1797: sIndex = sscanfScientific(string, sIndex,
1798: maxLen, obj, isAssign);
1799: break loop;
1800:
1801: default:
1802: log.fine(L.l("'{0}' is a bad sscanf string.",
1803: format));
1804: env.warning(L
1805: .l("'{0}' is a bad sscanf string.",
1806: format));
1807:
1808: return isAssign ? LongValue.create(argIndex)
1809: : array;
1810: }
1811: }
1812: } else if (ch == string.charAt(sIndex)) {
1813: sIndex++;
1814: } else
1815: return sscanfReturn(env, array, args, argIndex,
1816: isAssign);
1817: }
1818:
1819: return sscanfReturn(env, array, args, argIndex, isAssign);
1820: }
1821:
1822: private static Value sscanfReturn(Env env, ArrayValue array,
1823: Value[] args, int argIndex, boolean isAssign) {
1824: if (isAssign) {
1825: if (argIndex != args.length)
1826: env.warning(L.l(
1827: "{0} vars passed in but saw only {1} '%' args",
1828: args.length, argIndex));
1829:
1830: return LongValue.create(argIndex);
1831: } else {
1832: return array;
1833: }
1834: }
1835:
1836: /**
1837: * Scans a string with a given length.
1838: */
1839: private static int sscanfString(StringValue string, int sIndex,
1840: int maxLen, Value obj, boolean isAssignment) {
1841: int strlen = string.length();
1842:
1843: if (maxLen < 0)
1844: maxLen = Integer.MAX_VALUE;
1845:
1846: StringValue sb = string.createStringBuilder();
1847:
1848: for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
1849: char ch = string.charAt(sIndex);
1850:
1851: if (!isWhitespace(ch))
1852: sb.append(ch);
1853: else
1854: break;
1855: }
1856:
1857: sscanfPut(obj, sb, isAssignment);
1858:
1859: return sIndex;
1860: }
1861:
1862: private static void sscanfPut(Value obj, Value val,
1863: boolean isAssignment) {
1864: if (isAssignment)
1865: obj.set(val);
1866: else
1867: obj.put(val);
1868: }
1869:
1870: /**
1871: * Scans a integer with a given length.
1872: */
1873: private static int sscanfInteger(StringValue string, int sIndex,
1874: int maxLen, Value obj, boolean isAssign, int base,
1875: boolean isUnsigned) {
1876: int strlen = string.length();
1877:
1878: if (maxLen < 0)
1879: maxLen = Integer.MAX_VALUE;
1880:
1881: int val = 0;
1882: int sign = 1;
1883: boolean isNotMatched = true;
1884:
1885: if (sIndex < strlen) {
1886: char ch = string.charAt(sIndex);
1887:
1888: if (ch == '+') {
1889: sIndex++;
1890: maxLen--;
1891: } else if (ch == '-') {
1892: sign = -1;
1893:
1894: sIndex++;
1895: maxLen--;
1896: }
1897: }
1898:
1899: int topRange = base + '0';
1900:
1901: for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
1902: char ch = string.charAt(sIndex);
1903:
1904: if ('0' <= ch && ch < topRange) {
1905: val = val * base + ch - '0';
1906: isNotMatched = false;
1907: } else if (isNotMatched) {
1908: sscanfPut(obj, NullValue.NULL, isAssign);
1909: return sIndex;
1910: } else
1911: break;
1912: }
1913:
1914: if (isUnsigned) {
1915: if (sign == -1 && val != 0)
1916: sscanfPut(obj, StringValue
1917: .create(0xFFFFFFFFL - val + 1), isAssign);
1918: else
1919: sscanfPut(obj, LongValue.create(val), isAssign);
1920: } else
1921: sscanfPut(obj, LongValue.create(val * sign), isAssign);
1922:
1923: return sIndex;
1924: }
1925:
1926: /**
1927: * Scans a integer with a given length.
1928: */
1929: private static int sscanfHex(StringValue string, int sIndex,
1930: int maxLen, Value obj, boolean isAssign) {
1931: int strlen = string.length();
1932:
1933: if (maxLen < 0)
1934: maxLen = Integer.MAX_VALUE;
1935:
1936: int val = 0;
1937: int sign = 1;
1938: boolean isMatched = false;
1939:
1940: if (sIndex < strlen) {
1941: char ch = string.charAt(sIndex);
1942:
1943: if (ch == '+') {
1944: sIndex++;
1945: maxLen--;
1946: } else if (ch == '-') {
1947: sign = -1;
1948:
1949: sIndex++;
1950: maxLen--;
1951: }
1952: }
1953:
1954: for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
1955: char ch = string.charAt(sIndex);
1956:
1957: if ('0' <= ch && ch <= '9') {
1958: val = val * 16 + ch - '0';
1959: isMatched = true;
1960: } else if ('a' <= ch && ch <= 'f') {
1961: val = val * 16 + ch - 'a' + 10;
1962: isMatched = true;
1963: } else if ('A' <= ch && ch <= 'F') {
1964: val = val * 16 + ch - 'A' + 10;
1965: isMatched = true;
1966: } else if (!isMatched) {
1967: sscanfPut(obj, NullValue.NULL, isAssign);
1968: return sIndex;
1969: } else
1970: break;
1971: }
1972:
1973: sscanfPut(obj, LongValue.create(val * sign), isAssign);
1974:
1975: return sIndex;
1976: }
1977:
1978: /**
1979: * Scans a integer with a given length.
1980: */
1981: private static int sscanfScientific(StringValue s, int i,
1982: int maxLen, Value obj, boolean isAssign) {
1983: if (maxLen < 0)
1984: maxLen = Integer.MAX_VALUE;
1985:
1986: int start = i;
1987: int len = s.length();
1988: int ch = 0;
1989:
1990: if (i < len && maxLen > 0
1991: && ((ch = s.charAt(i)) == '+' || ch == '-')) {
1992: i++;
1993: maxLen--;
1994: }
1995:
1996: for (; i < len && maxLen > 0 && '0' <= (ch = s.charAt(i))
1997: && ch <= '9'; i++) {
1998: maxLen--;
1999: }
2000:
2001: if (ch == '.') {
2002: maxLen--;
2003:
2004: for (i++; i < len && maxLen > 0
2005: && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
2006: maxLen--;
2007: }
2008: }
2009:
2010: if (ch == 'e' || ch == 'E') {
2011: maxLen--;
2012:
2013: int e = i++;
2014:
2015: if (start == e) {
2016: sscanfPut(obj, NullValue.NULL, isAssign);
2017: return start;
2018: }
2019:
2020: if (i < len && maxLen > 0 && (ch = s.charAt(i)) == '+'
2021: || ch == '-') {
2022: i++;
2023: maxLen--;
2024: }
2025:
2026: for (; i < len && maxLen > 0 && '0' <= (ch = s.charAt(i))
2027: && ch <= '9'; i++) {
2028: maxLen--;
2029: }
2030:
2031: if (i == e + 1)
2032: i = e;
2033: }
2034:
2035: double val;
2036:
2037: if (i == 0)
2038: val = 0;
2039: else
2040: val = Double.parseDouble(s.substring(start, i).toString());
2041:
2042: sscanfPut(obj, DoubleValue.create(val), isAssign);
2043:
2044: return i;
2045: }
2046:
2047: /**
2048: * print to the output with a formatter
2049: *
2050: * @param env the quercus environment
2051: * @param format the format string
2052: * @param args the format arguments
2053: *
2054: * @return the formatted string
2055: */
2056: public static int printf(Env env, StringValue format, Value[] args) {
2057: Value str = sprintf(format, args);
2058:
2059: str.print(env);
2060:
2061: return str.length();
2062: }
2063:
2064: private static final char[] SOUNDEX_VALUES = "01230120022455012623010202"
2065: .toCharArray();
2066:
2067: public static Value soundex(StringValue string) {
2068: int length = string.length();
2069:
2070: if (length == 0)
2071: return BooleanValue.FALSE;
2072:
2073: StringValue result = string.createStringBuilder();
2074:
2075: int count = 0;
2076: char lastCode = 0;
2077:
2078: for (int i = 0; i < length && count < 4; i++) {
2079: char ch = toUpperCase(string.charAt(i));
2080:
2081: if ('A' <= ch && ch <= 'Z') {
2082: char code = SOUNDEX_VALUES[ch - 'A'];
2083:
2084: if (count == 0) {
2085: result.append(ch);
2086: count++;
2087: } else if (code != '0' && code != lastCode) {
2088: result.append(code);
2089: count++;
2090: }
2091:
2092: lastCode = code;
2093: }
2094: }
2095:
2096: for (; count < 4; count++) {
2097: result.append('0');
2098: }
2099:
2100: return result;
2101: }
2102:
2103: /**
2104: * Print to a string with a formatter
2105: *
2106: * @param format the format string
2107: * @param args the format arguments
2108: *
2109: * @return the formatted string
2110: */
2111: public static Value sprintf(StringValue format, Value[] args) {
2112: ArrayList<PrintfSegment> segments = parsePrintfFormat(format);
2113:
2114: StringValue sb = format.createStringBuilder();
2115:
2116: for (PrintfSegment segment : segments)
2117: segment.apply(sb, args);
2118:
2119: return sb;
2120: }
2121:
2122: private static ArrayList<PrintfSegment> parsePrintfFormat(
2123: StringValue format) {
2124: ArrayList<PrintfSegment> segments = new ArrayList<PrintfSegment>();
2125:
2126: StringBuilder sb = new StringBuilder();
2127: StringBuilder flags = new StringBuilder();
2128:
2129: int length = format.length();
2130: int index = 0;
2131:
2132: for (int i = 0; i < length; i++) {
2133: char ch = format.charAt(i);
2134:
2135: if (i + 1 < length && ch == '%') {
2136: // The C printf silently ignores invalid flags, so we need to
2137: // remove them if present.
2138:
2139: sb.append(ch);
2140:
2141: boolean isLeft = false;
2142: boolean isAlt = false;
2143: boolean isZero = false;
2144:
2145: flags.setLength(0);
2146:
2147: int j = i + 1;
2148:
2149: loop: for (; j < length; j++) {
2150: ch = format.charAt(j);
2151:
2152: switch (ch) {
2153: case '-':
2154: isLeft = true;
2155: break;
2156: case '#':
2157: isAlt = true;
2158: break;
2159: case '0':
2160: isZero = true;
2161: flags.append(ch);
2162: break;
2163: case '+':
2164: case ' ':
2165: case ',':
2166: case '(':
2167: flags.append(ch);
2168: break;
2169: default:
2170: break loop;
2171: }
2172: }
2173:
2174: int head = j;
2175: loop: for (; j < length; j++) {
2176: ch = format.charAt(j);
2177:
2178: switch (ch) {
2179: case '%':
2180: i = j;
2181: segments.add(new TextPrintfSegment(sb));
2182: sb.setLength(0);
2183: break loop;
2184:
2185: case '0':
2186: case '1':
2187: case '2':
2188: case '3':
2189: case '4':
2190: case '5':
2191: case '6':
2192: case '7':
2193: case '8':
2194: case '9':
2195: case '.':
2196: case '$':
2197: break;
2198:
2199: case 'b':
2200: case 'B':
2201: if (isLeft)
2202: sb.append('-');
2203: if (isAlt)
2204: sb.append('#');
2205: sb.append(format, head, j);
2206: sb.append(ch);
2207: i = j;
2208: break loop;
2209:
2210: case 's':
2211: case 'S':
2212: sb.setLength(sb.length() - 1);
2213: segments
2214: .add(new StringPrintfSegment(sb, isLeft
2215: || isAlt, isZero, ch == 'S',
2216: format.substring(head, j)
2217: .toString(), index++));
2218: sb.setLength(0);
2219: i = j;
2220: break loop;
2221:
2222: case 'c':
2223: case 'C':
2224: sb.setLength(sb.length() - 1);
2225: segments
2226: .add(new CharPrintfSegment(sb, isLeft
2227: || isAlt, isZero, ch == 'C',
2228: format.substring(head, j)
2229: .toString(), index++));
2230: sb.setLength(0);
2231: i = j;
2232: break loop;
2233:
2234: case 'i':
2235: case 'u':
2236: ch = 'd';
2237: case 'd':
2238: case 'x':
2239: case 'o':
2240: case 'X':
2241: sb.setLength(sb.length() - 1);
2242: if (sb.length() > 0)
2243: segments.add(new TextPrintfSegment(sb));
2244: sb.setLength(0);
2245:
2246: if (isLeft)
2247: sb.append('-');
2248: if (isAlt)
2249: sb.append('#');
2250: sb.append(flags);
2251: sb.append(format, head, j);
2252: sb.append(ch);
2253:
2254: segments.add(LongPrintfSegment.create(sb
2255: .toString(), index++));
2256: sb.setLength(0);
2257: i = j;
2258: break loop;
2259:
2260: case 'e':
2261: case 'E':
2262: case 'f':
2263: case 'g':
2264: case 'G':
2265: sb.setLength(sb.length() - 1);
2266: if (sb.length() > 0)
2267: segments.add(new TextPrintfSegment(sb));
2268: sb.setLength(0);
2269:
2270: if (isLeft)
2271: sb.append('-');
2272: if (isAlt)
2273: sb.append('#');
2274: sb.append(flags);
2275: sb.append(format, head, j);
2276: sb.append(ch);
2277:
2278: segments.add(new DoublePrintfSegment(sb
2279: .toString(), index++));
2280: sb.setLength(0);
2281: i = j;
2282: break loop;
2283:
2284: default:
2285: if (isLeft)
2286: sb.append('-');
2287: if (isAlt)
2288: sb.append('#');
2289: sb.append(flags);
2290: sb.append(format, head, j);
2291: sb.append(ch);
2292: i = j;
2293: break loop;
2294: }
2295: }
2296: } else
2297: sb.append(ch);
2298: }
2299:
2300: if (sb.length() > 0)
2301: segments.add(new TextPrintfSegment(sb));
2302:
2303: return segments;
2304: }
2305:
2306: /**
2307: * replaces substrings.
2308: *
2309: * @param search search string
2310: * @param replace replacement string
2311: * @param subject replacement
2312: * @param count return value
2313: */
2314: public static Value str_ireplace(Env env, Value search,
2315: Value replace, Value subject, @Reference
2316: @Optional
2317: Value count) {
2318: return strReplace(env, search, replace, subject, count, true);
2319: }
2320:
2321: /**
2322: * Pads strings
2323: *
2324: * @param string string
2325: * @param length length
2326: * @param pad padding string
2327: * @param type padding type
2328: */
2329: public static StringValue str_pad(StringValue string, int length,
2330: @Optional("' '")
2331: String pad, @Optional("STR_PAD_RIGHT")
2332: int type) {
2333: int strLen = string.length();
2334: int padLen = length - strLen;
2335:
2336: if (padLen <= 0)
2337: return string;
2338:
2339: if (pad == null || pad.length() == 0)
2340: pad = " ";
2341:
2342: int leftPad = 0;
2343: int rightPad = 0;
2344:
2345: switch (type) {
2346: case STR_PAD_LEFT:
2347: leftPad = padLen;
2348: break;
2349: case STR_PAD_RIGHT:
2350: default:
2351: rightPad = padLen;
2352: break;
2353: case STR_PAD_BOTH:
2354: leftPad = padLen / 2;
2355: rightPad = padLen - leftPad;
2356: break;
2357: }
2358:
2359: int padStringLen = pad.length();
2360:
2361: StringValue sb = string.createStringBuilder(string.length()
2362: + padLen);
2363:
2364: for (int i = 0; i < leftPad; i++)
2365: sb.append(pad.charAt(i % padStringLen));
2366:
2367: sb = sb.append(string);
2368:
2369: for (int i = 0; i < rightPad; i++)
2370: sb.append(pad.charAt(i % padStringLen));
2371:
2372: return sb;
2373: }
2374:
2375: /**
2376: * repeats a string
2377: *
2378: * @param string string to repeat
2379: * @param count number of times to repeat
2380: */
2381: public static Value str_repeat(StringValue string, int count) {
2382: StringValue sb = string.createStringBuilder(count
2383: * string.length());
2384:
2385: for (int i = 0; i < count; i++)
2386: sb = sb.append(string);
2387:
2388: return sb;
2389: }
2390:
2391: /**
2392: * replaces substrings.
2393: *
2394: * @param search search string
2395: * @param replace replacement string
2396: * @param subject replacement
2397: * @param count return value
2398: */
2399: public static Value str_replace(Env env, Value search,
2400: Value replace, Value subject, @Reference
2401: @Optional
2402: Value count) {
2403: return strReplace(env, search, replace, subject, count, false);
2404: }
2405:
2406: /**
2407: * replaces substrings.
2408: *
2409: * @param search search string
2410: * @param replace replacement string
2411: * @param subject replacement
2412: * @param count return value
2413: */
2414: private static Value strReplace(Env env, Value search,
2415: Value replace, Value subject, @Reference
2416: @Optional
2417: Value count, boolean isInsensitive) {
2418: count.set(LongValue.ZERO);
2419:
2420: if (subject.isNull())
2421: return env.createEmptyString();
2422:
2423: if (search.isNull())
2424: return subject;
2425:
2426: if (subject instanceof ArrayValue) {
2427: ArrayValue subjectArray = (ArrayValue) subject;
2428: ArrayValue resultArray = new ArrayValueImpl();
2429:
2430: for (Map.Entry<Value, Value> entry : subjectArray
2431: .entrySet()) {
2432: Value result = strReplaceImpl(env, search, replace,
2433: entry.getValue().toStringValue(), count,
2434: isInsensitive);
2435:
2436: resultArray.append(entry.getKey(), result);
2437: }
2438:
2439: return resultArray;
2440: } else {
2441: StringValue subjectString = subject.toStringValue();
2442:
2443: if (subjectString.length() == 0)
2444: return env.createEmptyString();
2445:
2446: return strReplaceImpl(env, search, replace, subjectString,
2447: count, isInsensitive);
2448: }
2449: }
2450:
2451: /**
2452: * replaces substrings.
2453: *
2454: * @param search search string
2455: * @param replace replacement string
2456: * @param subject replacement
2457: * @param count return value
2458: */
2459: private static Value strReplaceImpl(Env env, Value search,
2460: Value replace, StringValue subject, Value count,
2461: boolean isInsensitive) {
2462: if (!search.isArray()) {
2463: StringValue searchString = search.toStringValue();
2464:
2465: if (searchString.length() == 0)
2466: return subject;
2467:
2468: if (replace instanceof ArrayValue) {
2469: env.warning(L.l("Array to string conversion"));
2470: }
2471:
2472: subject = strReplaceImpl(env, searchString, replace
2473: .toStringValue(), subject, count, isInsensitive);
2474: } else if (replace instanceof ArrayValue) {
2475: ArrayValue searchArray = (ArrayValue) search;
2476: ArrayValue replaceArray = (ArrayValue) replace;
2477:
2478: Iterator<Value> searchIter = searchArray.values()
2479: .iterator();
2480: Iterator<Value> replaceIter = replaceArray.values()
2481: .iterator();
2482:
2483: while (searchIter.hasNext()) {
2484: Value searchItem = searchIter.next();
2485: Value replaceItem = replaceIter.next();
2486:
2487: if (replaceItem == null)
2488: replaceItem = NullValue.NULL;
2489:
2490: subject = strReplaceImpl(env, searchItem
2491: .toStringValue(), replaceItem.toStringValue(),
2492: subject, count, isInsensitive);
2493: }
2494: } else {
2495: ArrayValue searchArray = (ArrayValue) search;
2496:
2497: Iterator<Value> searchIter = searchArray.values()
2498: .iterator();
2499:
2500: while (searchIter.hasNext()) {
2501: Value searchItem = searchIter.next();
2502:
2503: subject = strReplaceImpl(env, searchItem
2504: .toStringValue(), replace.toStringValue(),
2505: subject, count, isInsensitive);
2506: }
2507: }
2508:
2509: return subject;
2510: }
2511:
2512: /**
2513: * replaces substrings.
2514: *
2515: * @param search search string
2516: * @param replace replacement string
2517: * @param subject replacement
2518: * @param countV return value
2519: */
2520: private static StringValue strReplaceImpl(Env env,
2521: StringValue search, StringValue replace,
2522: StringValue subject, Value countV, boolean isInsensitive) {
2523: long count = countV.toLong();
2524:
2525: int head = 0;
2526: int next;
2527:
2528: int searchLen = search.length();
2529:
2530: StringValue result = null;
2531:
2532: while ((next = indexOf(subject, search, head, isInsensitive)) >= head) {
2533: if (result == null)
2534: result = subject.createStringBuilder();
2535:
2536: result = result.append(subject, head, next);
2537: result = result.append(replace);
2538:
2539: if (head < next + searchLen)
2540: head = next + searchLen;
2541: else
2542: head += 1;
2543:
2544: count++;
2545: }
2546:
2547: if (count != 0) {
2548: countV.set(LongValue.create(count));
2549:
2550: if (head > 0 && head < subject.length())
2551: result = result.append(subject, head, subject.length());
2552:
2553: return result;
2554: } else
2555: return subject;
2556: }
2557:
2558: /**
2559: * Returns the next index.
2560: */
2561: private static int indexOf(StringValue subject, StringValue match,
2562: int head, boolean isInsensitive) {
2563: if (!isInsensitive)
2564: return subject.indexOf(match, head);
2565: else {
2566: int length = subject.length();
2567: int matchLen = match.length();
2568:
2569: if (matchLen <= 0)
2570: return -1;
2571:
2572: char ch = Character.toLowerCase(match.charAt(0));
2573: loop: for (; head + matchLen <= length; head++) {
2574: if (ch == Character.toLowerCase(subject.charAt(head))) {
2575: for (int i = 1; i < matchLen; i++) {
2576: if (Character.toLowerCase(subject.charAt(head
2577: + i)) != Character.toLowerCase(match
2578: .charAt(i)))
2579: continue loop;
2580: }
2581:
2582: return head;
2583: }
2584: }
2585:
2586: return -1;
2587: }
2588: }
2589:
2590: /**
2591: * rot13 conversion
2592: *
2593: * @param string string to convert
2594: */
2595: public static Value str_rot13(StringValue string) {
2596: if (string == null)
2597: return NullValue.NULL;
2598:
2599: StringValue sb = string.createStringBuilder(string.length());
2600:
2601: int len = string.length();
2602: for (int i = 0; i < len; i++) {
2603: char ch = string.charAt(i);
2604:
2605: if ('a' <= ch && ch <= 'z') {
2606: int off = ch - 'a';
2607:
2608: sb.append((char) ('a' + (off + 13) % 26));
2609: } else if ('A' <= ch && ch <= 'Z') {
2610: int off = ch - 'A';
2611:
2612: sb.append((char) ('A' + (off + 13) % 26));
2613: } else {
2614: sb.append(ch);
2615: }
2616: }
2617:
2618: return sb;
2619: }
2620:
2621: /**
2622: * shuffles a string
2623: */
2624: public static String str_shuffle(String string) {
2625: if (string == null)
2626: string = "";
2627:
2628: char[] chars = string.toCharArray();
2629:
2630: int length = chars.length;
2631:
2632: for (int i = 0; i < length; i++) {
2633: int rand = RandomUtil.nextInt(length);
2634:
2635: char temp = chars[rand];
2636: chars[rand] = chars[i];
2637: chars[i] = temp;
2638: }
2639:
2640: return new String(chars);
2641: }
2642:
2643: /**
2644: * split into an array
2645: *
2646: * @param string string to split
2647: * @param chunk chunk size
2648: */
2649: public static Value str_split(StringValue string, @Optional("1")
2650: int chunk) {
2651: ArrayValue array = new ArrayValueImpl();
2652:
2653: if (string.length() == 0) {
2654: array.put(string);
2655: return array;
2656: }
2657:
2658: int strLen = string.length();
2659:
2660: for (int i = 0; i < strLen; i += chunk) {
2661: Value value;
2662:
2663: if (i + chunk <= strLen) {
2664: value = string.substring(i, i + chunk);
2665: } else {
2666: value = string.substring(i);
2667: }
2668:
2669: array.put(new LongValue(i), value);
2670: }
2671:
2672: return array;
2673: }
2674:
2675: public static Value str_word_count(StringValue string, @Optional
2676: int format, @Optional
2677: String additionalWordCharacters) {
2678: if (format < 0 || format > 2)
2679: return NullValue.NULL;
2680:
2681: int strlen = string.length();
2682: boolean isAdditionalWordCharacters = additionalWordCharacters
2683: .length() > 0;
2684:
2685: ArrayValueImpl resultArray = null;
2686:
2687: if (format > 0)
2688: resultArray = new ArrayValueImpl();
2689:
2690: boolean isBetweenWords = true;
2691:
2692: int wordCount = 0;
2693:
2694: int lastWordStart = 0;
2695:
2696: for (int i = 0; i <= strlen; i++) {
2697: boolean isWordCharacter;
2698:
2699: if (i < strlen) {
2700: int ch = string.charAt(i);
2701:
2702: isWordCharacter = Character.isLetter(ch)
2703: || ch == '-'
2704: || ch == '\''
2705: || (isAdditionalWordCharacters && additionalWordCharacters
2706: .indexOf(ch) > -1);
2707: } else
2708: isWordCharacter = false;
2709:
2710: if (isWordCharacter) {
2711: if (isBetweenWords) {
2712: // starting a word
2713: isBetweenWords = false;
2714:
2715: lastWordStart = i;
2716: wordCount++;
2717: }
2718: } else {
2719: if (!isBetweenWords) {
2720: // finished a word
2721: isBetweenWords = true;
2722:
2723: if (format > 0) {
2724: StringValue word = string.substring(
2725: lastWordStart, i);
2726:
2727: if (format == 1)
2728: resultArray.append(word);
2729: else if (format == 2)
2730: resultArray.put(
2731: new LongValue(lastWordStart), word);
2732: }
2733: }
2734: }
2735: }
2736:
2737: if (resultArray == null)
2738: return LongValue.create(wordCount);
2739: else
2740: return resultArray;
2741: }
2742:
2743: /**
2744: * Case-insensitive comparison
2745: *
2746: * @param a left value
2747: * @param b right value
2748: * @return -1, 0, or 1
2749: */
2750: public static int strcasecmp(StringValue a, StringValue b) {
2751: int aLen = a.length();
2752: int bLen = b.length();
2753:
2754: for (int i = 0; i < aLen && i < bLen; i++) {
2755: char chA = a.charAt(i);
2756: char chB = b.charAt(i);
2757:
2758: if (chA == chB)
2759: continue;
2760:
2761: if (Character.isUpperCase(chA))
2762: chA = Character.toLowerCase(chA);
2763:
2764: if (Character.isUpperCase(chB))
2765: chB = Character.toLowerCase(chB);
2766:
2767: if (chA == chB)
2768: continue;
2769: else if (chA < chB)
2770: return -1;
2771: else
2772: return 1;
2773: }
2774:
2775: if (aLen == bLen)
2776: return 0;
2777: else if (aLen < bLen)
2778: return -1;
2779: else
2780: return 1;
2781: }
2782:
2783: /**
2784: * Case-sensitive comparison
2785: *
2786: * @param a left value
2787: * @param b right value
2788: * @return -1, 0, or 1
2789: */
2790: public static int strcmp(StringValue a, StringValue b) {
2791: int aLen = a.length();
2792: int bLen = b.length();
2793:
2794: for (int i = 0; i < aLen && i < bLen; i++) {
2795: char chA = a.charAt(i);
2796: char chB = b.charAt(i);
2797:
2798: if (chA == chB)
2799: continue;
2800:
2801: if (chA == chB)
2802: continue;
2803: else if (chA < chB)
2804: return -1;
2805: else
2806: return 1;
2807: }
2808:
2809: if (aLen == bLen)
2810: return 0;
2811: else if (aLen < bLen)
2812: return -1;
2813: else
2814: return 1;
2815: }
2816:
2817: /**
2818: * Finds the index of a substring
2819: *
2820: * @param env the calling environment
2821: */
2822: public static Value strchr(Env env, StringValue haystack,
2823: Value needle) {
2824: return strstr(env, haystack, needle);
2825: }
2826:
2827: /**
2828: * Locale-based comparison
2829: * XXX: i18n
2830: *
2831: * @param a left value
2832: * @param b right value
2833: * @return -1, 0, or 1
2834: */
2835: public static Value strcoll(String a, String b) {
2836: if (a == null)
2837: a = "";
2838:
2839: if (b == null)
2840: b = "";
2841:
2842: int cmp = a.compareTo(b);
2843:
2844: if (cmp == 0)
2845: return LongValue.ZERO;
2846: else if (cmp < 0)
2847: return LongValue.MINUS_ONE;
2848: else
2849: return LongValue.ONE;
2850: }
2851:
2852: /**
2853: * Finds the number of initial characters in <i>string</i> that do not match
2854: * one of the characters in <i>characters</i>
2855: *
2856: * @param string the string to search in
2857: * @param characters the character set
2858: * @param offset the starting offset
2859: * @param length the length
2860: *
2861: * @return the length of the match or FALSE if the offset or length are invalid
2862: */
2863: public static Value strcspn(StringValue string,
2864: StringValue characters, @Optional("0")
2865: int offset, @Optional("-2147483648")
2866: int length) {
2867: return strspnImpl(string, characters, offset, length, false);
2868: }
2869:
2870: /**
2871: * Removes tags from a string.
2872: *
2873: * @param string the string to remove
2874: * @param allowTags the allowable tags
2875: */
2876: public static StringValue strip_tags(StringValue string, @Optional
2877: String allowTags) {
2878: // XXX: allowTags is stubbed
2879:
2880: StringValue result = string
2881: .createStringBuilder(string.length());
2882:
2883: int len = string.length();
2884:
2885: for (int i = 0; i < len; i++) {
2886: char ch = string.charAt(i);
2887:
2888: if (ch != '<') {
2889: result.append(ch);
2890: continue;
2891: }
2892:
2893: for (i++; i < len; i++) {
2894: ch = string.charAt(i);
2895:
2896: if (ch == '>')
2897: break;
2898: }
2899: }
2900:
2901: return result;
2902: }
2903:
2904: /**
2905: * Returns the length of a string.
2906: *
2907: * @param value the argument value
2908: */
2909: public static Value strlen(Value value) {
2910: return LongValue.create(value.length());
2911: }
2912:
2913: /**
2914: * Case-insensitive comparison
2915: *
2916: * @param a left value
2917: * @param b right value
2918: * @return -1, 0, or 1
2919: */
2920: public static int strnatcasecmp(StringValue a, StringValue b) {
2921: return naturalOrderCompare(a, b, true);
2922: }
2923:
2924: /**
2925: * Case-sensitive comparison
2926: *
2927: * @param a left value
2928: * @param b right value
2929: * @return -1, 0, or 1
2930: */
2931: public static int strnatcmp(StringValue a, StringValue b) {
2932: return naturalOrderCompare(a, b, false);
2933: }
2934:
2935: /**
2936: * http://sourcefrog.net/projects/natsort/
2937: */
2938: private static int naturalOrderCompare(StringValue a,
2939: StringValue b, boolean ignoreCase) {
2940: SimpleStringReader aIn = new SimpleStringReader(a);
2941: SimpleStringReader bIn = new SimpleStringReader(b);
2942:
2943: int aChar = aIn.read();
2944: int bChar = bIn.read();
2945:
2946: if (aChar == -1 && bChar >= 0)
2947: return -1;
2948: else if (aChar >= 0 && bChar == -1)
2949: return 1;
2950:
2951: while (true) {
2952: while (Character.isWhitespace(aChar)) {
2953: aChar = aIn.read();
2954: }
2955:
2956: while (Character.isWhitespace(bChar)) {
2957: bChar = bIn.read();
2958: }
2959:
2960: if (aChar == -1 && bChar == -1) {
2961: return 0;
2962: }
2963:
2964: // leading zeros
2965: // '01' < '2'
2966: // '0a' > 'a'
2967: if (aChar == '0' && bChar == '0') {
2968: while (true) {
2969: aChar = aIn.read();
2970: bChar = bIn.read();
2971:
2972: if (aChar == '0' && bChar == '0') {
2973: continue;
2974: } else if (aChar == '0') {
2975: if ('1' <= bChar && bChar <= '9')
2976: return -1;
2977: else
2978: return 1;
2979: } else if (bChar == 0) {
2980: if ('1' <= aChar && aChar <= '9')
2981: return 1;
2982: else
2983: return -1;
2984: } else {
2985: break;
2986: }
2987: }
2988: } else if ('0' < aChar && aChar <= '9' && '0' < bChar
2989: && bChar <= '9') {
2990: int aInteger = aIn.readInt(aChar);
2991: int bInteger = bIn.readInt(bChar);
2992:
2993: if (aInteger > bInteger)
2994: return 1;
2995: else if (aInteger < bInteger)
2996: return -1;
2997: else {
2998: aChar = aIn.read();
2999: bChar = bIn.read();
3000: }
3001: }
3002:
3003: if (ignoreCase) {
3004: aChar = Character.toUpperCase(aChar);
3005: bChar = Character.toUpperCase(bChar);
3006: }
3007:
3008: if (aChar > bChar)
3009: return 1;
3010: else if (aChar < bChar)
3011: return -1;
3012:
3013: aChar = aIn.read();
3014: bChar = bIn.read();
3015:
3016: // trailing spaces
3017: // "abc " > "abc"
3018: if (aChar >= 0 && bChar == -1)
3019: return 1;
3020: else if (aChar == -1 && bChar >= 0)
3021: return -1;
3022: }
3023: }
3024:
3025: /**
3026: * Case-insensitive comparison
3027: *
3028: * @param a left value
3029: * @param b right value
3030: * @return -1, 0, or 1
3031: */
3032: public static int strncasecmp(StringValue a, StringValue b,
3033: int length) {
3034: int aLen = a.length();
3035: int bLen = b.length();
3036:
3037: for (int i = 0; i < length; i++) {
3038: if (aLen <= i)
3039: return -1;
3040: else if (bLen <= i)
3041: return 1;
3042:
3043: char aChar = Character.toUpperCase(a.charAt(i));
3044: char bChar = Character.toUpperCase(b.charAt(i));
3045:
3046: if (aChar < bChar)
3047: return -1;
3048: else if (bChar < aChar)
3049: return 1;
3050: }
3051:
3052: return 0;
3053: }
3054:
3055: /**
3056: * Case-sensitive comparison
3057: *
3058: * @param a left value
3059: * @param b right value
3060: * @return -1, 0, or 1
3061: */
3062: public static int strncmp(StringValue a, StringValue b, int length) {
3063: if (length < a.length())
3064: a = a.substring(0, length);
3065:
3066: if (length < b.length())
3067: b = b.substring(0, length);
3068:
3069: return strcmp(a, b);
3070: }
3071:
3072: /**
3073: * Returns a substring of <i>haystack</i> starting from the earliest
3074: * occurence of any char in <i>charList</i>
3075: *
3076: * @param haystack the string to search in
3077: * @param charList list of chars that would trigger match
3078: * @return substring, else FALSE
3079: */
3080: public static Value strpbrk(StringValue haystack,
3081: StringValue charList) {
3082: int len = haystack.length();
3083: int sublen = charList.length();
3084:
3085: for (int i = 0; i < len; i++) {
3086: for (int j = 0; j < sublen; j++) {
3087: if (haystack.charAt(i) == charList.charAt(j))
3088: return haystack.substring(i);
3089: }
3090: }
3091:
3092: return BooleanValue.FALSE;
3093: }
3094:
3095: /**
3096: * Returns the position of a substring.
3097: *
3098: * @param haystack the string to search in
3099: * @param needleV the string to search for
3100: */
3101: public static Value strpos(StringValue haystack, Value needleV,
3102: @Optional
3103: int offset) {
3104: StringValue needle;
3105:
3106: if (needleV instanceof StringValue)
3107: needle = (StringValue) needleV;
3108: else
3109: needle = StringValue.create((char) needleV.toInt());
3110:
3111: int pos = haystack.indexOf(needle, offset);
3112:
3113: if (pos < 0)
3114: return BooleanValue.FALSE;
3115: else
3116: return LongValue.create(pos);
3117: }
3118:
3119: /**
3120: * Returns the position of a substring, testing case insensitive.
3121: *
3122: * @param haystack the full argument to check
3123: * @param needleV the substring argument to check
3124: * @param offsetV optional starting position
3125: */
3126: public static Value stripos(StringValue haystack, Value needleV,
3127: @Optional
3128: int offset) {
3129: StringValue needle;
3130:
3131: if (needleV instanceof StringValue)
3132: needle = (StringValue) needleV;
3133: else
3134: needle = StringValue.create((char) needleV.toInt());
3135:
3136: haystack = haystack.toLowerCase();
3137: needle = needle.toLowerCase();
3138:
3139: int pos = haystack.indexOf(needle, offset);
3140:
3141: if (pos < 0)
3142: return BooleanValue.FALSE;
3143: else
3144: return LongValue.create(pos);
3145: }
3146:
3147: /**
3148: * Strip out the backslashes, recognizing the escape sequences, octal,
3149: * and hexadecimal representations.
3150: *
3151: * @param source the string to clean
3152: * @see #addcslashes
3153: */
3154: public static String stripcslashes(String source) {
3155: if (source == null)
3156: source = "";
3157:
3158: StringBuilder result = new StringBuilder(source.length());
3159:
3160: int length = source.length();
3161:
3162: for (int i = 0; i < length; i++) {
3163: int ch = source.charAt(i);
3164:
3165: if (ch == '\\') {
3166: i++;
3167:
3168: if (i == length)
3169: ch = '\\';
3170: else {
3171: ch = source.charAt(i);
3172:
3173: switch (ch) {
3174: case 'a':
3175: ch = 0x07;
3176: break;
3177: case 'b':
3178: ch = '\b';
3179: break;
3180: case 't':
3181: ch = '\t';
3182: break;
3183: case 'n':
3184: ch = '\n';
3185: break;
3186: case 'v':
3187: ch = 0xb;
3188: break;
3189: case 'f':
3190: ch = '\f';
3191: break;
3192: case 'r':
3193: ch = '\r';
3194: break;
3195: case 'x':
3196: // up to two digits for a hex number
3197: if (i + 1 == length)
3198: break;
3199:
3200: int digitValue = hexToDigit(source
3201: .charAt(i + 1));
3202:
3203: if (digitValue < 0)
3204: break;
3205:
3206: ch = digitValue;
3207: i++;
3208:
3209: if (i + 1 == length)
3210: break;
3211:
3212: digitValue = hexToDigit(source.charAt(i + 1));
3213:
3214: if (digitValue < 0)
3215: break;
3216:
3217: ch = ((ch << 4) | digitValue);
3218: i++;
3219:
3220: break;
3221: default:
3222: // up to three digits from 0 to 7 for an octal number
3223: digitValue = octToDigit((char) ch);
3224:
3225: if (digitValue < 0)
3226: break;
3227:
3228: ch = digitValue;
3229:
3230: if (i + 1 == length)
3231: break;
3232:
3233: digitValue = octToDigit(source.charAt(i + 1));
3234:
3235: if (digitValue < 0)
3236: break;
3237:
3238: ch = ((ch << 3) | digitValue);
3239: i++;
3240:
3241: if (i + 1 == length)
3242: break;
3243:
3244: digitValue = octToDigit(source.charAt(i + 1));
3245:
3246: if (digitValue < 0)
3247: break;
3248:
3249: ch = ((ch << 3) | digitValue);
3250: i++;
3251: }
3252: }
3253: } // if ch == '/'
3254:
3255: result.append((char) ch);
3256: }
3257:
3258: return result.toString();
3259: }
3260:
3261: /**
3262: * Strips out the backslashes.
3263: *
3264: * @param string the string to clean
3265: */
3266: public static StringValue stripslashes(StringValue string) {
3267: StringValue sb = string.createStringBuilder();
3268: int len = string.length();
3269:
3270: for (int i = 0; i < len; i++) {
3271: char ch = string.charAt(i);
3272:
3273: if (ch == '\\') {
3274: if (i + 1 < len) {
3275: sb.append(string.charAt(i + 1));
3276: i++;
3277: }
3278: } else
3279: sb.append(ch);
3280: }
3281:
3282: return sb;
3283: }
3284:
3285: /**
3286: * Finds the first instance of a substring, testing case insensitively
3287: *
3288: * @param haystack the string to search in
3289: * @param needleV the string to search for
3290: * @return the trailing match or FALSE
3291: */
3292: public static Value stristr(StringValue haystack, Value needleV) {
3293: CharSequence needleLower;
3294:
3295: if (needleV instanceof StringValue) {
3296: needleLower = ((StringValue) needleV).toLowerCase();
3297: } else {
3298: char lower = Character.toLowerCase((char) needleV.toLong());
3299:
3300: needleLower = String.valueOf(lower);
3301: }
3302:
3303: StringValue haystackLower = haystack.toLowerCase();
3304:
3305: int i = haystackLower.indexOf(needleLower);
3306:
3307: if (i >= 0)
3308: return haystack.substring(i);
3309: else
3310: return BooleanValue.FALSE;
3311: }
3312:
3313: /**
3314: * Finds the last instance of a substring
3315: *
3316: * @param haystack the string to search in
3317: * @param needleV the string to search for
3318: * @return the trailing match or FALSE
3319: */
3320: public static Value strrchr(StringValue haystack, Value needleV) {
3321: CharSequence needle;
3322:
3323: if (needleV instanceof StringValue)
3324: needle = (StringValue) needleV;
3325: else
3326: needle = String.valueOf((char) needleV.toLong());
3327:
3328: int i = haystack.lastIndexOf(needle);
3329:
3330: if (i > 0)
3331: return haystack.substring(i);
3332: else
3333: return BooleanValue.FALSE;
3334: }
3335:
3336: /**
3337: * Reverses a string.
3338: *
3339: */
3340: public static Value strrev(StringValue string) {
3341: StringValue sb = string.createStringBuilder(string.length());
3342:
3343: for (int i = string.length() - 1; i >= 0; i--) {
3344: sb.append(string.charAt(i));
3345: }
3346:
3347: return sb;
3348: }
3349:
3350: /**
3351: * Returns the position of a substring.
3352: *
3353: * @param haystack the string to search in
3354: * @param needleV the string to search for
3355: */
3356: public static Value strrpos(StringValue haystack, Value needleV,
3357: @Optional
3358: Value offsetV) {
3359: StringValue needle;
3360:
3361: if (needleV instanceof StringValue)
3362: needle = needleV.toStringValue();
3363: else
3364: needle = StringValue.create((char) needleV.toInt());
3365:
3366: int offset;
3367:
3368: if (offsetV instanceof DefaultValue)
3369: offset = haystack.length();
3370: else
3371: offset = offsetV.toInt();
3372:
3373: int pos = haystack.lastIndexOf(needle, offset);
3374:
3375: if (pos < 0)
3376: return BooleanValue.FALSE;
3377: else
3378: return new LongValue(pos);
3379: }
3380:
3381: /**
3382: * Returns the position of a substring, testing case-insensitive.
3383: *
3384: * @param haystack the full string to test
3385: * @param needleV the substring string to test
3386: * @param offsetV the optional offset to start searching
3387: */
3388: public static Value strripos(String haystack, Value needleV,
3389: @Optional
3390: Value offsetV) {
3391: if (haystack == null)
3392: haystack = "";
3393:
3394: String needle;
3395:
3396: if (needleV instanceof StringValue)
3397: needle = needleV.toString();
3398: else
3399: needle = String.valueOf((char) needleV.toInt());
3400:
3401: int offset;
3402:
3403: if (offsetV instanceof DefaultValue)
3404: offset = haystack.length();
3405: else
3406: offset = offsetV.toInt();
3407:
3408: haystack = haystack.toLowerCase();
3409: needle = needle.toLowerCase();
3410:
3411: int pos = haystack.lastIndexOf(needle, offset);
3412:
3413: if (pos < 0)
3414: return BooleanValue.FALSE;
3415: else
3416: return new LongValue(pos);
3417: }
3418:
3419: /**
3420: * Finds the number of initial characters in <i>string</i> that match one of
3421: * the characters in <i>characters</i>
3422: *
3423: * @param string the string to search in
3424: * @param characters the character set
3425: * @param offset the starting offset
3426: * @param length the length
3427: *
3428: * @return the length of the match or FALSE if the offset or length are invalid
3429: */
3430: public static Value strspn(StringValue string,
3431: StringValue characters, @Optional
3432: int offset, @Optional("-2147483648")
3433: int length) {
3434: return strspnImpl(string, characters, offset, length, true);
3435: }
3436:
3437: private static Value strspnImpl(StringValue string,
3438: StringValue characters, int offset, int length,
3439: boolean isMatch) {
3440: int strlen = string.length();
3441:
3442: // see also strcspn which uses the same procedure for determining
3443: // effective offset and length
3444: if (offset < 0) {
3445: offset += strlen;
3446:
3447: if (offset < 0)
3448: offset = 0;
3449: }
3450:
3451: if (offset > strlen)
3452: return BooleanValue.FALSE;
3453:
3454: if (length == -2147483648)
3455: length = strlen;
3456: else if (length < 0) {
3457: length += (strlen - offset);
3458:
3459: if (length < 0)
3460: length = 0;
3461: }
3462:
3463: int end = offset + length;
3464:
3465: if (strlen < end)
3466: end = strlen;
3467:
3468: int count = 0;
3469:
3470: for (; offset < end; offset++) {
3471: char ch = string.charAt(offset);
3472:
3473: boolean isPresent = characters.indexOf(ch) > -1;
3474:
3475: if (isPresent == isMatch)
3476: count++;
3477: else
3478: return LongValue.create(count);
3479: }
3480:
3481: return LongValue.create(count);
3482: }
3483:
3484: /**
3485: * Finds the first instance of a needle in haystack and returns
3486: * the portion of haystack from the beginning of needle to the end of haystack.
3487: *
3488: * @param env the calling environment
3489: * @param haystack the string to search in
3490: * @param needleV the string to search for, or the oridinal value of a character
3491: * @return the trailing match or FALSE if needle is not found
3492: */
3493: public static Value strstr(Env env, StringValue haystackV,
3494: Value needleV) {
3495: if (haystackV == null)
3496: haystackV = env.createEmptyString();
3497:
3498: String needle;
3499:
3500: if (needleV instanceof StringValue) {
3501: needle = needleV.toString();
3502: } else {
3503: needle = String.valueOf((char) needleV.toLong());
3504: }
3505:
3506: if (needle.length() == 0) {
3507: env.warning("empty needle");
3508: return BooleanValue.FALSE;
3509: }
3510:
3511: int i = haystackV.indexOf(needle);
3512:
3513: if (i >= 0)
3514: return haystackV.substring(i);
3515: else
3516: return BooleanValue.FALSE;
3517: }
3518:
3519: /**
3520: * Split a string into tokens using any character in another string as a delimiter.
3521: *
3522: * The first call establishes the string to search and the characters to use as tokens,
3523: * the first token is returned:
3524: * <pre>
3525: * strtok("hello, world", ", ")
3526: * => "hello"
3527: * </pre>
3528: *
3529: * Subsequent calls pass only the token characters, the next token is returned:
3530: * <pre>
3531: * strtok("hello, world", ", ")
3532: * => "hello"
3533: * strtok(", ")
3534: * => "world"
3535: * </pre>
3536: *
3537: * False is returned if there are no more tokens:
3538: * <pre>
3539: * strtok("hello, world", ", ")
3540: * => "hello"
3541: * strtok(", ")
3542: * => "world"
3543: * strtok(", ")
3544: * => false
3545: * </pre>
3546: *
3547: * Calls that pass two arguments reset the search string:
3548: * <pre>
3549: * strtok("hello, world", ", ")
3550: * => "hello"
3551: * strtok("goodbye, world", ", ")
3552: * => "goodbye"
3553: * strtok("world")
3554: * => false
3555: * strtok(", ")
3556: * => false
3557: * </pre>
3558: */
3559: public static Value strtok(Env env, StringValue string1, @Optional
3560: Value string2) {
3561: StringValue string;
3562: StringValue characters;
3563: int offset;
3564:
3565: if (string2.isNull()) {
3566: StringValue savedString = (StringValue) env
3567: .getSpecialValue("caucho.strtok_string");
3568: Integer savedOffset = (Integer) env
3569: .getSpecialValue("caucho.strtok_offset");
3570:
3571: string = savedString == null ? env.createEmptyString()
3572: : savedString;
3573: offset = savedOffset == null ? 0 : savedOffset;
3574: characters = string1;
3575: } else {
3576: string = string1;
3577: offset = 0;
3578: characters = string2.toStringValue();
3579:
3580: env.setSpecialValue("caucho.strtok_string", string);
3581: }
3582:
3583: int strlen = string.length();
3584:
3585: // skip any at beginning
3586: for (; offset < strlen; offset++) {
3587: char ch = string.charAt(offset);
3588:
3589: if (characters.indexOf(ch) < 0)
3590: break;
3591: }
3592:
3593: Value result;
3594:
3595: if (offset == strlen)
3596: result = BooleanValue.FALSE;
3597: else {
3598: int start = offset;
3599:
3600: offset++;
3601:
3602: // find end
3603: for (; offset < strlen; offset++) {
3604: char ch = string.charAt(offset);
3605:
3606: if (characters.indexOf(ch) > -1)
3607: break;
3608: }
3609:
3610: result = string.substring(start, offset);
3611: }
3612:
3613: env.setSpecialValue("caucho.strtok_offset", offset);
3614:
3615: return result;
3616: }
3617:
3618: /**
3619: * Converts to lower case.
3620: *
3621: * @param string the input string
3622: */
3623: public static StringValue strtolower(StringValue string) {
3624: return string.toLowerCase();
3625: }
3626:
3627: /**
3628: * Converts to upper case.
3629: *
3630: * @param string the input string
3631: */
3632: public static StringValue strtoupper(StringValue string) {
3633: return string.toUpperCase();
3634: }
3635:
3636: /**
3637: * Translates characters in a string to target values.
3638: *
3639: * @param string the source string
3640: * @param fromV the from characters
3641: * @param to the to character map
3642: */
3643: public static StringValue strtr(Env env, StringValue string,
3644: Value fromV, @Optional
3645: StringValue to) {
3646: if (fromV instanceof ArrayValue)
3647: return strtrArray(string, (ArrayValue) fromV);
3648:
3649: StringValue from = fromV.toStringValue();
3650:
3651: int len = from.length();
3652:
3653: if (to.length() < len)
3654: len = to.length();
3655:
3656: char[] map = new char[256];
3657: for (int i = len - 1; i >= 0; i--)
3658: map[from.charAt(i)] = to.charAt(i);
3659:
3660: StringValue sb = string.createStringBuilder();
3661:
3662: len = string.length();
3663: for (int i = 0; i < len; i++) {
3664: char ch = string.charAt(i);
3665:
3666: if (map[ch] != 0)
3667: sb.append(map[ch]);
3668: else
3669: sb.append(ch);
3670: }
3671:
3672: return sb;
3673: }
3674:
3675: /**
3676: * Translates characters in a string to target values.
3677: *
3678: * @param string the source string
3679: * @param map the character map
3680: */
3681: private static StringValue strtrArray(StringValue string,
3682: ArrayValue map) {
3683: int size = map.getSize();
3684:
3685: StringValue[] fromList = new StringValue[size];
3686: StringValue[] toList = new StringValue[size];
3687:
3688: Map.Entry<Value, Value>[] entryArray = new Map.Entry[size];
3689:
3690: int i = 0;
3691: for (Map.Entry<Value, Value> entry : map.entrySet()) {
3692: entryArray[i++] = entry;
3693: }
3694:
3695: // sort entries in descending fashion
3696: Arrays.sort(entryArray,
3697: new StrtrComparator<Map.Entry<Value, Value>>());
3698:
3699: boolean[] charSet = new boolean[256];
3700:
3701: for (i = 0; i < size; i++) {
3702: fromList[i] = entryArray[i].getKey().toStringValue();
3703: toList[i] = entryArray[i].getValue().toStringValue();
3704:
3705: charSet[fromList[i].charAt(0)] = true;
3706: }
3707:
3708: StringValue result = string.createStringBuilder();
3709: int len = string.length();
3710: int head = 0;
3711:
3712: top: while (head < len) {
3713: char ch = string.charAt(head);
3714:
3715: if (charSet.length <= ch || charSet[ch]) {
3716: fromLoop: for (i = 0; i < fromList.length; i++) {
3717: StringValue from = fromList[i];
3718: int fromLen = from.length();
3719:
3720: if (head + fromLen > len)
3721: continue;
3722:
3723: if (ch != from.charAt(0))
3724: continue;
3725:
3726: for (int j = 0; j < fromLen; j++) {
3727: if (string.charAt(head + j) != from.charAt(j))
3728: continue fromLoop;
3729: }
3730:
3731: result = result.append(toList[i]);
3732: head = head + fromLen;
3733:
3734: continue top;
3735: }
3736: }
3737:
3738: result.append(ch);
3739: head++;
3740: }
3741:
3742: return result;
3743: }
3744:
3745: /*
3746: * Comparator for sorting in descending fashion based on length.
3747: */
3748: static class StrtrComparator<T extends Map.Entry<Value, Value>>
3749: implements Comparator<T> {
3750: public int compare(T a, T b) {
3751: int lenA = a.getKey().length();
3752: int lenB = b.getKey().length();
3753:
3754: if (lenA < lenB)
3755: return 1;
3756: else if (lenA == lenB)
3757: return 0;
3758: else
3759: return -1;
3760: }
3761: }
3762:
3763: /**
3764: * Returns a substring
3765: *
3766: * @param env the calling environment
3767: * @param string the string
3768: * @param start the start offset
3769: * @param lenV the optional length
3770: */
3771: public static Value substr(Env env, StringValue string, int start,
3772: @Optional
3773: Value lenV) {
3774: int strLen = string.length();
3775: if (start < 0)
3776: start = strLen + start;
3777:
3778: if (start < 0 || strLen < start)
3779: return BooleanValue.FALSE;
3780:
3781: if (lenV instanceof DefaultValue) {
3782: return string.substring(start);
3783: } else {
3784: int len = lenV.toInt();
3785: int end;
3786:
3787: if (len < 0)
3788: end = strLen + len;
3789: else
3790: end = start + len;
3791:
3792: if (end <= start)
3793: return string.getEmptyString();
3794: else if (strLen <= end)
3795: return string.substring(start);
3796: else
3797: return string.substring(start, end);
3798: }
3799: }
3800:
3801: public static Value substr_compare(Env env, StringValue mainStr,
3802: StringValue str, int offset, @Optional
3803: Value lenV, @Optional
3804: boolean isCaseInsensitive) {
3805: int strLen = mainStr.length();
3806:
3807: if (lenV.toInt() > strLen || offset > strLen
3808: || lenV.toInt() + offset > strLen) {
3809: return BooleanValue.FALSE;
3810: }
3811:
3812: mainStr = substr(env, mainStr, offset, lenV).toStringValue();
3813:
3814: if (isCaseInsensitive)
3815: return LongValue.create(strcasecmp(mainStr, str));
3816: else
3817: return LongValue.create(strcmp(mainStr, str));
3818: }
3819:
3820: public static Value substr_count(Env env, StringValue haystackV,
3821: StringValue needleV, @Optional("0")
3822: int offset, @Optional("-1")
3823: int length) {
3824: String haystack = haystackV.toString();
3825:
3826: String needle = needleV.toString();
3827:
3828: if (needle.length() == 0) {
3829: env.warning(L.l("empty substr"));
3830: return BooleanValue.FALSE;
3831: }
3832:
3833: int haystackLength = haystack.length();
3834:
3835: if (offset < 0 || offset > haystackLength) {
3836: env.warning(L.l("offset `{0}' out of range", offset));
3837: return BooleanValue.FALSE;
3838: }
3839:
3840: if (length > -1) {
3841: if (offset + length > haystackLength) {
3842: env.warning(L.l("length `{0}' out of range", length));
3843: return BooleanValue.FALSE;
3844: } else
3845: haystackLength = offset + length;
3846: }
3847:
3848: int needleLength = needle.length();
3849:
3850: int count = 0;
3851:
3852: int end = haystackLength - needleLength + 1;
3853:
3854: for (int i = offset; i < end; i++) {
3855: if (haystack.startsWith(needle, i)) {
3856: count++;
3857: i += needleLength;
3858: }
3859: }
3860:
3861: return new LongValue(count);
3862: }
3863:
3864: /**
3865: * Replaces a substring with a replacement
3866: *
3867: * @param subjectV a string to modify, or an array of strings to modify
3868: * @param replacement the replacement string
3869: * @param startV the start offset
3870: * @param lengthV the optional length
3871: */
3872: public static Value substr_replace(Value subjectV,
3873: StringValue replacement, Value startV, @Optional
3874: Value lengthV) {
3875: int start = 0;
3876: int length = Integer.MAX_VALUE / 2;
3877:
3878: if (!(lengthV.isNull() || lengthV.isArray()))
3879: length = lengthV.toInt();
3880:
3881: if (!(startV.isNull() || startV.isArray()))
3882: start = startV.toInt();
3883:
3884: Iterator<Value> startIterator = startV.isArray() ? ((ArrayValue) startV)
3885: .values().iterator()
3886: : null;
3887:
3888: Iterator<Value> lengthIterator = lengthV.isArray() ? ((ArrayValue) lengthV)
3889: .values().iterator()
3890: : null;
3891:
3892: if (subjectV.isArray()) {
3893: ArrayValue resultArray = new ArrayValueImpl();
3894:
3895: ArrayValue subjectArray = (ArrayValue) subjectV;
3896:
3897: for (Value value : subjectArray.values()) {
3898:
3899: if (lengthIterator != null && lengthIterator.hasNext())
3900: length = lengthIterator.next().toInt();
3901:
3902: if (startIterator != null && startIterator.hasNext())
3903: start = startIterator.next().toInt();
3904:
3905: Value result = substrReplaceImpl(value.toStringValue(),
3906: replacement, start, length);
3907:
3908: resultArray.append(result);
3909: }
3910:
3911: return resultArray;
3912: } else {
3913: if (lengthIterator != null && lengthIterator.hasNext())
3914: length = lengthIterator.next().toInt();
3915:
3916: if (startIterator != null && startIterator.hasNext())
3917: start = startIterator.next().toInt();
3918:
3919: return substrReplaceImpl(subjectV.toStringValue(),
3920: replacement, start, length);
3921: }
3922: }
3923:
3924: private static Value substrReplaceImpl(StringValue string,
3925: StringValue replacement, int start, int len) {
3926: int strLen = string.length();
3927:
3928: if (start > strLen)
3929: start = strLen;
3930: else if (start < 0)
3931: start = Math.max(strLen + start, 0);
3932:
3933: int end;
3934:
3935: if (len < 0)
3936: end = Math.max(strLen + len, start);
3937: else
3938: end = Math.min(start + len, strLen);
3939:
3940: StringValue result = string.createStringBuilder();
3941:
3942: result = result.append(string.substring(0, start));
3943: result = result.append(replacement);
3944: result = result.append(string.substring(end));
3945:
3946: return result;
3947: }
3948:
3949: /**
3950: * Removes leading and trailing whitespace.
3951: *
3952: * @param string the string to be trimmed
3953: * @param characters optional set of characters to trim
3954: * @return the trimmed string
3955: */
3956: public static Value trim(Env env, StringValue string, @Optional
3957: String characters) {
3958: boolean[] trim;
3959:
3960: if (characters == null || characters.equals(""))
3961: trim = TRIM_WHITESPACE;
3962: else
3963: trim = parseCharsetBitmap(characters.toString());
3964:
3965: int len = string.length();
3966:
3967: int head = 0;
3968: for (; head < len; head++) {
3969: char ch = string.charAt(head);
3970:
3971: if (ch >= 256 || !trim[ch]) {
3972: break;
3973: }
3974: }
3975:
3976: int tail = len - 1;
3977: for (; tail >= 0; tail--) {
3978: char ch = string.charAt(tail);
3979:
3980: if (ch >= 256 || !trim[ch]) {
3981: break;
3982: }
3983: }
3984:
3985: if (tail < head)
3986: return env.createEmptyString();
3987: else {
3988: return (StringValue) string.subSequence(head, tail + 1);
3989: }
3990: }
3991:
3992: /**
3993: * Uppercases the first character
3994: *
3995: * @param string the input string
3996: */
3997: public static String ucfirst(String string) {
3998: if (string == null)
3999: string = "";
4000:
4001: if (string.length() == 0)
4002: return string;
4003:
4004: return Character.toUpperCase(string.charAt(0))
4005: + string.substring(1);
4006: }
4007:
4008: /**
4009: * Uppercases the first character of each word
4010: *
4011: * @param string the input string
4012: */
4013: public static String ucwords(String string) {
4014: if (string == null)
4015: string = "";
4016:
4017: int strLen = string.length();
4018:
4019: boolean isStart = true;
4020: StringBuilder sb = new StringBuilder();
4021:
4022: for (int i = 0; i < strLen; i++) {
4023: char ch = string.charAt(i);
4024:
4025: switch (ch) {
4026: case ' ':
4027: case '\t':
4028: case '\r':
4029: case '\n':
4030: isStart = true;
4031: sb.append(ch);
4032: break;
4033: default:
4034: if (isStart)
4035: sb.append(Character.toUpperCase(ch));
4036: else
4037: sb.append(ch);
4038: isStart = false;
4039: break;
4040: }
4041: }
4042:
4043: return sb.toString();
4044: }
4045:
4046: /**
4047: * Formatted strings with array arguments
4048: *
4049: * @param format the format string
4050: * @param array the arguments to apply to the format string
4051: */
4052: public static int vprintf(Env env, StringValue format, @NotNull
4053: ArrayValue array) {
4054: Value[] args;
4055:
4056: if (array != null) {
4057: args = new Value[array.getSize()];
4058: int i = 0;
4059: for (Value value : array.values())
4060: args[i++] = value;
4061: } else
4062: args = new Value[0];
4063:
4064: return printf(env, format, args);
4065: }
4066:
4067: /**
4068: * Formatted strings with array arguments
4069: *
4070: * @param format the format string
4071: * @param array the arguments to apply to the format string
4072: */
4073: public static Value vsprintf(StringValue format, @NotNull
4074: ArrayValue array) {
4075: Value[] args;
4076:
4077: if (array != null) {
4078: args = new Value[array.getSize()];
4079: int i = 0;
4080: for (Value value : array.values())
4081: args[i++] = value;
4082: } else
4083: args = new Value[0];
4084:
4085: return sprintf(format, args);
4086: }
4087:
4088: /**
4089: * Wraps a string to the given number of characters.
4090: *
4091: * @param string the input string
4092: * @param width the width
4093: * @param breakString the break string
4094: * @param cut if true, break on exact match
4095: */
4096: public static String wordwrap(String string, @Optional("75")
4097: int width, @Optional("'\n'")
4098: String breakString, @Optional
4099: boolean cut) {
4100: if (string == null)
4101: string = "";
4102:
4103: if (breakString == null)
4104: breakString = "";
4105:
4106: int len = string.length();
4107: int head = 0;
4108:
4109: StringBuilder sb = new StringBuilder();
4110: while (head + width < len) {
4111: int newline = string.indexOf('\n', head + 1);
4112:
4113: int tail = head + width;
4114:
4115: if (newline > 0 && newline < tail) {
4116: if (sb.length() > 0)
4117: sb.append(breakString);
4118:
4119: sb.append(string.substring(head, newline));
4120: head = newline + 1;
4121: continue;
4122: }
4123:
4124: if (!cut) {
4125: for (; head < tail
4126: && !Character.isWhitespace(string.charAt(tail)); tail--) {
4127: }
4128:
4129: if (head == tail)
4130: tail = head + width;
4131: }
4132:
4133: if (sb.length() > 0)
4134: sb.append(breakString);
4135:
4136: sb.append(string.substring(head, tail));
4137:
4138: head = tail;
4139:
4140: if (!cut && head < len
4141: && Character.isWhitespace(string.charAt(head)))
4142: head++;
4143: }
4144:
4145: if (head < len) {
4146: if (sb.length() > 0)
4147: sb.append(breakString);
4148:
4149: sb.append(string.substring(head));
4150: }
4151:
4152: return sb.toString();
4153: }
4154:
4155: /**
4156: * Returns true if the character is a whitespace character.
4157: */
4158: protected static boolean isWhitespace(char ch) {
4159: return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
4160: }
4161:
4162: /**
4163: * Returns the uppercase equivalent of the caharacter
4164: */
4165: protected static char toUpperCase(char ch) {
4166: if (ch >= 'a' && ch <= 'z')
4167: return (char) ('A' + (ch - 'a'));
4168: else
4169: return ch;
4170: }
4171:
4172: /**
4173: * Converts an integer digit to a uuencoded char.
4174: */
4175: protected static char toUUChar(int d) {
4176: if (d == 0)
4177: return (char) 0x60;
4178: else
4179: return (char) (0x20 + (d & 0x3f));
4180: }
4181:
4182: protected static char toHexChar(int d) {
4183: d &= 0xf;
4184:
4185: if (d < 10)
4186: return (char) (d + '0');
4187: else
4188: return (char) (d - 10 + 'a');
4189: }
4190:
4191: protected static char toUpperHexChar(int d) {
4192: d &= 0xf;
4193:
4194: if (d < 10)
4195: return (char) (d + '0');
4196: else
4197: return (char) (d - 10 + 'A');
4198: }
4199:
4200: protected static int hexToDigit(char ch) {
4201: if ('0' <= ch && ch <= '9')
4202: return ch - '0';
4203: else if ('a' <= ch && ch <= 'f')
4204: return ch - 'a' + 10;
4205: else if ('A' <= ch && ch <= 'F')
4206: return ch - 'A' + 10;
4207: else
4208: return -1;
4209: }
4210:
4211: protected static int octToDigit(char ch) {
4212: if ('0' <= ch && ch <= '7')
4213: return ch - '0';
4214: else
4215: return -1;
4216: }
4217:
4218: abstract static class PrintfSegment {
4219: abstract public void apply(StringValue sb, Value[] args);
4220:
4221: static boolean hasIndex(String format) {
4222: return format.indexOf('$') >= 0;
4223: }
4224:
4225: static int getIndex(String format) {
4226: int value = 0;
4227:
4228: for (int i = 0; i < format.length(); i++) {
4229: char ch;
4230:
4231: if ('0' <= (ch = format.charAt(i)) && ch <= '9')
4232: value = 10 * value + ch - '0';
4233: else
4234: break;
4235: }
4236:
4237: return value - 1;
4238: }
4239:
4240: static String getIndexFormat(String format) {
4241: int p = format.indexOf('$');
4242:
4243: return '%' + format.substring(p + 1);
4244: }
4245: }
4246:
4247: static class TextPrintfSegment extends PrintfSegment {
4248: private final char[] _text;
4249:
4250: TextPrintfSegment(StringBuilder text) {
4251: _text = new char[text.length()];
4252:
4253: text.getChars(0, _text.length, _text, 0);
4254: }
4255:
4256: public void apply(StringValue sb, Value[] args) {
4257: sb.append(_text, 0, _text.length);
4258: }
4259: }
4260:
4261: static class LongPrintfSegment extends PrintfSegment {
4262: private final String _format;
4263: private final int _index;
4264:
4265: private LongPrintfSegment(String format, int index) {
4266: _format = format;
4267: _index = index;
4268: }
4269:
4270: static PrintfSegment create(String format, int index) {
4271: if (hasIndex(format)) {
4272: index = getIndex(format);
4273: format = getIndexFormat(format);
4274: } else {
4275: format = '%' + format;
4276: index = index;
4277: }
4278:
4279: // php/115b
4280: // strip out illegal precision specifier from phpBB vote function
4281: if (format.length() > 1 && format.charAt(1) == '.') {
4282: int i;
4283:
4284: for (i = 2; i < format.length(); i++) {
4285: char ch = format.charAt(i);
4286:
4287: if (!('0' <= ch && ch <= '9'))
4288: break;
4289: }
4290:
4291: format = '%' + format.substring(i);
4292: }
4293:
4294: if (format.charAt(format.length() - 1) == 'x'
4295: || format.charAt(format.length() - 1) == 'X') {
4296: HexPrintfSegment hex = HexPrintfSegment.create(format,
4297: index);
4298:
4299: if (hex != null)
4300: return hex;
4301: }
4302:
4303: return new LongPrintfSegment(format, index);
4304: }
4305:
4306: public void apply(StringValue sb, Value[] args) {
4307: long value;
4308:
4309: if (_index < args.length)
4310: value = args[_index].toLong();
4311: else
4312: value = 0;
4313:
4314: sb.append(String.format(_format, value));
4315: }
4316: }
4317:
4318: static class HexPrintfSegment extends PrintfSegment {
4319: private final int _index;
4320: private final int _min;
4321: private final char _pad;
4322: private boolean _isUpper;
4323:
4324: HexPrintfSegment(int index, int min, char pad, boolean isUpper) {
4325: _index = index;
4326: _min = min;
4327: _pad = pad;
4328: _isUpper = isUpper;
4329: }
4330:
4331: static HexPrintfSegment create(String format, int index) {
4332: int length = format.length();
4333: int offset = 1;
4334:
4335: boolean isUpper = format.charAt(length - 1) == 'X';
4336: char pad = ' ';
4337:
4338: if (format.charAt(offset) == ' ') {
4339: pad = ' ';
4340: offset++;
4341: } else if (format.charAt(offset) == '0') {
4342: pad = '0';
4343: offset++;
4344: }
4345:
4346: int min = 0;
4347: for (; offset < length - 1; offset++) {
4348: char ch = format.charAt(offset);
4349:
4350: if ('0' <= ch && ch <= '9')
4351: min = 10 * min + ch - '0';
4352: else
4353: return null;
4354: }
4355:
4356: return new HexPrintfSegment(index, min, pad, isUpper);
4357: }
4358:
4359: public void apply(StringValue sb, Value[] args) {
4360: long value;
4361:
4362: if (_index >= 0 && _index < args.length)
4363: value = args[_index].toLong();
4364: else
4365: value = 0;
4366:
4367: int digits = 0;
4368:
4369: long shift = value;
4370: for (int i = 0; i < 16; i++) {
4371: if (shift != 0)
4372: digits = i;
4373:
4374: shift = shift >>> 4;
4375: }
4376:
4377: for (int i = digits + 1; i < _min; i++)
4378: sb.append(_pad);
4379:
4380: for (; digits >= 0; digits--) {
4381: int digit = (int) (value >>> (4 * digits)) & 0xf;
4382:
4383: if (digit <= 9)
4384: sb.append((char) ('0' + digit));
4385: else if (_isUpper)
4386: sb.append((char) ('A' + digit - 10));
4387: else
4388: sb.append((char) ('a' + digit - 10));
4389: }
4390: }
4391: }
4392:
4393: static class DoublePrintfSegment extends PrintfSegment {
4394: private final String _format;
4395: private final int _index;
4396:
4397: DoublePrintfSegment(String format, int index) {
4398: if (hasIndex(format)) {
4399: _index = getIndex(format);
4400: _format = getIndexFormat(format);
4401: } else {
4402: _format = '%' + format;
4403: _index = index;
4404: }
4405: }
4406:
4407: public void apply(StringValue sb, Value[] args) {
4408: double value;
4409:
4410: if (_index < args.length)
4411: value = args[_index].toDouble();
4412: else
4413: value = 0;
4414:
4415: sb.append(String.format(_format, value));
4416: }
4417: }
4418:
4419: static class StringPrintfSegment extends PrintfSegment {
4420: private final char[] _prefix;
4421: private final int _min;
4422: private final int _max;
4423: private final boolean _isLeft;
4424: private final boolean _isUpper;
4425: private final char _pad;
4426: protected final int _index;
4427:
4428: StringPrintfSegment(StringBuilder prefix, boolean isLeft,
4429: boolean isZero, boolean isUpper, String format,
4430: int index) {
4431: _prefix = new char[prefix.length()];
4432:
4433: _isLeft = isLeft;
4434: _isUpper = isUpper;
4435:
4436: _pad = isZero ? '0' : ' ';
4437:
4438: prefix.getChars(0, _prefix.length, _prefix, 0);
4439:
4440: if (hasIndex(format)) {
4441: index = getIndex(format);
4442: format = getIndexFormat(format);
4443: }
4444:
4445: int i = 0;
4446: int len = format.length();
4447:
4448: int min = 0;
4449: int max = Integer.MAX_VALUE;
4450: char ch = ' ';
4451:
4452: for (; i < len && '0' <= (ch = format.charAt(i))
4453: && ch <= '9'; i++) {
4454: min = 10 * min + ch - '0';
4455: }
4456:
4457: if (ch == '.') {
4458: max = 0;
4459:
4460: for (i++; i < len && '0' <= (ch = format.charAt(i))
4461: && ch <= '9'; i++) {
4462: max = 10 * max + ch - '0';
4463: }
4464: }
4465:
4466: _min = min;
4467: _max = max;
4468:
4469: _index = index;
4470: }
4471:
4472: public void apply(StringValue sb, Value[] args) {
4473: sb.append(_prefix, 0, _prefix.length);
4474:
4475: String value = toValue(args);
4476:
4477: int len = value.length();
4478:
4479: if (_max < len) {
4480: value = value.substring(0, _max);
4481: len = _max;
4482: }
4483:
4484: if (_isUpper)
4485: value = value.toUpperCase();
4486:
4487: if (!_isLeft) {
4488: for (int i = len; i < _min; i++) {
4489: sb.append(_pad);
4490: }
4491: }
4492:
4493: sb.append(value);
4494:
4495: if (_isLeft) {
4496: for (int i = len; i < _min; i++) {
4497: sb.append(_pad);
4498: }
4499: }
4500: }
4501:
4502: String toValue(Value[] args) {
4503: if (_index < args.length)
4504: return args[_index].toString();
4505: else
4506: return "";
4507: }
4508: }
4509:
4510: static class CharPrintfSegment extends StringPrintfSegment {
4511: CharPrintfSegment(StringBuilder prefix, boolean isLeft,
4512: boolean isZero, boolean isUpper, String format,
4513: int index) {
4514: super (prefix, isLeft, isZero, isUpper, format, index);
4515: }
4516:
4517: String toValue(Value[] args) {
4518: if (args.length <= _index)
4519: return "";
4520:
4521: Value v = args[_index];
4522:
4523: if (v.isLongConvertible())
4524: return String.valueOf((char) v.toLong());
4525: else
4526: return v.charValueAt(0).toString();
4527: }
4528: }
4529:
4530: static class SimpleStringReader {
4531: StringValue _str;
4532:
4533: int _length;
4534: int _index;
4535:
4536: SimpleStringReader(StringValue str) {
4537: _str = str;
4538: _length = str.length();
4539: _index = 0;
4540: }
4541:
4542: int read() {
4543: if (_index < _length)
4544: return _str.charAt(_index++);
4545: else
4546: return -1;
4547: }
4548:
4549: int peek() {
4550: if (_index < _length)
4551: return _str.charAt(_index);
4552: else
4553: return -1;
4554:
4555: }
4556:
4557: int readInt(int currChar) {
4558: int number = currChar - '0';
4559:
4560: while (true) {
4561: currChar = peek();
4562:
4563: if ('0' <= currChar && currChar <= '9') {
4564: number = number * 10 + currChar - '0';
4565: _index++;
4566: } else {
4567: break;
4568: }
4569: }
4570:
4571: return number;
4572: }
4573: }
4574:
4575: static {
4576: DEFAULT_DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols();
4577: DEFAULT_DECIMAL_FORMAT_SYMBOLS.setDecimalSeparator('.');
4578: DEFAULT_DECIMAL_FORMAT_SYMBOLS.setGroupingSeparator(',');
4579: DEFAULT_DECIMAL_FORMAT_SYMBOLS.setZeroDigit('0');
4580: }
4581: }
|