com.caucho.quercus.lib.string.StringModule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of quercus Show documentation
Show all versions of quercus Show documentation
A PHP engine implemented in 100% Java
/*
* Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.quercus.lib.string;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Currency;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.caucho.quercus.QuercusException;
import com.caucho.quercus.QuercusModuleException;
import com.caucho.quercus.annotation.Expect;
import com.caucho.quercus.annotation.NotNull;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.annotation.Reference;
import com.caucho.quercus.annotation.UsesSymbolTable;
import com.caucho.quercus.env.ArrayValue;
import com.caucho.quercus.env.ArrayValueImpl;
import com.caucho.quercus.env.BooleanValue;
import com.caucho.quercus.env.DefaultValue;
import com.caucho.quercus.env.DoubleValue;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.LocaleInfo;
import com.caucho.quercus.env.LongValue;
import com.caucho.quercus.env.NullValue;
import com.caucho.quercus.env.QuercusLocale;
import com.caucho.quercus.env.StringValue;
import com.caucho.quercus.env.UnexpectedValue;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.env.Var;
import com.caucho.quercus.lib.file.BinaryOutput;
import com.caucho.quercus.lib.file.FileModule;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.util.CharBuffer;
import com.caucho.util.FreeList;
import com.caucho.util.IntSet;
import com.caucho.util.L10N;
import com.caucho.util.RandomUtil;
import com.caucho.vfs.ByteToChar;
import com.caucho.vfs.Path;
import com.caucho.vfs.TempBuffer;
/**
* PHP functions implemented from the string module
*/
public class StringModule extends AbstractQuercusModule {
private static final Logger log =
Logger.getLogger(StringModule.class.getName());
private static final L10N L = new L10N(StringModule.class);
public static final int CRYPT_SALT_LENGTH = 2;
public static final int CRYPT_STD_DES = 0;
public static final int CRYPT_EXT_DES = 0;
public static final int CRYPT_MD5 = 0;
public static final int CRYPT_BLOWFISH = 0;
public static final int CHAR_MAX = 1;
public static final int LC_CTYPE = 1;
public static final int LC_NUMERIC = 2;
public static final int LC_TIME = 3;
public static final int LC_COLLATE = 4;
public static final int LC_MONETARY = 5;
public static final int LC_ALL = 6;
public static final int LC_MESSAGES = 7;
public static final int STR_PAD_LEFT = 1;
public static final int STR_PAD_RIGHT = 0;
public static final int STR_PAD_BOTH = 2;
private static final DecimalFormatSymbols DEFAULT_DECIMAL_FORMAT_SYMBOLS;
private static final BigInteger BIG_TEN = new BigInteger("10");
private static final BigInteger BIG_2_64
= BigInteger.ONE.shiftLeft(64);
private static final FreeList _md5FreeList
= new FreeList(16);
/**
* Escapes a string using C syntax.
*
* @see #stripcslashes
*
* @param source the source string to convert
* @param characters the set of characters to convert
* @return the escaped string
*/
public static StringValue addcslashes(Env env,
StringValue source,
String characters)
{
if (characters == null) {
characters = "";
}
boolean []bitmap = parseCharsetBitmap(env, characters);
int length = source.length();
StringValue sb = source.createStringBuilder(length * 5 / 4);
for (int i = 0; i < length; i++) {
char ch = source.charAt(i);
if (ch >= 256 || ! bitmap[ch]) {
sb.append(ch);
continue;
}
switch (ch) {
case 0x07:
sb.append("\\a");
break;
case '\b':
sb.append("\\b");
break;
case '\t':
sb.append("\\t");
break;
case '\n':
sb.append("\\n");
break;
case 0xb:
sb.append("\\v");
break;
case '\f':
sb.append("\\f");
break;
case '\r':
sb.append("\\r");
break;
default:
if (ch < 0x20 || ch >= 0x7f) {
// save as octal
sb.append("\\");
sb.append((char) ('0' + ((ch >> 6) & 7)));
sb.append((char) ('0' + ((ch >> 3) & 7)));
sb.append((char) ('0' + ((ch) & 7)));
break;
}
else {
sb.append("\\");
sb.append(ch);
break;
}
}
}
return sb;
}
/**
* Parses the cslashes bitmap returning an actual bitmap.
*
* @param charset the bitmap string
* @return the actual bitmap
*/
private static boolean []parseCharsetBitmap(Env env, String charset)
{
boolean []bitmap = new boolean[256];
int length = charset.length();
for (int i = 0; i < length; i++) {
char ch = charset.charAt(i);
// XXX: the bitmap eventual might need to deal with unicode
if (ch >= 256)
continue;
bitmap[ch] = true;
if (length <= i + 3)
continue;
if (charset.charAt(i + 1) != '.' || charset.charAt(i + 2) != '.')
continue;
char last = charset.charAt(i + 3);
if (last < ch) {
env.warning(L.l("character set range is invalid: {0}..{1}",
ch, last));
continue;
}
i += 3;
for (; ch <= last; ch++) {
bitmap[ch] = true;
}
// XXX: handling of '@'?
}
return bitmap;
}
/**
* Escapes a string for db characters.
*
* @param source the source string to convert
* @return the escaped string
*/
public static StringValue addslashes(StringValue source)
{
StringValue sb = source.createStringBuilder(source.length() * 5 / 4);
int length = source.length();
for (int i = 0; i < length; i++) {
char ch = source.charAt(i);
switch (ch) {
case 0x0:
sb.append("\\0");
break;
case '\'':
sb.append("\\'");
break;
case '\"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
default:
sb.append(ch);
break;
}
}
return sb;
}
/**
* Converts a binary value to a hex value.
*/
public static StringValue bin2hex(Env env, InputStream is)
{
try {
StringValue sb = env.createUnicodeBuilder();
int ch;
while ((ch = is.read()) >= 0) {
int d = (ch >> 4) & 0xf;
if (d < 10)
sb.append((char) (d + '0'));
else
sb.append((char) (d + 'a' - 10));
d = (ch) & 0xf;
if (d < 10)
sb.append((char) (d + '0'));
else
sb.append((char) (d + 'a' - 10));
}
return sb;
} catch (IOException e) {
throw new QuercusModuleException(e);
}
}
/**
* Alias of rtrim. Removes trailing whitespace.
*
* @param env the quercus environment
* @param str the string to be trimmed
* @param charset optional set of characters to trim
* @return the trimmed string
*/
public static StringValue chop(Env env,
StringValue str,
@Optional String charset)
{
return rtrim(env, str, charset);
}
/**
* converts a number to its character equivalent
*
* @param value the integer value
*
* @return the string equivalent
*/
public static StringValue chr(Env env, long value)
{
if (! env.isUnicodeSemantics())
value = value & 0xFF;
StringValue sb = env.createUnicodeBuilder();
sb.append((char) value);
return sb;
}
/**
* Splits a string into chunks
*
* @param body the body string
* @param chunkLen the optional chunk length, defaults to 76
* @param end the optional end value, defaults to "\r\n"
*/
public static String chunk_split(String body,
@Optional("76") int chunkLen,
@Optional("\"\\r\\n\"") String end)
{
if (body == null)
body = "";
if (end == null)
end = "";
if (chunkLen < 1) // XXX: real exn
throw new IllegalArgumentException(L.l("bad value {0}", chunkLen));
StringBuilder sb = new StringBuilder();
int i = 0;
for (; i + chunkLen <= body.length(); i += chunkLen) {
sb.append(body.substring(i, i + chunkLen));
sb.append(end);
}
if (i < body.length()) {
sb.append(body.substring(i));
sb.append(end);
}
return sb.toString();
}
/**
* Converts from one cyrillic set to another.
*
* This implementation does nothing, because quercus stores strings as
* 16 bit unicode.
*/
public static String convert_cyr_string(Env env,
String str,
String from,
String to)
{
env.stub("convert_cyr_string");
return str;
}
public static Value convert_uudecode(Env env, StringValue source)
{
try {
int length = source.length();
if (length == 0)
return BooleanValue.FALSE;
ByteToChar byteToChar = env.getByteToChar();
int i = 0;
while (i < length) {
int ch1 = source.charAt(i++);
if (ch1 == 0x60 || ch1 == 0x20)
break;
else if (ch1 < 0x20 || 0x5f < ch1)
continue;
int sublen = ch1 - 0x20;
while (sublen > 0) {
int code;
code = ((source.charAt(i++) - 0x20) & 0x3f) << 18;
code += ((source.charAt(i++) - 0x20) & 0x3f) << 12;
code += ((source.charAt(i++) - 0x20) & 0x3f) << 6;
code += ((source.charAt(i++) - 0x20) & 0x3f);
byteToChar.addByte(code >> 16);
if (sublen > 1)
byteToChar.addByte(code >> 8);
if (sublen > 2)
byteToChar.addByte(code);
sublen -= 3;
}
}
return env.createString(byteToChar.getConvertedString());
} catch (IOException e) {
throw new QuercusModuleException(e);
}
}
/**
* uuencode a string.
*/
public static Value convert_uuencode(StringValue source)
{
if (source.length() == 0)
return BooleanValue.FALSE;
StringValue result = source.createStringBuilder();
int i = 0;
int length = source.length();
while (i < length) {
int sublen = length - i;
if (45 < sublen)
sublen = 45;
result.append((char) (sublen + 0x20));
int end = i + sublen;
while (i < end) {
int code = source.charAt(i++) << 16;
if (i < length)
code += source.charAt(i++) << 8;
if (i < length)
code += source.charAt(i++);
result.append(toUUChar(((code >> 18) & 0x3f)));
result.append(toUUChar(((code >> 12) & 0x3f)));
result.append(toUUChar(((code >> 6) & 0x3f)));
result.append(toUUChar(((code) & 0x3f)));
}
result.append('\n');
}
result.append((char) 0x60);
result.append('\n');
return result;
}
/**
* Returns an array of information about the characters.
*/
public static Value count_chars(StringValue data,
@Optional("0") int mode)
{
int []count = new int[256];
int length = data.length();
for (int i = 0; i < length; i++) {
count[data.charAt(i) & 0xff] += 1;
}
switch (mode) {
case 0:
{
ArrayValue result = new ArrayValueImpl();
for (int i = 0; i < count.length; i++) {
result.put(LongValue.create(i), LongValue.create(count[i]));
}
return result;
}
case 1:
{
ArrayValue result = new ArrayValueImpl();
for (int i = 0; i < count.length; i++) {
if (count[i] > 0)
result.put(LongValue.create(i), LongValue.create(count[i]));
}
return result;
}
case 2:
{
ArrayValue result = new ArrayValueImpl();
for (int i = 0; i < count.length; i++) {
if (count[i] == 0)
result.put(LongValue.create(i), LongValue.create(count[i]));
}
return result;
}
case 3:
{
StringValue sb = data.createStringBuilder();
for (int i = 0; i < count.length; i++) {
if (count[i] > 0)
sb.append((char) i);
}
return sb;
}
case 4:
{
StringValue sb = data.createStringBuilder();
for (int i = 0; i < count.length; i++) {
if (count[i] == 0)
sb.append((char) i);
}
return sb;
}
default:
return BooleanValue.FALSE;
}
}
/**
* Calculates the crc32 value for a string
*
* @param str the string value
*
* @return the crc32 hash
*/
public static long crc32(StringValue str)
{
return str.getCrc32Value();
}
public static String crypt(String string, @Optional String salt)
{
if (string == null)
string = "";
if (salt == null || salt.equals("")) {
salt = ("" + Crypt.resultToChar(RandomUtil.nextInt(0x40))
+ Crypt.resultToChar(RandomUtil.nextInt(0x40)));
}
return Crypt.crypt(string, salt);
}
// XXX: echo
/**
* Explodes a string into an array
*
* @param separator the separator string
* @param string the string to be exploded
* @param limit the max number of elements
* @return an array of exploded values
*/
public static Value explode(Env env,
StringValue separator,
StringValue string,
@Optional("0x7fffffff") long limit)
{
if (separator.length() == 0) {
env.warning(L.l("Delimiter is empty"));
return BooleanValue.FALSE;
}
int head = 0;
ArrayValue array = new ArrayValueImpl();
int separatorLength = separator.length();
int stringLength = string.length();
long ulimit;
if (limit >= 0) {
ulimit = limit;
} else {
ulimit = 0x7fffffff;
}
for (int i = 0; i < stringLength; ++i) {
if (ulimit <= array.getSize() + 1) {
break;
}
if (string.regionMatches(i, separator, 0, separatorLength)) {
StringValue chunk = string.substring(head, i);
array.append(chunk);
head = i + separatorLength;
i = head - 1;
}
}
StringValue chunk = string.substring(head);
array.append(chunk);
while (array.getSize() > 0 && limit++ < 0) {
array.pop(env);
}
return array;
}
/**
* Use printf style formatting to write a string to a file.
* @param fd the file to write to
* @param format the format string
* @param args the valujes to apply to the format string
*/
public static Value fprintf(Env env,
@NotNull BinaryOutput os,
StringValue format,
Value []args)
{
Value value = sprintf(env, format, args);
return FileModule.fwrite(env, os, value.toInputStream(),
Integer.MAX_VALUE);
}
/**
* Converts a binary value to a hex value.
*/
public static StringValue hex2bin(Env env, StringValue s)
{
StringValue sb = env.createBinaryBuilder();
int len = s.length();
for (int i = 0; i + 1 < len; i += 2) {
int d1 = hexDigit(s.charAt(i));
int d2 = hexDigit(s.charAt(i + 1));
int d = d1 * 16 + d2;
sb.append((char) d);
}
return sb;
}
private static int hexDigit(int c)
{
if ('0' <= c && c <= '9') {
return c - '0';
}
else if ('a' <= c && c <= 'f') {
return c - 'a' + 10;
}
else if ('A' <= c && c <= 'F') {
return c - 'A' + 10;
}
else {
return 0;
}
}
/**
* implodes an array into a string
*
* @param glueV the separator string
* @param piecesV the array to be imploded
*
* @return a string of imploded values
*/
public static Value implode(Env env,
Value glueV,
@Optional Value piecesV)
{
StringValue glue;
ArrayValue pieces;
if ((piecesV.isArray() && glueV.isArray())
|| glueV.isArray()) {
pieces = glueV.toArrayValue(env);
glue = piecesV.toStringValue(env);
}
else if (piecesV.isArray()) {
pieces = piecesV.toArrayValue(env);
glue = glueV.toStringValue(env);
}
else {
env.warning(L.l("neither argument to implode is an array: {0}, {1}",
glueV.getClass().getName(), piecesV.getClass().getName()));
return NullValue.NULL;
}
StringValue sb = glue.createStringBuilder();
boolean isFirst = true;
Iterator iter = pieces.getValueIterator(env);
while (iter.hasNext()) {
if (! isFirst)
sb = sb.append(glue);
isFirst = false;
sb = sb.append(iter.next());
}
return sb;
}
/**
* implodes an array into a string
*
* @param glueV the separator string
* @param piecesV the array to be imploded
*
* @return a string of imploded values
*/
public static Value join(Env env,
Value glueV,
Value piecesV)
{
return implode(env, glueV, piecesV);
}
// XXX: lcfirst
// XXX: levenshtein
/**
* Gets locale-specific symbols.
* XXX: locale charset
*/
public static ArrayValue localeconv(Env env)
{
ArrayValueImpl array = new ArrayValueImpl();
QuercusLocale money = env.getLocaleInfo().getMonetary();
Locale locale = money.getLocale();
DecimalFormatSymbols decimal = new DecimalFormatSymbols(locale);
Currency currency = NumberFormat.getInstance(locale).getCurrency();
array.put(env.createString("decimal_point"),
env.createString(decimal.getDecimalSeparator()));
array.put(env.createString("thousands_sep"),
env.createString(decimal.getGroupingSeparator()));
//array.put("grouping", "");
array.put(env.createString("int_curr_symbol"),
env.createString(decimal.getInternationalCurrencySymbol()));
array.put(env.createString("currency_symbol"),
env.createString(decimal.getCurrencySymbol()));
array.put(env.createString("mon_decimal_point"),
env.createString(decimal.getMonetaryDecimalSeparator()));
array.put(env.createString("mon_thousands_sep"),
env.createString(decimal.getGroupingSeparator()));
//array.put("mon_grouping", "");
array.put(env.createString("positive_sign"), env.getEmptyString());
array.put(env.createString("negative_sign"),
env.createString(decimal.getMinusSign()));
array.put(env.createString("int_frac_digits"),
LongValue.create(currency.getDefaultFractionDigits()));
array.put(env.createString("frac_digits"),
LongValue.create(currency.getDefaultFractionDigits()));
//array.put("p_cs_precedes", "");
//array.put("p_sep_by_space", "");
//array.put("n_cs_precedes", "");
//array.put("n_sep_by_space", "");
//array.put("p_sign_posn", "");
//array.put("n_sign_posn", "");
return array;
}
/**
* Removes leading whitespace.
*
* @param string the string to be trimmed
* @param characters optional set of characters to trim
* @return the trimmed string
*/
public static StringValue ltrim(Env env,
StringValue string,
@Optional String characters)
{
if (characters == null)
characters = "";
boolean []trim;
if (characters.equals(""))
trim = TRIM_WHITESPACE;
else
trim = parseCharsetBitmap(env, characters);
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
if (ch >= 256 || ! trim[ch]) {
if (i == 0)
return string;
else
return string.substring(i);
}
}
return env.getEmptyString();
}
/**
* returns the md5 hash
*
* @param source the string
* @param rawOutput if true, return the raw binary
*
* @return a string of imploded values
*/
public static Value md5(Env env,
InputStream is,
@Optional boolean rawOutput)
{
TempBuffer tempBuffer = TempBuffer.allocate();
try {
MessageDigest md = _md5FreeList.allocate();
if (md == null) {
md = MessageDigest.getInstance("MD5");
}
md.reset();
byte[] buffer = tempBuffer.getBuffer();
// XXX: iso-8859-1
while (true) {
int len = is.read(buffer, 0, buffer.length);
if (len < 0) {
break;
}
md.update(buffer, 0, len);
}
byte []digest = md.digest();
_md5FreeList.free(md);
return hashToValue(env, digest, rawOutput);
}
catch (Exception e) {
throw new QuercusModuleException(e);
}
finally {
TempBuffer.free(tempBuffer);
}
}
/**
* returns the md5 hash
*
* @param source the string
* @param rawOutput if true, return the raw binary
*
* @return a string of imploded values
*/
public static Value md5_file(Env env,
Path source,
@Optional boolean rawOutput)
{
TempBuffer tempBuffer = TempBuffer.allocate();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
InputStream is = null;
try {
is = source.openRead();
byte[] buffer = tempBuffer.getBuffer();
while (true) {
int len = is.read(buffer, 0, buffer.length);
if (len < 0) {
break;
}
md.update(buffer, 0, len);
}
byte []digest = md.digest();
return hashToValue(env, digest, rawOutput);
}
catch (IOException e) {
log.log(Level.FINE, e.toString(), e);
return BooleanValue.FALSE;
}
finally {
try {
if (is != null)
is.close();
}
catch (IOException e) {
}
}
}
catch (Exception e) {
throw new QuercusModuleException(e);
}
finally {
TempBuffer.free(tempBuffer);
}
}
private static StringValue digestToString(Env env, byte []digest)
{
StringValue sb = env.createUnicodeBuilder();
for (int i = 0; i < digest.length; i++) {
int d1 = (digest[i] >> 4) & 0xf;
int d2 = (digest[i] & 0xf);
sb.append(toHexChar(d1));
sb.append(toHexChar(d2));
}
return sb;
}
/**
* Returns the metaphone of a string.
* This implementation produces identical results to the php version,
* which does contain some bugs.
*/
public static String metaphone(String string)
{
if (string == null)
string = "";
int length = string.length();
int index = 0;
char ch = 0;
// ignore everything up until first letter
for (; index < length; index++) {
ch = toUpperCase(string.charAt(index));
if ('A' <= ch && ch <= 'Z')
break;
}
if (index == length)
return "";
int lastIndex = length - 1;
StringBuilder result = new StringBuilder(length);
// special case first letter
char nextCh
= index < lastIndex
? toUpperCase(string.charAt(index + 1))
: 0;
switch (ch) {
case 'A':
if (nextCh == 'E') {
result.append('E');
index += 2;
}
else {
result.append('A');
index += 1;
}
break;
case 'E':
case 'I':
case 'O':
case 'U':
result.append(ch);
index += 1;
break;
case 'G':
case 'K':
case 'P':
if (nextCh == 'N') {
result.append('N');
index += 2;
}
break;
case 'W':
if (nextCh == 'H' || nextCh == 'R') {
result.append(nextCh);
index += 2;
}
else {
switch (nextCh) {
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
result.append('W');
index += 2;
break;
default:
break;
}
}
break;
case 'X':
result.append('S');
index += 1;
break;
default:
break;
}
// the rest of the letters
char prevCh;
for (; index < length; index++) {
if (index > 0)
prevCh = toUpperCase(string.charAt(index - 1));
else
prevCh = 0;
ch = toUpperCase(string.charAt(index));
if (ch < 'A' || ch > 'Z')
continue;
if (ch == prevCh && ch != 'C')
continue;
if (index + 1 < length)
nextCh = toUpperCase(string.charAt(index + 1));
else
nextCh = 0;
char nextnextCh;
if (index + 2 < length)
nextnextCh = toUpperCase(string.charAt(index + 2));
else
nextnextCh = 0;
switch (ch) {
case 'B':
if (prevCh != 'M')
result.append('B');
break;
case 'C':
switch (nextCh) {
case 'E':
case 'I':
case 'Y':
// makesoft
if (nextCh == 'I' && nextnextCh == 'A') {
result.append('X');
}
else if (prevCh == 'S') {
}
else {
result.append('S');
}
break;
default:
if (nextCh == 'H') {
result.append('X');
index++;
}
else {
result.append('K');
}
break;
}
break;
case 'D':
if (nextCh == 'G') {
switch (nextnextCh) {
case 'E':
case 'I':
case 'Y':
// makesoft
result.append('J');
index++;
break;
default:
result.append('T');
break;
}
}
else
result.append('T');
break;
case 'G':
if (nextCh == 'H') {
boolean isSilent = false;
if (index - 3 >= 0) {
char prev3Ch = toUpperCase(string.charAt(index - 3));
switch (prev3Ch) {
// noghtof
case 'B':
case 'D':
case 'H':
isSilent = true;
break;
default:
break;
}
}
if (!isSilent) {
if (index - 4 >= 0) {
char prev4Ch = toUpperCase(string.charAt(index - 4));
isSilent = (prev4Ch == 'H');
}
}
if (!isSilent) {
result.append('F');
index++;
}
}
else if (nextCh == 'N') {
char nextnextnextCh;
if (index + 3 < length)
nextnextnextCh = toUpperCase(string.charAt(index + 3));
else
nextnextnextCh = 0;
if (nextnextCh < 'A' || nextnextCh > 'Z') {
}
else if (nextnextCh == 'E' && nextnextnextCh == 'D') {
}
else
result.append('K');
}
else if (prevCh == 'G') {
result.append('K');
}
else {
switch (nextCh) {
case 'E':
case 'I':
case 'Y':
// makesoft
result.append('J');
break;
default:
result.append('K');
break;
}
}
break;
case 'H':
case 'W':
case 'Y':
switch (nextCh) {
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
// followed by a vowel
if (ch == 'H') {
switch (prevCh) {
case 'C':
case 'G':
case 'P':
case 'S':
case 'T':
// affecth
break;
default:
result.append('H');
break;
}
}
else
result.append(ch);
break;
default:
// not followed by a vowel
break;
}
break;
case 'K':
if (prevCh != 'C')
result.append('K');
break;
case 'P':
if (nextCh == 'H')
result.append('F');
else
result.append('P');
break;
case 'Q':
result.append('K');
break;
case 'S':
if (nextCh == 'I' && (nextnextCh == 'O' || nextnextCh == 'A')) {
result.append('X');
}
else if (nextCh == 'H') {
result.append('X');
index++;
}
else
result.append('S');
break;
case 'T':
if (nextCh == 'I' && (nextnextCh == 'O' || nextnextCh == 'A')) {
result.append('X');
}
else if (nextCh == 'H') {
result.append('0');
index++;
}
else
result.append('T');
break;
case 'V':
result.append('F');
break;
case 'X':
result.append('K');
result.append('S');
break;
case 'Z':
result.append('S');
break;
case 'F':
case 'J':
case 'L':
case 'M':
case 'N':
case 'R':
result.append(ch);
break;
default:
break;
}
}
return result.toString();
}
/**
* Returns a formatted money value.
* XXX: locale charset
*
* @param format the format
* @param value the value
*
* @return a string of formatted values
*/
public static String money_format(Env env, String format, double value)
{
QuercusLocale monetary = env.getLocaleInfo().getMonetary();
Locale locale = monetary.getLocale();
return NumberFormat.getCurrencyInstance(locale).format(value);
}
// XXX: nl_langinfo
// XXX: nl2br
/**
* Returns a formatted number.
*
* @param value the value
* @param decimals the number of decimals
* @param pointValue the decimal point string
* @param groupValue the thousands separator
*
* @return a string of the formatted number
*/
public static String number_format(Env env,
double value,
@Optional int decimals,
@Optional Value pointValue,
@Optional Value groupValue)
{
boolean isGroupDefault = (groupValue instanceof DefaultValue);
boolean isPointDefault = (pointValue instanceof DefaultValue);
if (!isPointDefault && isGroupDefault) {
env.warning(L.l("wrong parameter count"));
return null;
}
String pattern;
char point = '.';
if (! pointValue.isNull()) {
String pointString = pointValue.toString();
point = (pointString.length() == 0) ? 0 : pointString.charAt(0);
}
char group = ',';
if (! groupValue.isNull()) {
String groupString = groupValue.toString();
group = (groupString.length() == 0) ? 0 : groupString.charAt(0);
}
if (decimals > 0) {
StringBuilder patternBuilder = new StringBuilder(6 + decimals);
patternBuilder.append(group == 0 ? "###0." : "#,##0.");
for (int i = 0; i < decimals; i++)
patternBuilder.append('0');
pattern = patternBuilder.toString();
}
else {
pattern = group == 0 ? "###0" : "#,##0";
}
DecimalFormatSymbols decimalFormatSymbols;
if (point == '.' && group == ',')
decimalFormatSymbols = DEFAULT_DECIMAL_FORMAT_SYMBOLS;
else {
decimalFormatSymbols = new DecimalFormatSymbols();
decimalFormatSymbols.setDecimalSeparator(point);
decimalFormatSymbols.setGroupingSeparator(group);
decimalFormatSymbols.setZeroDigit('0');
}
DecimalFormat format = new DecimalFormat(pattern, decimalFormatSymbols);
String result = format.format(value);
if (point == 0 && decimals > 0) {
// no way to get DecimalFormat to output nothing for the point,
// so remove it here
int i = result.lastIndexOf(point);
return result.substring(0, i) + result.substring(i + 1, result.length());
}
else
return result;
}
/**
* Converts the first character to an integer.
*
* @param string the string to be converted
*
* @return the integer value
*/
public static long ord(StringValue string)
{
if (string.length() == 0)
return 0;
else {
return string.charAt(0);
}
}
/**
* Parses the string as a query string.
*
* @param env the calling environment
* @param str the query string
* @param array the optional result array
*/
@UsesSymbolTable
public static Value parse_str(Env env, StringValue str,
@Optional @Reference Value ref)
{
boolean isRef = ref instanceof Var;
ArrayValue result = null;
if (isRef) {
result = new ArrayValueImpl();
ref.set(result);
}
else if (ref instanceof ArrayValue) {
result = (ArrayValue) ref;
isRef = true;
}
else
result = new ArrayValueImpl();
return StringUtility.parseStr(env,
str,
result,
isRef,
env.getHttpInputEncoding(),
false);
}
/**
* Prints the string.
*
* @param env the quercus environment
* @param value the string to print
*/
public static long print(Env env, Value value)
{
value.print(env);
return 1;
}
/**
* print to the output with a formatter
*
* @param env the quercus environment
* @param format the format string
* @param args the format arguments
*
* @return the formatted string
*/
public static int printf(Env env, StringValue format, Value []args)
{
Value str = sprintf(env, format, args);
str.print(env);
return str.length();
}
/**
* Converts a RFC2045 quoted printable string to a string.
*/
// XXX: i18n
public static String quoted_printable_decode(String str)
{
if (str == null)
str = "";
StringBuilder sb = new StringBuilder();
int length = str.length();
for (int i = 0; i < length; i++) {
char ch = str.charAt(i);
if (33 <= ch && ch <= 60)
sb.append(ch);
else if (62 <= ch && ch <= 126)
sb.append(ch);
else if (ch == ' ' || ch == '\t') {
if (i + 1 < str.length()
&& (str.charAt(i + 1) == '\r' || str.charAt(i + 1) == '\n')) {
sb.append('=');
sb.append(toUpperHexChar(ch >> 4));
sb.append(toUpperHexChar(ch));
}
else
sb.append(ch);
}
else if (ch == '\r' || ch == '\n') {
sb.append(ch);
}
else {
sb.append('=');
sb.append(toUpperHexChar(ch >> 4));
sb.append(toUpperHexChar(ch));
}
}
return sb.toString();
}
/**
* Escapes meta characters.
*
* @param string the string to be quoted
*
* @return the quoted
*/
public static Value quotemeta(StringValue string)
{
int len = string.length();
StringValue sb = string.createStringBuilder(len * 5 / 4);
for (int i = 0; i < len; i++) {
char ch = string.charAt(i);
switch (ch) {
case '.': case '\\': case '+': case '*': case '?':
case '[': case '^': case ']': case '(': case ')': case '$':
sb.append("\\");
sb.append(ch);
break;
default:
sb.append(ch);
break;
}
}
return sb;
}
private static final boolean[]TRIM_WHITESPACE = new boolean[256];
static {
TRIM_WHITESPACE['\0'] = true;
TRIM_WHITESPACE['\b'] = true;
TRIM_WHITESPACE[' '] = true;
TRIM_WHITESPACE['\t'] = true;
TRIM_WHITESPACE['\r'] = true;
TRIM_WHITESPACE['\n'] = true;
TRIM_WHITESPACE[0x0B] = true;
}
/**
* Removes trailing whitespace.
*
* @param env the quercus environment
* @param string the string to be trimmed
* @param characters optional set of characters to trim
* @return the trimmed string
*/
public static StringValue rtrim(Env env,
StringValue string,
@Optional String characters)
{
if (characters == null)
characters = "";
boolean []trim;
if (characters.equals(""))
trim = TRIM_WHITESPACE;
else
trim = parseCharsetBitmap(env, characters);
for (int i = string.length() - 1; i >= 0; i--) {
char ch = string.charAt(i);
if (ch >= 256 || ! trim[ch]) {
if (i == string.length())
return string;
else
return string.substring(0, i + 1);
}
}
return env.getEmptyString();
}
/**
* Sets locale configuration.
*/
public static Value setlocale(Env env,
int category,
Value localeArg,
Value []fallback)
{
LocaleInfo localeInfo = env.getLocaleInfo();
if (localeArg instanceof ArrayValue) {
for (Value value : ((ArrayValue) localeArg).values()) {
QuercusLocale locale = setLocale(localeInfo,
category,
value.toString());
if (locale != null)
return env.createString(locale.toString());
}
}
else {
QuercusLocale locale = setLocale(localeInfo,
category,
localeArg.toString());
if (locale != null)
return env.createString(locale.toString());
}
for (int i = 0; i < fallback.length; i++) {
QuercusLocale locale = setLocale(localeInfo,
category,
fallback[i].toString());
if (locale != null)
return env.createString(locale.toString());
}
return BooleanValue.FALSE;
}
/**
* Sets locale configuration.
*/
private static QuercusLocale setLocale(LocaleInfo localeInfo,
int category,
String localeName)
{
QuercusLocale locale = findLocale(localeName);
if (locale == null)
return null;
switch (category) {
case LC_ALL:
localeInfo.setAll(locale);
return localeInfo.getMessages();
case LC_COLLATE:
localeInfo.setCollate(locale);
return localeInfo.getCollate();
case LC_CTYPE:
localeInfo.setCtype(locale);
return localeInfo.getCtype();
case LC_MONETARY:
localeInfo.setMonetary(locale);
return localeInfo.getMonetary();
case LC_NUMERIC:
localeInfo.setNumeric(locale);
return localeInfo.getNumeric();
case LC_TIME:
localeInfo.setTime(locale);
return localeInfo.getTime();
case LC_MESSAGES:
localeInfo.setMessages(locale);
return localeInfo.getMessages();
default:
return null;
}
}
/*
* Example locale: fr_FR.UTF-8@euro, french (on Windows)
* (French, France, UTF-8, with euro currency support)
*/
private static QuercusLocale findLocale(String localeName)
{
String language;
String country;
String charset = null;
String variant = null;
CharBuffer sb = CharBuffer.allocate();
int len = localeName.length();
int i = 0;
char ch = 0;
while (i < len && (ch = localeName.charAt(i++)) != '-' && ch != '_') {
sb.append(ch);
}
language = sb.toString();
sb.clear();
while (i < len && (ch = localeName.charAt(i)) != '.' && ch != '@') {
sb.append(ch);
i++;
}
if (ch == '.')
i++;
country = sb.toString();
sb.clear();
while (i < len && (ch = localeName.charAt(i)) != '@') {
sb.append(ch);
i++;
}
if (sb.length() > 0)
charset = sb.toString();
if (i + 1 < len)
variant = localeName.substring(i + 1);
Locale locale;
// java versions >= 1.5 should automatically use the euro sign
if (variant != null && ! variant.equalsIgnoreCase("euro"))
locale = new Locale(language, country, variant);
else if (country != null)
locale = new Locale(language, country);
else
locale = new Locale(language);
if (isValidLocale(locale))
return new QuercusLocale(locale, charset);
else
return null;
}
/**
* Returns true if the locale is supported.
*/
private static boolean isValidLocale(Locale locale)
{
Locale []validLocales = Locale.getAvailableLocales();
for (int i = 0; i < validLocales.length; i++) {
if (validLocales[i].equals(locale)) {
return true;
}
}
return false;
}
/**
* returns the md5 hash
*
* @param source the string
* @param rawOutput if true, return the raw binary
*
* @return a string of imploded values
*/
public static Value sha1(Env env,
InputStream is,
@Optional boolean rawOutput)
{
TempBuffer tempBuffer = TempBuffer.allocate();
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
// XXX: iso-8859-1
byte[] buffer = tempBuffer.getBuffer();
while (true) {
int len = is.read(buffer, 0, buffer.length);
if (len < 0) {
break;
}
md.update(buffer, 0, len);
}
byte []digest = md.digest();
return hashToValue(env, digest, rawOutput);
}
catch (Exception e) {
throw new QuercusException(e);
}
finally {
TempBuffer.free(tempBuffer);
}
}
/**
* returns the md5 hash
*
* @param source the string
* @param rawOutput if true, return the raw binary
*
* @return a string of imploded values
*/
public static Value sha1_file(Env env,
Path source,
@Optional boolean rawOutput)
{
TempBuffer tempBuffer = TempBuffer.allocate();
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
InputStream is = null;
try {
is = source.openRead();
byte[] buffer = tempBuffer.getBuffer();
while (true) {
int len = is.read(buffer, 0, buffer.length);
if (len < 0) {
break;
}
md.update(buffer, 0, len);
}
byte []digest = md.digest();
return hashToValue(env, digest, rawOutput);
}
catch (IOException e) {
log.log(Level.FINE, e.toString(), e);
return BooleanValue.FALSE;
}
finally {
try {
if (is != null) {
is.close();
}
}
catch (IOException e) {
}
}
}
catch (Exception e) {
throw new QuercusException(e);
}
finally {
TempBuffer.free(tempBuffer);
}
}
private static Value hashToValue(Env env, byte []bytes, boolean isBinary)
{
if (isBinary) {
StringValue v = env.createBinaryBuilder();
v.append(bytes, 0, bytes.length);
return v;
}
else {
StringValue v = env.createUnicodeBuilder();
for (int i = 0; i < bytes.length; i++) {
int ch = bytes[i];
int d1 = (ch >> 4) & 0xf;
int d2 = (ch) & 0xf;
if (d1 < 10)
v.append((char) ('0' + d1));
else
v.append((char) ('a' + d1 - 10));
if (d2 < 10)
v.append((char) ('0' + d2));
else
v.append((char) ('a' + d2 - 10));
}
return v;
}
}
// XXX: similar_text
private static final char []SOUNDEX_VALUES = "01230120022455012623010202"
.toCharArray();
public static Value soundex(StringValue string)
{
int length = string.length();
if (length == 0)
return BooleanValue.FALSE;
StringValue result = string.createStringBuilder();
int count = 0;
char lastCode = 0;
for (int i = 0; i < length && count < 4; i++) {
char ch = toUpperCase(string.charAt(i));
if ('A' <= ch && ch <= 'Z') {
char code = SOUNDEX_VALUES[ch - 'A'];
if (count == 0) {
result.append(ch);
count++;
}
else if (code != '0' && code != lastCode) {
result.append(code);
count++;
}
lastCode = code;
}
}
for (; count < 4; count++) {
result.append('0');
}
return result;
}
/**
* Print to a string with a formatter
*
* @param format the format string
* @param args the format arguments
*
* @return the formatted string
*/
public static Value sprintf(Env env, StringValue format, Value []args)
{
ArrayList segments = parsePrintfFormat(env, format);
StringValue sb = format.createStringBuilder();
for (PrintfSegment segment : segments) {
if (! segment.apply(env, sb, args))
return BooleanValue.FALSE;
}
return sb;
}
private static ArrayList parsePrintfFormat(Env env,
StringValue format)
{
ArrayList segments = new ArrayList();
StringBuilder sb = new StringBuilder();
StringBuilder flags = new StringBuilder();
int length = format.length();
int index = 0;
for (int i = 0; i < length; i++) {
char ch = format.charAt(i);
if (i + 1 < length && ch == '%') {
// The C printf silently ignores invalid flags, so we need to
// remove them if present.
sb.append(ch);
boolean isLeft = false;
boolean isAlt = false;
boolean isShowSign = false;
int argIndex = -1;
int leftPadLength = 0;
int width = 0;
int padChar = -1;
flags.setLength(0);
int j = i + 1;
loop:
for (; j < length; j++) {
ch = format.charAt(j);
switch (ch) {
case '-':
isLeft = true;
if (j + 1 < length && format.charAt(j + 1) == '0') {
padChar = '0';
j++;
}
/*
for (int k = j + 1; k < length; k++) {
char digit = format.charAt(k);
if ('0' <= digit && digit <= '9') {
leftPadLength = leftPadLength * 10 + digit - '0';
j++;
}
else
break;
}
*/
break;
case '#':
isAlt = true;
break;
case '0':
if (padChar < 0)
padChar = '0';
else {
int value = 0;
for (int k = j + 1; k < length; k++) {
char digit = format.charAt(k);
if ('0' <= digit && digit <= '9') {
value = value * 10 + digit - '0';
j++;
}
else
break;
}
if (j + 1 < length && format.charAt(j + 1) == '$') {
argIndex = value - 1;
j++;
}
else {
width = value;
}
}
break;
case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
int value = ch - '0';
for (int k = j + 1; k < length; k++) {
char digit = format.charAt(k);
if ('0' <= digit && digit <= '9') {
value = value * 10 + digit - '0';
j++;
}
else
break;
}
if (j + 1 < length && format.charAt(j + 1) == '$') {
argIndex = value - 1;
j++;
}
else {
width = value;
}
break;
case '\'':
padChar = format.charAt(j + 1);
j += 1;
break;
case '+':
isShowSign = true;
break;
case ',': case '(':
flags.append(ch);
break;
case ' ':
break;
default:
break loop;
}
}
int head = j;
if (argIndex < 0)
argIndex = index;
loop:
for (; j < length; j++) {
ch = format.charAt(j);
switch (ch) {
case '%':
i = j;
segments.add(new TextPrintfSegment(sb));
sb.setLength(0);
break loop;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.': case '$':
break;
case 's': case 'S':
sb.setLength(sb.length() - 1);
if (width <= 0 && 0 < leftPadLength)
width = leftPadLength;
index++;
segments.add(new StringPrintfSegment(
sb,
isLeft || isAlt,
padChar,
ch == 'S',
width,
format.substring(head, j).toString(),
argIndex));
sb.setLength(0);
i = j;
break loop;
case 'c': case 'C':
sb.setLength(sb.length() - 1);
if (width <= 0 && 0 < leftPadLength)
width = leftPadLength;
index++;
segments.add(new CharPrintfSegment(
sb,
isLeft || isAlt,
padChar,
ch == 'C',
width,
format.substring(head, j).toString(),
argIndex));
sb.setLength(0);
i = j;
break loop;
/*
case 'u':
sb.setLength(sb.length() - 1);
if (sb.length() > 0)
segments.add(new TextPrintfSegment(sb));
sb.setLength(0);
if (isLeft)
sb.append('-');
if (isAlt)
sb.append('#');
sb.append(flags);
sb.append(format, head, j);
sb.append(ch);
//segments.add(UnsignedLongPrintfSegment.create(
env, sb.toString(), index++));
sb.setLength(0);
i = j;
break loop;
*/
case 'i':
ch = 'd';
case 'd': case 'x': case 'o': case 'X':
case 'b': case 'B': case 'u':
sb.setLength(sb.length() - 1);
if (sb.length() > 0)
segments.add(new TextPrintfSegment(sb));
sb.setLength(0);
if (isAlt)
sb.append('#');
if (isShowSign)
sb.append('+');
sb.append(flags);
if (width > 0) {
if (isLeft)
sb.append('-');
else if (padChar == '0')
sb.append('0');
sb.append(width);
}
sb.append(format, head, j);
sb.append(ch);
index++;
segments.add(LongPrintfSegment.create(env, sb.toString(), argIndex));
sb.setLength(0);
i = j;
break loop;
case 'e': case 'E': case 'f': case 'g': case 'G':
case 'F':
QuercusLocale locale = null;
if (ch == 'F') {
ch = 'f';
locale = QuercusLocale.getDefault();
}
else if (ch == 'f') {
locale = env.getLocaleInfo().getNumeric();
}
sb.setLength(sb.length() - 1);
if (sb.length() > 0)
segments.add(new TextPrintfSegment(sb));
sb.setLength(0);
if (isAlt)
sb.append('#');
if (isShowSign)
sb.append('+');
sb.append(flags);
if (width > 0) {
if (isLeft)
sb.append('-');
else if (padChar == '0')
sb.append('0');
// '-' and '0' together is not supported by java.util.Formatter
//if (padChar == '0')
// sb.append((char) padChar);
sb.append(width);
}
sb.append(format, head, j);
sb.append(ch);
index++;
segments.add(new DoublePrintfSegment(sb.toString(),
isLeft && padChar == '0',
argIndex,
locale));
sb.setLength(0);
i = j;
break loop;
default:
if (isLeft)
sb.append('-');
if (isAlt)
sb.append('#');
sb.append(flags);
sb.append(format, head, j);
sb.append(ch);
i = j;
break loop;
}
}
} else
sb.append(ch);
}
if (sb.length() > 0)
segments.add(new TextPrintfSegment(sb));
return segments;
}
/**
* scans a string
*
* @param format the format string
* @param args the format arguments
*
* @return the formatted string
*/
public static Value sscanf(Env env,
StringValue string,
StringValue format,
@Optional @Reference Value []args)
{
ScanfSegment[] formatArray = sscanfParseFormat(env, format);
int strlen = string.length();
int sIndex = 0;
boolean isReturnArray = args.length == 0;
int argIndex = 0;
if (strlen == 0) {
return isReturnArray ? NullValue.NULL : LongValue.MINUS_ONE;
}
ArrayValue array = new ArrayValueImpl();
for (int i = 0; i < formatArray.length; i++) {
ScanfSegment segment = formatArray[i];
Value var;
if (! segment.isAssigned()) {
var = null;
} else if (isReturnArray) {
var = array;
} else {
if (argIndex < args.length) {
var = args[argIndex];
if (sIndex < strlen)
argIndex++;
}
else {
env.warning(L.l("not enough vars passed in"));
var = NullValue.NULL;
}
}
sIndex = segment.apply(string,
strlen,
sIndex,
var,
isReturnArray);
if (sIndex < 0) {
if (isReturnArray)
return sscanfFillNull(array, formatArray, i);
else
return LongValue.create(argIndex);
}
/*
else if (sIndex == strlen) {
if (isReturnArray)
return sscanfFillNull(array, formatArray, i + 1);
else
return LongValue.create(argIndex);
}
*/
}
return sscanfReturn(env, array, args, argIndex, isReturnArray, false);
}
private static Value sscanfFillNull(
ArrayValue array, ScanfSegment[] formatArray, int fIndex)
{
for (; fIndex < formatArray.length; fIndex++) {
ScanfSegment segment = formatArray[fIndex];
if (segment.isAssigned())
array.put(NullValue.NULL);
}
return array;
}
/**
* scans a string
*
* @param format the format string
* @param args the format arguments
*
* @return the formatted string
*/
private static ScanfSegment[] sscanfParseFormat(Env env,
StringValue format)
{
int fmtLen = format.length();
int fIndex = 0;
ArrayList segmentList = new ArrayList();
StringBuilder sb = new StringBuilder();
while (fIndex < fmtLen) {
char ch = format.charAt(fIndex++);
if (isWhitespace(ch)) {
for (;
(fIndex < fmtLen
&& isWhitespace(ch = format.charAt(fIndex)));
fIndex++) {
}
scanfAddConstant(segmentList, sb);
segmentList.add(ScanfWhitespace.SEGMENT);
}
else if (ch == '%') {
int maxLen = -1;
loop:
while (fIndex < fmtLen) {
ch = format.charAt(fIndex++);
switch (ch) {
case '%':
sb.append('%');
break loop;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (maxLen < 0)
maxLen = 0;
maxLen = 10 * maxLen + ch - '0';
break;
case 's':
{
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfString(maxLen));
break loop;
}
case 'c':
{
if (maxLen < 0)
maxLen = 1;
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfString(maxLen));
break loop;
}
case 'n':
{
scanfAddConstant(segmentList, sb);
segmentList.add(ScanfStringLength.SEGMENT);
break loop;
}
case 'd':
{
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfInteger(maxLen, 10, false));
break loop;
}
case 'i':
{
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfIntegerWithBaseDetection(maxLen));
break loop;
}
case 'u':
{
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfInteger(maxLen, 10, true));
break loop;
}
case 'o':
{
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfInteger(maxLen, 8, false));
break loop;
}
case 'x': case 'X':
{
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfHex(maxLen));
break loop;
}
case 'e': case 'f':
{
scanfAddConstant(segmentList, sb);
segmentList.add(new ScanfScientific(maxLen));
break loop;
}
case '[':
{
scanfAddConstant(segmentList, sb);
if (fmtLen <= fIndex) {
env.warning(L.l("expected ']', saw end of string"));
break loop;
}
boolean isNegated = false;
if (fIndex < fmtLen
&& format.charAt(fIndex) == '^') {
isNegated = true;
fIndex++;
}
IntSet set = new IntSet();
while (true) {
if (fIndex == fmtLen) {
env.warning(L.l("expected ']', saw end of string"));
break loop;
}
char ch2 = format.charAt(fIndex++);
if (ch2 == ']') {
break;
}
else {
set.union(ch2);
}
}
if (isNegated)
segmentList.add(new ScanfSetNegated(set));
else
segmentList.add(new ScanfSet(set));
break loop;
}
default:
log.fine(L.l("'{0}' is a bad sscanf string.", format));
env.warning(L.l("'{0}' is a bad sscanf string.", format));
// XXX:
//return isAssign ? LongValue.create(argIndex) : array;
break loop;
}
}
}
else
sb.append(ch);
}
scanfAddConstant(segmentList, sb);
ScanfSegment[] segmentArray = new ScanfSegment[segmentList.size()];
return segmentList.toArray(segmentArray);
}
private static void scanfAddConstant(
ArrayList segmentList, StringBuilder sb)
{
if (sb.length() == 0)
return;
segmentList.add(new ScanfConstant(sb.toString()));
sb.setLength(0);
}
/**
* scans a string
*
* @param format the format string
* @param args the format arguments
*
* @return the formatted string
*/
public static Value sscanfOld(Env env,
StringValue string,
StringValue format,
@Optional @Reference Value []args)
{
int fmtLen = format.length();
int strlen = string.length();
int sIndex = 0;
int fIndex = 0;
boolean isAssign = args.length != 0;
boolean isReturnArray = ! isAssign;
int argIndex = 0;
if (strlen == 0) {
return isAssign ? LongValue.MINUS_ONE : NullValue.NULL;
}
ArrayValue array = new ArrayValueImpl();
while (fIndex < fmtLen) {
char ch = format.charAt(fIndex++);
if (isWhitespace(ch)) {
for (;
(fIndex < fmtLen
&& isWhitespace(ch = format.charAt(fIndex)));
fIndex++) {
}
/*ch = string.charAt(sIndex);
if (! isWhitespace(ch)) {
// XXX: return false?
return sscanfReturn(env, array, args, argIndex, isAssign, true);
}*/
for (;
sIndex < strlen && isWhitespace(string.charAt(sIndex));
sIndex++) {
}
}
else if (ch == '%') {
int maxLen = -1;
loop:
while (fIndex < fmtLen) {
ch = format.charAt(fIndex++);
if (sIndex >= strlen && ch != 'n') {
array.append(NullValue.NULL);
break loop;
}
Value obj;
if (isAssign) {
if (argIndex < args.length)
obj = args[argIndex++];
else {
env.warning(L.l("not enough vars passed in"));
break loop;
}
}
else
obj = array;
switch (ch) {
case '%':
if (string.charAt(sIndex) != '%')
return sscanfReturn(env, array, args, argIndex, isAssign, true);
else
break loop;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (maxLen < 0)
maxLen = 0;
maxLen = 10 * maxLen + ch - '0';
break;
case 's':
{
ScanfSegment seg = new ScanfString(maxLen);
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
case 'c':
{
if (maxLen < 0)
maxLen = 1;
ScanfSegment seg = new ScanfString(maxLen);
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
case 'n':
{
ScanfSegment seg = ScanfStringLength.SEGMENT;
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
case 'd':
{
ScanfSegment seg = new ScanfInteger(maxLen, 10, false);
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
case 'u':
{
ScanfSegment seg = new ScanfInteger(maxLen, 10, true);
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
case 'o':
{
ScanfSegment seg = new ScanfInteger(maxLen, 8, false);
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
case 'x': case 'X':
{
ScanfSegment seg = new ScanfHex(maxLen);
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
case 'e': case 'f':
{
ScanfSegment seg = new ScanfScientific(maxLen);
sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
break loop;
}
default:
log.fine(L.l("'{0}' is a bad sscanf string.", format));
env.warning(L.l("'{0}' is a bad sscanf string.", format));
return isAssign ? LongValue.create(argIndex) : array;
}
}
}
else if (ch == string.charAt(sIndex)) {
sIndex++;
}
else
return sscanfReturn(env, array, args, argIndex, false, true);
}
return sscanfReturn(env, array, args, argIndex, isAssign, false);
}
private static Value sscanfReturn(Env env,
ArrayValue array,
Value []args,
int argIndex,
boolean isReturnArray,
boolean isWarn)
{
if (isReturnArray)
return array;
else {
if (isWarn && argIndex != args.length)
env.warning(
L.l("{0} vars passed in but saw only {1} '%' args",
args.length, argIndex));
return LongValue.create(argIndex);
}
}
/**
* Scans a string with a given length.
*/
private static int sscanfString(StringValue string,
int sIndex,
int maxLen,
Value obj,
boolean isAssignment)
{
int strlen = string.length();
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
StringValue sb = string.createStringBuilder();
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if (! isWhitespace(ch))
sb.append(ch);
else
break;
}
sscanfPut(obj, sb, isAssignment);
return sIndex;
}
private static void sscanfPut(Value obj, Value val, boolean isAssignment)
{
if (isAssignment)
obj.set(val);
else
obj.put(val);
}
/**
* Scans a integer with a given length.
*/
private static int sscanfInteger(StringValue string,
int sIndex,
int maxLen,
Value obj,
boolean isAssign,
int base,
boolean isUnsigned)
{
int strlen = string.length();
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
int val = 0;
int sign = 1;
boolean isNotMatched = true;
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '+') {
sIndex++;
maxLen--;
}
else if (ch == '-') {
sign = -1;
sIndex++;
maxLen--;
}
}
int topRange = base + '0';
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if ('0' <= ch && ch < topRange) {
val = val * base + ch - '0';
isNotMatched = false;
}
else if (isNotMatched) {
sscanfPut(obj, NullValue.NULL, isAssign);
return sIndex;
}
else
break;
}
if (isUnsigned) {
if (sign == -1 && val != 0)
sscanfPut(obj, StringValue.create(0xFFFFFFFFL - val + 1), isAssign);
else
sscanfPut(obj, LongValue.create(val), isAssign);
}
else
sscanfPut(obj, LongValue.create(val * sign), isAssign);
return sIndex;
}
/**
* Scans a integer with a given length.
*/
private static int sscanfHex(StringValue string,
int sIndex,
int maxLen,
Value obj,
boolean isAssign)
{
int strlen = string.length();
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
int val = 0;
int sign = 1;
boolean isMatched = false;
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '+') {
sIndex++;
maxLen--;
}
else if (ch == '-') {
sign = -1;
sIndex++;
maxLen--;
}
}
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if ('0' <= ch && ch <= '9') {
val = val * 16 + ch - '0';
isMatched = true;
}
else if ('a' <= ch && ch <= 'f') {
val = val * 16 + ch - 'a' + 10;
isMatched = true;
}
else if ('A' <= ch && ch <= 'F') {
val = val * 16 + ch - 'A' + 10;
isMatched = true;
}
else if (! isMatched) {
sscanfPut(obj, NullValue.NULL, isAssign);
return sIndex;
}
else
break;
}
sscanfPut(obj, LongValue.create(val * sign), isAssign);
return sIndex;
}
/**
* Scans a integer with a given length.
*/
private static int sscanfScientific(StringValue s,
int i,
int maxLen,
Value obj,
boolean isAssign)
{
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
int start = i;
int len = s.length();
int ch = 0;
if (i < len && maxLen > 0 && ((ch = s.charAt(i)) == '+' || ch == '-')) {
i++;
maxLen--;
}
for (; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
if (ch == '.') {
maxLen--;
for (i++; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
}
if (ch == 'e' || ch == 'E') {
maxLen--;
int e = i++;
if (start == e) {
sscanfPut(obj, NullValue.NULL, isAssign);
return start;
}
if (i < len && maxLen > 0 && (ch = s.charAt(i)) == '+' || ch == '-') {
i++;
maxLen--;
}
for (; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
if (i == e + 1)
i = e;
}
double val;
if (i == 0)
val = 0;
else
val = Double.parseDouble(s.substring(start, i).toString());
sscanfPut(obj, DoubleValue.create(val), isAssign);
return i;
}
// XXX: str_getcsv
/**
* replaces substrings.
*
* @param search search string
* @param replace replacement string
* @param subject replacement
* @param count return value
*/
public static Value str_ireplace(Env env,
Value search,
Value replace,
Value subject,
@Reference @Optional Value count)
{
return strReplace(env, search, replace, subject, count, true);
}
/**
* Pads strings
*
* @param string string
* @param length length
* @param pad padding string
* @param type padding type
*/
public static StringValue str_pad(StringValue string,
int length,
@Optional("' '") String pad,
@Optional("STR_PAD_RIGHT") int type)
{
int strLen = string.length();
int padLen = length - strLen;
if (padLen <= 0)
return string;
if (pad == null || pad.length() == 0)
pad = " ";
int leftPad = 0;
int rightPad = 0;
switch (type) {
case STR_PAD_LEFT:
leftPad = padLen;
break;
case STR_PAD_RIGHT:
default:
rightPad = padLen;
break;
case STR_PAD_BOTH:
leftPad = padLen / 2;
rightPad = padLen - leftPad;
break;
}
int padStringLen = pad.length();
StringValue sb = string.createStringBuilder(string.length() + padLen);
for (int i = 0; i < leftPad; i++)
sb.append(pad.charAt(i % padStringLen));
sb = sb.append(string);
for (int i = 0; i < rightPad; i++)
sb.append(pad.charAt(i % padStringLen));
return sb;
}
/**
* repeats a string
*
* @param string string to repeat
* @param count number of times to repeat
*/
public static Value str_repeat(StringValue string, int count)
{
StringValue sb = string.createStringBuilder(count * string.length());
for (int i = 0; i < count; i++)
sb = sb.append(string);
return sb;
}
/**
* replaces substrings.
*
* @param search search string
* @param replace replacement string
* @param subject replacement
* @param count return value
*/
public static Value str_replace(Env env,
Value search,
Value replace,
Value subject,
@Reference @Optional Value count)
{
return strReplace(env, search, replace, subject, count, false);
}
/**
* replaces substrings.
*
* @param search search string
* @param replace replacement string
* @param subject replacement
* @param count return value
*/
private static Value strReplace(Env env,
Value search,
Value replace,
Value subject,
Value count,
boolean isInsensitive)
{
count.set(LongValue.ZERO);
if (subject.isNull()) {
return env.getEmptyString();
}
if (search.isNull()) {
return subject;
}
if (subject.isArray()) {
ArrayValue subjectArray = subject.toArrayValue(env);
ArrayValue resultArray = new ArrayValueImpl();
for (Map.Entry entry : subjectArray.entrySet()) {
Value key = entry.getKey();
Value value = entry.getValue();
if (value.isArray()) {
resultArray.append(key, value);
}
else {
Value result = strReplaceImpl(env,
search,
replace,
value.toStringValue(env),
count,
isInsensitive);
resultArray.append(key, result);
}
}
return resultArray;
}
else {
StringValue subjectString = subject.toStringValue(env);
if (subjectString.length() == 0) {
return env.getEmptyString();
}
return strReplaceImpl(env,
search,
replace,
subjectString,
count,
isInsensitive);
}
}
/**
* replaces substrings.
*
* @param search search string
* @param replace replacement string
* @param subject replacement
* @param count return value
*/
private static Value strReplaceImpl(Env env,
Value search,
Value replace,
StringValue subject,
Value count,
boolean isInsensitive)
{
if (! search.isArray()) {
StringValue searchString = search.toStringValue(env);
if (searchString.length() == 0) {
return subject;
}
if (replace.isArray()) {
env.warning(L.l("Array to string conversion"));
}
subject = strReplaceImpl(env,
searchString,
replace.toStringValue(env),
subject,
count,
isInsensitive);
}
else if (replace.isArray()) {
ArrayValue searchArray = search.toArrayValue(env);
ArrayValue replaceArray = replace.toArrayValue(env);
Iterator searchIter = searchArray.values().iterator();
Iterator replaceIter = replaceArray.values().iterator();
while (searchIter.hasNext()) {
Value searchItem = searchIter.next();
Value replaceItem = replaceIter.next();
if (replaceItem == null) {
replaceItem = NullValue.NULL;
}
subject = strReplaceImpl(env,
searchItem.toStringValue(env),
replaceItem.toStringValue(env),
subject,
count,
isInsensitive);
}
}
else {
ArrayValue searchArray = search.toArrayValue(env);
Iterator searchIter = searchArray.values().iterator();
while (searchIter.hasNext()) {
Value searchItem = searchIter.next();
subject = strReplaceImpl(env,
searchItem.toStringValue(env),
replace.toStringValue(env),
subject,
count,
isInsensitive);
}
}
return subject;
}
/**
* replaces substrings.
*
* @param search search string
* @param replace replacement string
* @param subject replacement
* @param countV return value
*/
private static StringValue strReplaceImpl(Env env,
StringValue search,
StringValue replace,
StringValue subject,
Value countV,
boolean isInsensitive)
{
long count = countV.toLong();
int head = 0;
int next;
int searchLen = search.length();
StringValue result = null;
while (head <= (next = indexOf(subject, search, head, isInsensitive))) {
if (result == null) {
result = subject.createStringBuilder();
}
result = result.append(subject, head, next);
result = result.append(replace);
if (head < next + searchLen) {
head = next + searchLen;
}
else {
head += 1;
}
count++;
}
if (count != 0 && result != null) {
countV.set(LongValue.create(count));
int subjectLength = subject.length();
if (head > 0 && head < subjectLength)
result = result.append(subject, head, subjectLength);
return result;
}
else
return subject;
}
/**
* Returns the next index.
*/
private static int indexOf(StringValue subject,
StringValue match,
int head,
boolean isInsensitive)
{
if (! isInsensitive)
return subject.indexOf(match, head);
else {
int length = subject.length();
int matchLen = match.length();
if (matchLen <= 0)
return -1;
char ch = Character.toLowerCase(match.charAt(0));
loop:
for (; head + matchLen <= length; head++) {
if (ch == Character.toLowerCase(subject.charAt(head))) {
for (int i = 1; i < matchLen; i++) {
if (Character.toLowerCase(subject.charAt(head + i))
!= Character.toLowerCase(match.charAt(i)))
continue loop;
}
return head;
}
}
return -1;
}
}
/**
* rot13 conversion
*
* @param string string to convert
*/
public static Value str_rot13(StringValue string)
{
if (string == null)
return NullValue.NULL;
StringValue sb = string.createStringBuilder(string.length());
int len = string.length();
for (int i = 0; i < len; i++) {
char ch = string.charAt(i);
if ('a' <= ch && ch <= 'z') {
int off = ch - 'a';
sb.append((char) ('a' + (off + 13) % 26));
}
else if ('A' <= ch && ch <= 'Z') {
int off = ch - 'A';
sb.append((char) ('A' + (off + 13) % 26));
}
else {
sb.append(ch);
}
}
return sb;
}
/**
* shuffles a string
*/
public static String str_shuffle(String string)
{
if (string == null)
string = "";
char []chars = string.toCharArray();
int length = chars.length;
for (int i = 0; i < length; i++) {
int rand = RandomUtil.nextInt(length);
char temp = chars[rand];
chars[rand] = chars[i];
chars[i] = temp;
}
return new String(chars);
}
/**
* split into an array
*
* @param string string to split
* @param chunk chunk size
*/
public static Value str_split(StringValue string,
@Optional("1") int chunk)
{
ArrayValue array = new ArrayValueImpl();
if (string.length() == 0) {
array.put(string);
return array;
}
int strLen = string.length();
for (int i = 0; i < strLen; i += chunk) {
Value value;
if (i + chunk <= strLen) {
value = string.substring(i, i + chunk);
}
else if (i != 0) {
value = string.substring(i);
}
else {
value = string;
}
array.put(value);
}
return array;
}
public static Value str_word_count(StringValue string,
@Optional int format,
@Optional String additionalWordCharacters)
{
if (format < 0 || format > 2)
return NullValue.NULL;
int strlen = string.length();
boolean isAdditionalWordCharacters = false;
if (additionalWordCharacters != null)
isAdditionalWordCharacters = additionalWordCharacters.length() > 0;
ArrayValueImpl resultArray = null;
if (format > 0)
resultArray = new ArrayValueImpl();
boolean isBetweenWords = true;
int wordCount = 0;
int lastWordStart = 0;
for (int i = 0; i <= strlen; i++) {
boolean isWordCharacter;
if (i < strlen) {
int ch = string.charAt(i);
isWordCharacter = Character.isLetter(ch)
|| ch == '-'
|| ch == '\''
|| (isAdditionalWordCharacters
&& additionalWordCharacters.indexOf(ch) > -1);
}
else
isWordCharacter = false;
if (isWordCharacter) {
if (isBetweenWords) {
// starting a word
isBetweenWords = false;
lastWordStart = i;
wordCount++;
}
}
else {
if (!isBetweenWords) {
// finished a word
isBetweenWords = true;
if (format > 0) {
StringValue word = string.substring(lastWordStart, i);
if (format == 1)
resultArray.append(word);
else if (format == 2)
resultArray.put(LongValue.create(lastWordStart), word);
}
}
}
}
if (resultArray == null)
return LongValue.create(wordCount);
else
return resultArray;
}
/**
* Case-insensitive comparison
*
* @param a left value
* @param b right value
* @return -1, 0, or 1
*/
public static int strcasecmp(StringValue a, StringValue b)
{
int aLen = a.length();
int bLen = b.length();
for (int i = 0; i < aLen && i < bLen; i++) {
char chA = a.charAt(i);
char chB = b.charAt(i);
if (chA == chB)
continue;
if (Character.isUpperCase(chA))
chA = Character.toLowerCase(chA);
if (Character.isUpperCase(chB))
chB = Character.toLowerCase(chB);
if (chA == chB)
continue;
else if (chA < chB)
return -1;
else
return 1;
}
if (aLen == bLen)
return 0;
else if (aLen < bLen)
return -1;
else
return 1;
}
/**
* Finds the index of a substring
*
* @param env the calling environment
*/
public static Value strchr(Env env, StringValue haystack, Value needle)
{
return strstr(env, haystack, needle);
}
/**
* Case-sensitive comparison
*
* @param a left value
* @param b right value
* @return -1, 0, or 1
*/
public static int strcmp(StringValue a, StringValue b)
{
int aLen = a.length();
int bLen = b.length();
for (int i = 0; i < aLen && i < bLen; i++) {
char chA = a.charAt(i);
char chB = b.charAt(i);
if (chA == chB)
continue;
if (chA == chB)
continue;
else if (chA < chB)
return -1;
else
return 1;
}
if (aLen == bLen)
return 0;
else if (aLen < bLen)
return -1;
else
return 1;
}
/**
* Locale-based comparison
* XXX: i18n
*
* @param a left value
* @param b right value
* @return -1, 0, or 1
*/
public static Value strcoll(String a, String b)
{
if (a == null)
a = "";
if (b == null)
b = "";
int cmp = a.compareTo(b);
if (cmp == 0)
return LongValue.ZERO;
else if (cmp < 0)
return LongValue.MINUS_ONE;
else
return LongValue.ONE;
}
/**
* Finds the number of initial characters in string that do not match
* one of the characters in characters
*
* @param string the string to search in
* @param characters the character set
* @param offset the starting offset
* @param length the length
*
* @return the length of the match or FALSE if
* the offset or length are invalid
*/
public static Value strcspn(StringValue string,
StringValue characters,
@Optional("0") int offset,
@Optional("-2147483648") int length)
{
if (characters.length() == 0) {
characters = StringValue.create((char)0);
}
return strspnImpl(string, characters, offset, length, false);
}
/**
* Removes tags from a string.
*
* @param string the string to remove
* @param allowTags the allowable tags
*/
public static StringValue strip_tags(Env env,
StringValue string,
@Optional Value allowTags)
{
StringValue result = string.createStringBuilder(string.length());
HashSet allowedTagMap = null;
if (! allowTags.isDefault())
allowedTagMap = getAllowedTags(allowTags.toStringValue(env));
int len = string.length();
for (int i = 0; i < len; i++) {
char ch = string.charAt(i);
if (i + 1 >= len || ch != '<') {
result.append(ch);
continue;
}
ch = string.charAt(i + 1);
if (Character.isWhitespace(ch)) {
i++;
result.append('<');
result.append(ch);
continue;
}
int tagNameStart = i + 1;
if (ch == '/')
tagNameStart++;
int j = tagNameStart;
while (j < len
&& (ch = string.charAt(j)) != '>'
// && ch != '/'
&& ! Character.isWhitespace(ch)) {
j++;
}
StringValue tagName = string.substring(tagNameStart, j);
int tagEnd = 0;
if (allowedTagMap != null && allowedTagMap.contains(tagName)) {
result.append(string, i, Math.min(j + 1, len));
}
else {
while (j < len && (ch = string.charAt(j)) != '<') {
if (ch == '>') {
tagEnd = j;
}
j++;
}
}
i = (tagEnd != 0) ? tagEnd : j;
}
return result;
}
private static HashSet getAllowedTags(StringValue str)
{
int len = str.length();
HashSet set = new HashSet();
for (int i = 0; i < len; i++) {
char ch = str.charAt(i);
switch (ch) {
case '<':
int j = i + 1;
while (j < len
&& (ch = str.charAt(j)) != '>'
//&& ch != '/'
&& ! Character.isWhitespace(ch)) {
j++;
}
if (ch == '>'
&& i + 1 < j
&& j < len)
set.add(str.substring(i + 1, j));
i = j;
default:
continue;
}
}
return set;
}
/**
* Strip out the backslashes, recognizing the escape sequences, octal,
* and hexadecimal representations.
*
* @param source the string to clean
* @see #addcslashes
*/
public static String stripcslashes(String source)
{
if (source == null)
source = "";
StringBuilder result = new StringBuilder(source.length());
int length = source.length();
for (int i = 0; i < length; i++) {
int ch = source.charAt(i);
if (ch == '\\') {
i++;
if (i == length)
ch = '\\';
else {
ch = source.charAt(i);
switch (ch) {
case 'a':
ch = 0x07;
break;
case 'b':
ch = '\b';
break;
case 't':
ch = '\t';
break;
case 'n':
ch = '\n';
break;
case 'v':
ch = 0xb;
break;
case 'f':
ch = '\f';
break;
case 'r':
ch = '\r';
break;
case 'x':
// up to two digits for a hex number
if (i + 1 == length)
break;
int digitValue = hexToDigit(source.charAt(i + 1));
if (digitValue < 0)
break;
ch = digitValue;
i++;
if (i + 1 == length)
break;
digitValue = hexToDigit(source.charAt(i + 1));
if (digitValue < 0)
break;
ch = ((ch << 4) | digitValue);
i++;
break;
default:
// up to three digits from 0 to 7 for an octal number
digitValue = octToDigit((char) ch);
if (digitValue < 0)
break;
ch = digitValue;
if (i + 1 == length)
break;
digitValue = octToDigit(source.charAt(i + 1));
if (digitValue < 0)
break;
ch = ((ch << 3) | digitValue);
i++;
if (i + 1 == length)
break;
digitValue = octToDigit(source.charAt(i + 1));
if (digitValue < 0)
break;
ch = ((ch << 3) | digitValue);
i++;
}
}
} // if ch == '/'
result.append((char) ch);
}
return result.toString();
}
/**
* Returns the position of a substring, testing case insensitive.
*
* @param haystack the full argument to check
* @param needleV the substring argument to check
* @param offsetV optional starting position
*/
public static Value stripos(Env env,
StringValue haystack,
Value needleV,
@Optional int offset)
{
StringValue needle;
int len = haystack.length();
if (len < offset) {
env.warning(L.l("offset cannot exceed string length"));
return BooleanValue.FALSE;
}
if (needleV.isString())
needle = needleV.toStringValue(env);
else
needle = StringValue.create((char) needleV.toInt());
haystack = haystack.toLowerCase();
needle = needle.toLowerCase();
int pos = haystack.indexOf(needle, offset);
if (pos < 0)
return BooleanValue.FALSE;
else
return LongValue.create(pos);
}
/**
* Strips out the backslashes.
*
* @param string the string to clean
*/
public static StringValue stripslashes(StringValue string)
{
StringValue sb = string.createStringBuilder();
int len = string.length();
for (int i = 0; i < len; i++) {
char ch = string.charAt(i);
if (ch == '\\') {
if (i + 1 < len) {
char ch2 = string.charAt(i + 1);
if (ch2 == '0') {
ch2 = 0x0;
}
sb.append(ch2);
i++;
}
}
else
sb.append(ch);
}
return sb;
}
/**
* Finds the first instance of a substring, testing case insensitively
*
* @param haystack the string to search in
* @param needleV the string to search for
* @return the trailing match or FALSE
*/
public static Value stristr(Env env,
StringValue haystack,
Value needleV)
{
CharSequence needleLower;
if (needleV.isString()) {
needleLower = needleV.toStringValue(env).toLowerCase();
}
else {
char lower = Character.toLowerCase((char) needleV.toLong());
needleLower = String.valueOf(lower);
}
StringValue haystackLower = haystack.toLowerCase();
int i = haystackLower.indexOf(needleLower);
if (i >= 0)
return haystack.substring(i);
else
return BooleanValue.FALSE;
}
/**
* Returns the length of a string.
*
* @param value the argument value
*/
public static Value strlen(Value value)
{
return LongValue.create(value.length());
}
/**
* Case-insensitive comparison
*
* @param a left value
* @param b right value
* @return -1, 0, or 1
*/
public static int strnatcasecmp(StringValue a, StringValue b)
{
return naturalOrderCompare(a, b, true);
}
/**
* Case-sensitive comparison
*
* @param a left value
* @param b right value
* @return -1, 0, or 1
*/
public static int strnatcmp(StringValue a, StringValue b)
{
return naturalOrderCompare(a, b, false);
}
/**
* http://sourcefrog.net/projects/natsort/
*/
private static int naturalOrderCompare(StringValue a,
StringValue b,
boolean ignoreCase)
{
SimpleStringReader aIn = new SimpleStringReader(a);
SimpleStringReader bIn = new SimpleStringReader(b);
int aChar = aIn.read();
int bChar = bIn.read();
if (aChar == -1 && bChar >= 0)
return -1;
else if (aChar >= 0 && bChar == -1)
return 1;
while (true) {
while (Character.isWhitespace(aChar)) {
aChar = aIn.read();
}
while (Character.isWhitespace(bChar)) {
bChar = bIn.read();
}
if (aChar == -1 && bChar == -1) {
return 0;
}
// leading zeros
// '01' < '2'
// '0a' > 'a'
if (aChar == '0' && bChar == '0') {
while (true) {
aChar = aIn.read();
bChar = bIn.read();
if (aChar == '0' && bChar == '0') {
continue;
}
else if (aChar == '0') {
if ('1' <= bChar && bChar <= '9')
return -1;
else
return 1;
}
else if (bChar == 0) {
if ('1' <= aChar && aChar <= '9')
return 1;
else
return -1;
}
else {
break;
}
}
}
else if ('0' < aChar && aChar <= '9'
&& '0' < bChar && bChar <= '9')
{
int aInteger = aIn.readInt(aChar);
int bInteger = bIn.readInt(bChar);
if (aInteger > bInteger)
return 1;
else if (aInteger < bInteger)
return -1;
else {
aChar = aIn.read();
bChar = bIn.read();
}
}
if (ignoreCase) {
aChar = Character.toUpperCase(aChar);
bChar = Character.toUpperCase(bChar);
}
if (aChar > bChar)
return 1;
else if (aChar < bChar)
return -1;
aChar = aIn.read();
bChar = bIn.read();
// trailing spaces
// "abc " > "abc"
if (aChar >= 0 && bChar == -1)
return 1;
else if (aChar == -1 && bChar >= 0)
return -1;
}
}
/**
* Case-insensitive comparison
*
* @param a left value
* @param b right value
* @return -1, 0, or 1
*/
public static Value strncasecmp(
Env env, StringValue a, StringValue b, int length)
{
if (length < 0) {
env.warning(L.l("strncasecmp() length '{0}' must be non-negative",
length));
return BooleanValue.FALSE;
}
int aLen = a.length();
int bLen = b.length();
for (int i = 0; i < length; i++) {
if (aLen <= i)
return LongValue.MINUS_ONE;
else if (bLen <= i)
return LongValue.ONE;
char aChar = Character.toUpperCase(a.charAt(i));
char bChar = Character.toUpperCase(b.charAt(i));
if (aChar < bChar)
return LongValue.MINUS_ONE;
else if (bChar < aChar)
return LongValue.ONE;
}
return LongValue.ZERO;
}
/**
* Case-sensitive comparison
*
* @param a left value
* @param b right value
* @return -1, 0, or 1
*/
public static Value strncmp(Env env, StringValue a, StringValue b, int length)
{
if (length < 0) {
env.warning(L.l("strncmp() length '{0}' must be non-negative",
length));
return BooleanValue.FALSE;
}
if (length < a.length())
a = a.substring(0, length);
if (length < b.length())
b = b.substring(0, length);
return LongValue.create(strcmp(a, b));
}
/**
* Returns a substring of haystack starting from the earliest
* occurence of any char in charList
*
* @param haystack the string to search in
* @param charList list of chars that would trigger match
* @return substring, else FALSE
*/
public static Value strpbrk(StringValue haystack,
StringValue charList)
{
int len = haystack.length();
int sublen = charList.length();
for (int i = 0; i < len; i++) {
for (int j = 0; j < sublen; j++) {
if (haystack.charAt(i) == charList.charAt(j))
return haystack.substring(i);
}
}
return BooleanValue.FALSE;
}
/**
* Returns the position of a substring.
*
* @param haystack the string to search in
* @param needleV the string to search for
*/
public static Value strpos(Env env,
StringValue haystack,
Value needleV,
@Optional int offset)
{
StringValue needle;
if (offset > haystack.length()) {
env.warning(L.l("offset cannot exceed string length"));
return BooleanValue.FALSE;
}
if (needleV.isString())
needle = needleV.toStringValue(env);
else
needle = StringValue.create((char) needleV.toInt());
int pos = haystack.indexOf(needle, offset);
if (pos < 0)
return BooleanValue.FALSE;
else
return LongValue.create(pos);
}
/**
* Finds the last instance of a substring
*
* @param haystack the string to search in
* @param needleV the string to search for
* @return the trailing match or FALSE
*/
public static Value strrchr(Env env,
StringValue haystack,
Value needleV)
{
CharSequence needle;
if (needleV.isString())
needle = needleV.toStringValue(env);
else
needle = String.valueOf((char) needleV.toLong());
int i = haystack.lastIndexOf(needle);
if (i > 0)
return haystack.substring(i);
else
return BooleanValue.FALSE;
}
/**
* Reverses a string.
*
*/
public static Value strrev(StringValue string)
{
StringValue sb = string.createStringBuilder(string.length());
for (int i = string.length() - 1; i >= 0; i--) {
sb.append(string.charAt(i));
}
return sb;
}
/**
* Returns the position of a substring, testing case-insensitive.
*
* @param haystack the full string to test
* @param needleV the substring string to test
* @param offsetV the optional offset to start searching
*/
public static Value strripos(Env env,
String haystack,
Value needleV,
@Optional Value offsetV)
{
if (haystack == null)
haystack = "";
String needle;
if (needleV.isString())
needle = needleV.toString();
else
needle = String.valueOf((char) needleV.toInt());
int offset;
if (offsetV.isDefault())
offset = haystack.length();
else {
offset = offsetV.toInt();
if (haystack.length() < offset) {
env.warning(L.l("offset cannot exceed string length"));
return BooleanValue.FALSE;
}
}
haystack = haystack.toLowerCase(Locale.ENGLISH);
needle = needle.toLowerCase(Locale.ENGLISH);
int pos = haystack.lastIndexOf(needle, offset);
if (pos < 0)
return BooleanValue.FALSE;
else
return LongValue.create(pos);
}
/**
* Returns the position of a substring.
*
* @param haystack the string to search in
* @param needleV the string to search for
*/
public static Value strrpos(Env env,
StringValue haystack,
Value needleV,
@Optional Value offsetV)
{
StringValue needle;
if (needleV.isString())
needle = needleV.toStringValue(env);
else
needle = StringValue.create((char) needleV.toInt());
int offset = haystack.length() - offsetV.toInt();
if (offset < 0) {
env.warning(L.l("offset cannot exceed string length"));
return BooleanValue.FALSE;
}
int pos = haystack.lastIndexOf(needle, offset);
if (pos < 0)
return BooleanValue.FALSE;
else
return LongValue.create(pos);
}
/**
* Finds the number of initial characters in string that match one of
* the characters in characters
*
* @param string the string to search in
* @param characters the character set
* @param offset the starting offset
* @param length the length
*
* @return the length of the match or FALSE
* if the offset or length are invalid
*/
public static Value strspn(StringValue string,
StringValue characters,
@Optional int offset,
@Optional("-2147483648") int length)
{
return strspnImpl(string, characters, offset, length, true);
}
private static Value strspnImpl(StringValue string,
StringValue characters,
int offset,
int length,
boolean isMatch)
{
int strlen = string.length();
// see also strcspn which uses the same procedure for determining
// effective offset and length
if (offset < 0) {
offset += strlen;
if (offset < 0)
offset = 0;
}
if (offset > strlen)
return BooleanValue.FALSE;
if (length == -2147483648)
length = strlen;
else if (length < 0) {
length += (strlen - offset);
if (length < 0)
length = 0;
}
int end = offset + length;
if (strlen < end)
end = strlen;
int count = 0;
for (; offset < end; offset++) {
char ch = string.charAt(offset);
boolean isPresent = characters.indexOf(ch) > -1;
if (isPresent == isMatch)
count++;
else
return LongValue.create(count);
}
return LongValue.create(count);
}
/**
* Finds the first instance of a needle in haystack and returns
* the portion of haystack from the beginning of
* needle to the end of haystack.
*
* @param env the calling environment
* @param haystackV the string to search in
* @param needleV the string to search for, or the
* original value of a character
* @return the trailing match or FALSE if needle is not found
*/
public static Value strstr(Env env,
StringValue haystackV,
Value needleV)
{
if (haystackV == null)
haystackV = env.getEmptyString();
String needle;
if (needleV.isString()) {
needle = needleV.toString();
}
else {
needle = String.valueOf((char) needleV.toLong());
}
if (needle.length() == 0) {
env.warning("empty needle");
return BooleanValue.FALSE;
}
int i = haystackV.indexOf(needle);
if (i >= 0)
return haystackV.substring(i);
else
return BooleanValue.FALSE;
}
/**
* Split a string into tokens using any character
* in another string as a delimiter.
*
* The first call establishes the string to
* search and the characters to use as tokens,
* the first token is returned:
*
* strtok("hello, world", ", ")
* => "hello"
*
*
* Subsequent calls pass only the token
* characters, the next token is returned:
*
* strtok("hello, world", ", ")
* => "hello"
* strtok(", ")
* => "world"
*
*
* False is returned if there are no more tokens:
*
* strtok("hello, world", ", ")
* => "hello"
* strtok(", ")
* => "world"
* strtok(", ")
* => false
*
*
* Calls that pass two arguments reset the search string:
*
* strtok("hello, world", ", ")
* => "hello"
* strtok("goodbye, world", ", ")
* => "goodbye"
* strtok("world")
* => false
* strtok(", ")
* => false
*
*/
public static Value strtok(Env env,
StringValue string1,
@Optional Value string2)
{
StringValue string;
StringValue characters;
int offset;
//StringValue savedToken = null;
if (string2.isNull()) {
StringValue savedString
= (StringValue) env.getSpecialValue("caucho.strtok_string");
Integer savedOffset
= (Integer) env.getSpecialValue("caucho.strtok_offset");
//savedToken = (StringValue) env.getSpecialValue("caucho.strtok_token");
string = savedString == null ? env.getEmptyString() : savedString;
offset = savedOffset == null ? 0 : savedOffset;
//savedToken = savedToken == null ? env.getEmptyString() : savedToken;
characters = string1;
}
else {
string = string1;
offset = 0;
characters = string2.toStringValue(env);
env.setSpecialValue("caucho.strtok_string", string);
//env.setSpecialValue("caucho.strtok_token", string2);
}
int strlen = string.length();
// skip any at beginning
for (; offset < strlen; offset++) {
char ch = string.charAt(offset);
if (characters.indexOf(ch) < 0)
break;
}
Value result;
if (offset == strlen)
result = BooleanValue.FALSE;
else {
//if (string2.isNull() && !(string1.eq(savedToken))) {
// offset = offset + savedToken.length();
//}
int start = offset;
int end = start;
// find end
for (; end < strlen; end++) {
char ch = string.charAt(end);
if (characters.indexOf(ch) > -1)
break;
}
for (offset = end; offset < strlen; offset++) {
char ch = string.charAt(offset);
if (characters.indexOf(ch) < 0)
break;
}
result = string.substring(start, end);
}
env.setSpecialValue("caucho.strtok_offset", offset);
return result;
}
/**
* Converts to lower case.
*
* @param string the input string
*/
public static StringValue strtolower(StringValue string)
{
return string.toLowerCase();
}
/**
* Converts to upper case.
*
* @param string the input string
*/
public static StringValue strtoupper(StringValue string)
{
return string.toUpperCase();
}
/**
* Translates characters in a string to target values.
*
* @param string the source string
* @param fromV the from characters
* @param to the to character map
*/
public static StringValue strtr(Env env,
StringValue string,
Value fromV,
@Optional StringValue to)
{
if (fromV.isArray()) {
return strtrArray(env, string, fromV.toArrayValue(env));
}
StringValue from = fromV.toStringValue(env);
int len = from.length();
if (to.length() < len)
len = to.length();
char []map = new char[256];
for (int i = len - 1; i >= 0; i--)
map[from.charAt(i)] = to.charAt(i);
StringValue sb = string.createStringBuilder();
len = string.length();
for (int i = 0; i < len; i++) {
char ch = string.charAt(i);
if (map[ch] != 0)
sb.append(map[ch]);
else
sb.append(ch);
}
return sb;
}
/**
* Translates characters in a string to target values.
*
* @param string the source string
* @param map the character map
*/
private static StringValue strtrArray(Env env,
StringValue string,
ArrayValue map)
{
int size = map.getSize();
StringValue []fromList = new StringValue[size];
StringValue []toList = new StringValue[size];
Map.Entry [] entryArray = new Map.Entry[size];
int i = 0;
for (Map.Entry entry : map.entrySet()) {
entryArray[i++] = entry;
}
// sort entries in descending fashion
Arrays.sort(entryArray, new StrtrComparator>());
boolean []charSet = new boolean[256];
for (i = 0; i < size; i++) {
fromList[i] = entryArray[i].getKey().toStringValue(env);
toList[i] = entryArray[i].getValue().toStringValue(env);
charSet[fromList[i].charAt(0)] = true;
}
StringValue result = string.createStringBuilder();
int len = string.length();
int head = 0;
top:
while (head < len) {
char ch = string.charAt(head);
if (charSet.length <= ch || charSet[ch]) {
fromLoop:
for (i = 0; i < fromList.length; i++) {
StringValue from = fromList[i];
int fromLen = from.length();
if (head + fromLen > len)
continue;
if (ch != from.charAt(0))
continue;
for (int j = 0; j < fromLen; j++) {
if (string.charAt(head + j) != from.charAt(j))
continue fromLoop;
}
result = result.append(toList[i]);
head = head + fromLen;
continue top;
}
}
result.append(ch);
head++;
}
return result;
}
/*
* Comparator for sorting in descending fashion based on length.
*/
static class StrtrComparator>
implements Comparator
{
public int compare(T a, T b)
{
int lenA = a.getKey().length();
int lenB = b.getKey().length();
if (lenA < lenB)
return 1;
else if (lenA == lenB)
return 0;
else
return -1;
}
}
/**
* Returns a substring
*
* @param env the calling environment
* @param string the string
* @param start the start offset
* @param lenV the optional length
*/
public static Value substr(Env env,
StringValue string,
int start,
@Optional Value lenV)
{
int len = lenV.toInt();
int strLen = string.length();
if (start < 0)
start = strLen + start;
if (start < 0 || start >= strLen)
return BooleanValue.FALSE;
if (lenV.isDefault())
return string.substring(start);
else if (len == 0)
return string.EMPTY;
else {
int end;
if (len < 0)
end = strLen + len;
else
end = (strLen < len) ? strLen : start + len;
if (end <= start)
return BooleanValue.FALSE;
else if (strLen <= end)
return string.substring(start);
else
return string.substring(start, end);
}
}
public static Value substr_compare(Env env,
StringValue mainStr,
StringValue str,
int offset,
@Optional Value lenV,
@Optional boolean isCaseInsensitive)
{
int strLen = mainStr.length();
int len = lenV.toInt();
if (! lenV.isDefault() && len == 0)
return BooleanValue.FALSE;
if (strLen < offset) {
env.warning(L.l("offset can not be greater than length of string"));
return BooleanValue.FALSE;
}
if (len > strLen
|| len + offset > strLen) {
return BooleanValue.FALSE;
}
mainStr = substr(env, mainStr, offset, lenV).toStringValue(env);
str = substr(env, str, 0, lenV).toStringValue(env);
if (isCaseInsensitive)
return LongValue.create(strcasecmp(mainStr, str));
else
return LongValue.create(strcmp(mainStr, str));
}
public static Value substr_count(Env env,
StringValue haystackV,
StringValue needleV,
@Optional int offset,
@Optional("-1") int length)
{
String haystack = haystackV.toString();
String needle = needleV.toString();
if (needle.length() == 0) {
env.warning(L.l("empty substr"));
return BooleanValue.FALSE;
}
int haystackLength = haystack.length();
if (offset < 0 || offset > haystackLength) {
env.warning(L.l("offset cannot exceed string length", offset));
return BooleanValue.FALSE;
}
if (length >= 0) {
int newLength = offset + length;
if (newLength < 0 || newLength > haystackLength) {
env.warning(L.l("length cannot exceed string length", length));
return BooleanValue.FALSE;
}
haystackLength = newLength;
}
int needleLength = needle.length();
int count = 0;
int end = haystackLength - needleLength + 1;
for (int i = offset; i < end; i++) {
if (haystack.startsWith(needle, i)) {
count++;
i += needleLength;
}
}
return LongValue.create(count);
}
/**
* Replaces a substring with a replacement
*
* @param subjectV a string to modify, or an array of strings to modify
* @param replacement the replacement string
* @param startV the start offset
* @param lengthV the optional length
*/
public static Value substr_replace(Env env,
Value subjectV,
StringValue replacement,
Value startV,
@Optional Value lengthV)
{
int start = 0;
int length = Integer.MAX_VALUE / 2;
if (!(lengthV.isNull() || lengthV.isArray()))
length = lengthV.toInt();
if (!(startV.isNull() || startV.isArray()))
start = startV.toInt();
Iterator startIterator =
startV.isArray()
? ((ArrayValue) startV).values().iterator()
: null;
Iterator lengthIterator =
lengthV.isArray()
? ((ArrayValue) lengthV).values().iterator()
: null;
if (subjectV.isArray()) {
ArrayValue resultArray = new ArrayValueImpl();
ArrayValue subjectArray = (ArrayValue) subjectV;
for (Value value : subjectArray.values()) {
if (lengthIterator != null && lengthIterator.hasNext())
length = lengthIterator.next().toInt();
if (startIterator != null && startIterator.hasNext())
start = startIterator.next().toInt();
Value result = substrReplaceImpl(
value.toStringValue(env), replacement, start, length);
resultArray.append(result);
}
return resultArray;
}
else {
if (lengthIterator != null && lengthIterator.hasNext())
length = lengthIterator.next().toInt();
if (startIterator != null && startIterator.hasNext())
start = startIterator.next().toInt();
return substrReplaceImpl(
subjectV.toStringValue(env), replacement, start, length);
}
}
private static Value substrReplaceImpl(StringValue string,
StringValue replacement,
int start,
int len)
{
int strLen = string.length();
if (start > strLen)
start = strLen;
else if (start < 0)
start = Math.max(strLen + start, 0);
int end;
if (len < 0)
end = Math.max(strLen + len, start);
else {
end = (strLen < len) ? strLen : (start + len);
}
StringValue result = string.createStringBuilder();
result = result.append(string.substring(0, start));
result = result.append(replacement);
result = result.append(string.substring(end));
return result;
}
/**
* Removes leading and trailing whitespace.
*
* @param string the string to be trimmed
* @param characters optional set of characters to trim
* @return the trimmed string
*/
public static Value trim(Env env,
StringValue string,
@Optional String characters)
{
boolean []trim;
if (characters == null || characters.equals(""))
trim = TRIM_WHITESPACE;
else
trim = parseCharsetBitmap(env, characters.toString());
int len = string.length();
int head = 0;
for (; head < len; head++) {
char ch = string.charAt(head);
if (ch >= 256 || ! trim[ch]) {
break;
}
}
int tail = len - 1;
for (; tail >= 0; tail--) {
char ch = string.charAt(tail);
if (ch >= 256 || ! trim[ch]) {
break;
}
}
if (head == 0 && tail == len - 1) {
return string;
}
else if (tail < head)
return env.getEmptyString();
else {
return string.substring(head, tail + 1);
}
}
/**
* Uppercases the first character
*
* @param string the input string
*/
public static StringValue ucfirst(Env env, StringValue string)
{
if (string == null)
return env.getEmptyString();
else if (string.length() == 0)
return string;
StringValue sb = string.createStringBuilder();
sb = sb.append(Character.toUpperCase(string.charAt(0)));
sb = sb.append(string, 1, string.length());
return sb;
}
/**
* Uppercases the first character of each word
*
* @param string the input string
*/
public static String ucwords(String string)
{
if (string == null)
string = "";
int strLen = string.length();
boolean isStart = true;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strLen; i++) {
char ch = string.charAt(i);
switch (ch) {
case ' ': case '\t': case '\r': case '\n':
isStart = true;
sb.append(ch);
break;
default:
if (isStart)
sb.append(Character.toUpperCase(ch));
else
sb.append(ch);
isStart = false;
break;
}
}
return sb.toString();
}
/**
* Formatted strings with array arguments
*
* @param format the format string
* @param array the arguments to apply to the format string
*/
public static int vprintf(Env env,
StringValue format,
@NotNull ArrayValue array)
{
Value []args;
if (array != null) {
args = new Value[array.getSize()];
int i = 0;
for (Value value : array.values())
args[i++] = value;
}
else
args = new Value[0];
return printf(env, format, args);
}
/**
* Formatted strings with array arguments
*
* @param format the format string
* @param array the arguments to apply to the format string
*/
public static Value vsprintf(Env env,
StringValue format,
@NotNull ArrayValue array)
{
Value []args;
if (array != null) {
args = new Value[array.getSize()];
int i = 0;
for (Value value : array.values())
args[i++] = value;
}
else
args = new Value[0];
return sprintf(env, format, args);
}
/**
* Wraps a string to the given number of characters.
*
* @param string the input string
* @param width the width
* @param breakString the break string
* @param cut if true, break on exact match
*/
public static Value wordwrap(Env env,
@Expect(type = Expect.Type.STRING) Value value,
@Optional @Expect(type = Expect.Type.NUMERIC) Value widthV,
@Optional @Expect(type = Expect.Type.STRING) Value breakV,
@Optional @Expect(type = Expect.Type.BOOLEAN) Value cutV)
{
if (value instanceof UnexpectedValue) {
env.warning(L.l("word must be a string, but {0} given",
value.getType()));
return NullValue.NULL;
}
if (widthV instanceof UnexpectedValue) {
env.warning(L.l("width must be numeric, but {0} given",
widthV.getType()));
return NullValue.NULL;
}
int width = 0;
if (widthV.isDefault())
width = 75;
else
width = widthV.toInt();
String string = value.toString();
if (cutV instanceof UnexpectedValue) {
env.warning(L.l("cut must be a boolean, but {0} given",
cutV.getType()));
return NullValue.NULL;
}
boolean isCut = cutV.toBoolean();
if (isCut && width == 0 && string.length() > 0) {
env.warning(L.l("cannot cut string to width 0"));
return BooleanValue.FALSE;
}
int len = string != null ? string.length() : 0;
if (breakV instanceof UnexpectedValue) {
env.warning(L.l("break string must be a string, but {0} given",
breakV.getType()));
return NullValue.NULL;
}
String breakString = "\n";
if (! breakV.isDefault())
breakString = breakV.toString();
if (breakString == null || breakString.length() == 0) {
env.warning(L.l("break string cannot be empty"));
return BooleanValue.FALSE;
}
int breakLen = breakString.length();
int breakChar;
if (breakLen == 0)
breakChar = -1;
else
breakChar = breakString.charAt(0);
int head = 0;
int lastSpace = 0;
StringValue sb = env.createStringBuilder();
for (int i = 0; i < len; i++) {
char ch = string.charAt(i);
if (ch == breakChar && string.regionMatches(
i, breakString, 0, breakLen)) {
sb.append(string, head, i + breakLen);
head = i + breakLen;
} else if (width <= i - head) {
if (ch == ' ') {
sb.append(string, head, i);
sb.append(breakString);
head = i + 1;
} else if (head < lastSpace) {
sb.append(string, head, lastSpace);
sb.append(breakString);
head = lastSpace + 1;
} else if (isCut) {
sb.append(string, head, i);
sb.append(breakString);
head = i;
}
} else if (ch == ' ') {
lastSpace = i;
}
}
if (head < len) {
sb.append(string, head, len);
}
return sb;
}
/**
* Returns true if the character is a whitespace character.
*/
protected static boolean isWhitespace(char ch)
{
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}
/**
* Returns the uppercase equivalent of the caharacter
*/
protected static char toUpperCase(char ch)
{
if (ch >= 'a' && ch <= 'z')
return (char) ('A' + (ch - 'a'));
else
return ch;
}
/**
* Converts an integer digit to a uuencoded char.
*/
protected static char toUUChar(int d)
{
if (d == 0)
return (char) 0x60;
else
return (char) (0x20 + (d & 0x3f));
}
protected static char toHexChar(int d)
{
d &= 0xf;
if (d < 10)
return (char) (d + '0');
else
return (char) (d - 10 + 'a');
}
protected static char toUpperHexChar(int d)
{
d &= 0xf;
if (d < 10)
return (char) (d + '0');
else
return (char) (d - 10 + 'A');
}
protected static int hexToDigit(char ch)
{
if ('0' <= ch && ch <= '9')
return ch - '0';
else if ('a' <= ch && ch <= 'f')
return ch - 'a' + 10;
else if ('A' <= ch && ch <= 'F')
return ch - 'A' + 10;
else
return -1;
}
protected static int octToDigit(char ch)
{
if ('0' <= ch && ch <= '7')
return ch - '0';
else
return -1;
}
abstract static class PrintfSegment {
abstract public boolean apply(Env env, StringValue sb, Value []args);
static boolean hasIndex(String format)
{
return format.indexOf('$') >= 0;
}
static int getIndex(String format)
{
int value = 0;
for (int i = 0; i < format.length(); i++) {
char ch;
if ('0' <= (ch = format.charAt(i)) && ch <= '9')
value = 10 * value + ch - '0';
else
break;
}
return value - 1;
}
static String getIndexFormat(String format)
{
int p = format.indexOf('$');
return '%' + format.substring(p + 1);
}
}
static class TextPrintfSegment extends PrintfSegment {
private final char []_text;
TextPrintfSegment(StringBuilder text)
{
_text = new char[text.length()];
text.getChars(0, _text.length, _text, 0);
}
@Override
public boolean apply(Env env, StringValue sb, Value []args)
{
sb.append(_text, 0, _text.length);
return true;
}
}
static class LongPrintfSegment extends PrintfSegment {
private final String _format;
private final int _index;
private final QuercusLocale _locale;
private LongPrintfSegment(String format, int index, QuercusLocale locale)
{
_format = format;
_index = index;
_locale = locale;
}
static PrintfSegment create(Env env, String format, int index)
{
if (hasIndex(format)) {
index = getIndex(format);
format = getIndexFormat(format);
}
else {
format = '%' + format;
//index = index;
}
// php/115b
// strip out illegal precision specifier from phpBB vote function
if (format.length() > 1 && format.charAt(1) == '.') {
int i;
for (i = 2; i < format.length(); i++) {
char ch = format.charAt(i);
if (! ('0' <= ch && ch <= '9'))
break;
}
format = '%' + format.substring(i);
}
if (format.charAt(format.length() - 1) == 'x'
|| format.charAt(format.length() - 1) == 'X') {
HexPrintfSegment hex = HexPrintfSegment.create(format, index);
if (hex != null)
return hex;
}
if (format.charAt(format.length() - 1) == 'b'
|| format.charAt(format.length() - 1) == 'B') {
BinaryPrintfSegment bin = BinaryPrintfSegment.create(format, index);
if (bin != null)
return bin;
}
if (format.charAt(format.length() - 1) == 'u') {
UnsignedPrintfSegment unsign
= UnsignedPrintfSegment.create(format, index);
if (unsign != null)
return unsign;
}
return new LongPrintfSegment(format, index,
env.getLocaleInfo().getNumeric());
}
@Override
public boolean apply(Env env, StringValue sb, Value []args)
{
long value;
if (_index < args.length)
value = args[_index].toLong();
else {
env.warning(L.l("printf(): not enough arguments to match format."));
return false;
}
sb.append(String.format(_locale.getLocale(), _format, value));
return true;
}
}
static class HexPrintfSegment extends PrintfSegment {
private final int _index;
private final int _min;
private final char _pad;
private boolean _isUpper;
HexPrintfSegment(int index, int min, int pad, boolean isUpper)
{
_index = index;
_min = min;
if (pad >= 0)
_pad = (char) pad;
else
_pad = ' ';
_isUpper = isUpper;
}
static HexPrintfSegment create(String format, int index)
{
int length = format.length();
int offset = 1;
boolean isUpper = format.charAt(length - 1) == 'X';
char pad = ' ';
if (format.charAt(offset) == ' ') {
pad = ' ';
offset++;
}
else if (format.charAt(offset) == '0') {
pad = '0';
offset++;
}
int min = 0;
for (; offset < length - 1; offset++) {
char ch = format.charAt(offset);
if ('0' <= ch && ch <= '9')
min = 10 * min + ch - '0';
else
return null;
}
return new HexPrintfSegment(index, min, pad, isUpper);
}
@Override
public boolean apply(Env env, StringValue sb, Value []args)
{
long value;
if (_index >= 0 && _index < args.length)
value = args[_index].toLong();
else {
env.warning(L.l("printf(): not enough arguments to match format."));
return false;
}
int digits = 0;
long shift = value;
for (int i = 0; i < 16; i++) {
if (shift != 0)
digits = i;
shift = shift >>> 4;
}
for (int i = digits + 1; i < _min; i++)
sb.append(_pad);
for (; digits >= 0; digits--) {
int digit = (int) (value >>> (4 * digits)) & 0xf;
if (digit <= 9)
sb.append((char) ('0' + digit));
else if (_isUpper)
sb.append((char) ('A' + digit - 10));
else
sb.append((char) ('a' + digit - 10));
}
return true;
}
}
static class UnsignedPrintfSegment extends PrintfSegment {
private final int _index;
private final int _min;
private final char _pad;
UnsignedPrintfSegment(int index, int min, int pad)
{
_index = index;
_min = min;
if (pad >= 0)
_pad = (char) pad;
else
_pad = ' ';
}
static UnsignedPrintfSegment create(String format, int index)
{
int length = format.length();
int offset = 1;
if (format.charAt(offset) == '+')
offset++;
char pad = ' ';
if (format.charAt(offset) == ' ') {
pad = ' ';
offset++;
}
else if (format.charAt(offset) == '0') {
pad = '0';
offset++;
}
int min = 0;
for (; offset < length - 1; offset++) {
char ch = format.charAt(offset);
if ('0' <= ch && ch <= '9')
min = 10 * min + ch - '0';
else
return null;
}
return new UnsignedPrintfSegment(index, min, pad);
}
@Override
public boolean apply(Env env, StringValue sb, Value []args)
{
long value;
if (_index >= 0 && _index < args.length)
value = args[_index].toLong();
else {
env.warning(L.l("printf(): not enough arguments to match format."));
return false;
}
char []buf = new char[32];
int digits = buf.length;
if (value == 0) {
buf[--digits] = '0';
}
else if (value > 0) {
while (value != 0) {
int digit = (int) (value % 10);
buf[--digits] = (char) ('0' + digit);
value = value / 10;
}
}
else {
BigInteger bigInt = new BigInteger(String.valueOf(value));
bigInt = bigInt.add(BIG_2_64);
while (bigInt.compareTo(BigInteger.ZERO) != 0) {
int digit = bigInt.mod(BIG_TEN).intValue();
buf[--digits] = (char) ('0' + digit);
bigInt = bigInt.divide(BIG_TEN);
}
}
for (int i = buf.length - digits; i < _min; i++)
sb.append(_pad);
for (; digits < buf.length; digits++) {
sb.append(buf[digits]);
}
return true;
}
}
static class BinaryPrintfSegment extends PrintfSegment {
private final int _index;
private final int _min;
private final char _pad;
BinaryPrintfSegment(int index, int min, int pad)
{
_index = index;
_min = min;
if (pad >= 0)
_pad = (char) pad;
else
_pad = ' ';
}
static BinaryPrintfSegment create(String format, int index)
{
int length = format.length();
int offset = 1;
char pad = ' ';
if (format.charAt(offset) == ' ') {
pad = ' ';
offset++;
}
else if (format.charAt(offset) == '0') {
pad = '0';
offset++;
}
int min = 0;
for (; offset < length - 1; offset++) {
char ch = format.charAt(offset);
if ('0' <= ch && ch <= '9')
min = 10 * min + ch - '0';
else
return null;
}
return new BinaryPrintfSegment(index, min, pad);
}
@Override
public boolean apply(Env env, StringValue sb, Value []args)
{
long value;
if (_index >= 0 && _index < args.length)
value = args[_index].toLong();
else {
env.warning(L.l("printf(): not enough arguments to match format."));
return false;
}
int digits = 0;
long shift = value;
for (int i = 0; i < 64; i++) {
if (shift != 0)
digits = i;
shift = shift >>> 1;
}
for (int i = digits + 1; i < _min; i++)
sb.append(_pad);
for (; digits >= 0; digits--) {
int digit = (int) (value >>> (digits)) & 0x1;
sb.append((char) ('0' + digit));
}
return true;
}
}
static class DoublePrintfSegment extends PrintfSegment {
private final String _format;
private final boolean _isLeftZero;
private final int _index;
private final QuercusLocale _locale;
DoublePrintfSegment(String format,
boolean isLeftZero,
int index,
QuercusLocale locale)
{
if (hasIndex(format)) {
_index = getIndex(format);
_format = getIndexFormat(format);
}
else {
_format = '%' + format;
_index = index;
}
_isLeftZero = isLeftZero;
_locale = locale;
}
@Override
public boolean apply(Env env, StringValue sb, Value []args)
{
double value;
if (_index < args.length)
value = args[_index].toDouble();
else {
env.warning(L.l("printf(): not enough arguments to match format."));
return false;
}
String s;
if (_locale == null)
s = String.format(_format, value);
else
s = String.format(_locale.getLocale(), _format, value);
if (_isLeftZero) {
int len = s.length();
// php/1174 "-0" not allowed by java formatter
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
if (ch == ' ')
sb.append('0');
else
sb.append(ch);
}
}
else {
sb.append(s);
}
return true;
}
}
static class StringPrintfSegment extends PrintfSegment {
protected final char []_prefix;
protected final int _min;
protected final int _max;
protected final boolean _isLeft;
protected final boolean _isUpper;
protected final char _pad;
protected final int _index;
StringPrintfSegment(StringBuilder prefix,
boolean isLeft, int pad, boolean isUpper,
int width,
String format, int index)
{
_prefix = new char[prefix.length()];
_isLeft = isLeft;
_isUpper = isUpper;
if (pad >= 0)
_pad = (char) pad;
else
_pad = ' ';
prefix.getChars(0, _prefix.length, _prefix, 0);
if (hasIndex(format)) {
index = getIndex(format);
format = getIndexFormat(format);
}
int i = 0;
int len = format.length();
int min = width;
int max = Integer.MAX_VALUE;
char ch = ' ';
/*
for (; i < len && '0' <= (ch = format.charAt(i)) && ch <= '9'; i++) {
min = 10 * min + ch - '0';
}
*/
if (0 < len && format.charAt(0) == '.') {
max = 0;
for (i++; i < len && '0' <= (ch = format.charAt(i)) && ch <= '9'; i++) {
max = 10 * max + ch - '0';
}
}
_min = min;
_max = max;
_index = index;
}
protected StringValue toValue(Env env, Value []args)
{
return args[_index].toStringValue(env);
}
@Override
public boolean apply(Env env, StringValue sb, Value []args)
{
sb.append(_prefix, 0, _prefix.length);
StringValue value;
if (_index < args.length) {
value = toValue(env, args);
}
else {
env.warning(L.l("printf(): not enough arguments to match format."));
return false;
}
int len = value.length();
if (_max < len) {
value = value.substring(0, _max);
len = _max;
}
if (_isUpper) {
value = value.toUpperCase(Locale.ENGLISH);
}
if (! _isLeft) {
for (int i = len; i < _min; i++) {
sb.append(_pad);
}
}
sb.append(value);
if (_isLeft) {
for (int i = len; i < _min; i++) {
sb.append(_pad);
}
}
return true;
}
}
static class CharPrintfSegment extends StringPrintfSegment {
CharPrintfSegment(StringBuilder prefix,
boolean isLeft, int pad, boolean isUpper,
int width,
String format, int index)
{
super(prefix, isLeft, pad, isUpper, width, format, index);
}
@Override
protected StringValue toValue(Env env, Value []args)
{
if (args.length <= _index)
return env.getEmptyString();
Value v = args[_index];
if (v.isLongConvertible())
return env.createString((char) v.toLong());
else
return v.charValueAt(0).toStringValue(env);
}
}
static class SimpleStringReader {
StringValue _str;
int _length;
int _index;
SimpleStringReader(StringValue str)
{
_str = str;
_length = str.length();
_index = 0;
}
int read()
{
if (_index < _length)
return _str.charAt(_index++);
else
return -1;
}
int peek()
{
if (_index < _length)
return _str.charAt(_index);
else
return -1;
}
int readInt(int currChar)
{
int number = currChar - '0';
while (true) {
currChar = peek();
if ('0' <= currChar && currChar <= '9') {
number = number * 10 + currChar - '0';
_index++;
}
else {
break;
}
}
return number;
}
}
// sscanf
abstract static class ScanfSegment {
abstract public boolean isAssigned();
abstract public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray);
void sscanfPut(Value var, Value val, boolean isReturnArray)
{
if (isReturnArray)
var.put(val);
else
var.set(val);
}
}
static class ScanfConstant extends ScanfSegment {
private final String _string;
private final int _strlen;
private ScanfConstant(String string)
{
_string = string;
_strlen = string.length();
}
@Override
public boolean isAssigned()
{
return false;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
int fStrlen = _strlen;
String fString = _string;
if (strlen - sIndex < fStrlen)
return -1;
for (int i = 0; i < fStrlen; i++) {
if (string.charAt(sIndex++) != fString.charAt(i))
return -1;
}
return sIndex;
}
}
static class ScanfWhitespace extends ScanfSegment {
static final ScanfWhitespace SEGMENT = new ScanfWhitespace();
private ScanfWhitespace()
{
}
@Override
public boolean isAssigned()
{
return false;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
for (;
sIndex < strlen && isWhitespace(string.charAt(sIndex));
sIndex++) {
}
return sIndex;
}
}
static class ScanfStringLength extends ScanfSegment {
static final ScanfStringLength SEGMENT = new ScanfStringLength();
private ScanfStringLength()
{
}
@Override
public boolean isAssigned()
{
return true;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
sscanfPut(var, LongValue.create(sIndex), isReturnArray);
return sIndex;
}
}
static class ScanfSet extends ScanfSegment {
private IntSet _set;
private ScanfSet(IntSet set)
{
_set = set;
}
@Override
public boolean isAssigned()
{
return true;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
StringValue sb = string.createStringBuilder();
for (; sIndex < strlen; sIndex++) {
char ch = string.charAt(sIndex);
if (_set.contains(ch)) {
sb.append(ch);
}
else {
break;
}
}
if (sb.length() > 0)
sscanfPut(var, sb, isReturnArray);
else if (isReturnArray)
var.put(NullValue.NULL);
return sIndex;
}
}
static class ScanfSetNegated extends ScanfSegment {
private IntSet _set;
private ScanfSetNegated(IntSet set)
{
_set = set;
}
@Override
public boolean isAssigned()
{
return true;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
StringValue sb = string.createStringBuilder();
for (; sIndex < strlen; sIndex++) {
char ch = string.charAt(sIndex);
if (! _set.contains(ch)) {
sb.append(ch);
}
else {
break;
}
}
if (sb.length() > 0)
sscanfPut(var, sb, isReturnArray);
else if (isReturnArray)
var.put(NullValue.NULL);
return sIndex;
}
}
static class ScanfScientific extends ScanfSegment {
private final int _maxLen;
ScanfScientific(int maxLen)
{
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
}
@Override
public boolean isAssigned()
{
return true;
}
@Override
public int apply(StringValue s,
int strlen,
int i,
Value var,
boolean isReturnArray)
{
if (i == strlen) {
if (isReturnArray)
var.put(NullValue.NULL);
return i;
}
int start = i;
int len = strlen;
int ch = 0;
int maxLen = _maxLen;
if (i < len && maxLen > 0 && ((ch = s.charAt(i)) == '+' || ch == '-')) {
i++;
maxLen--;
}
for (; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
if (ch == '.') {
maxLen--;
for (i++; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
}
if (ch == 'e' || ch == 'E') {
maxLen--;
int e = i++;
if (start == e) {
sscanfPut(var, NullValue.NULL, isReturnArray);
return start;
}
if (i < len && maxLen > 0 && (ch = s.charAt(i)) == '+' || ch == '-') {
i++;
maxLen--;
}
for (; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
if (i == e + 1)
i = e;
}
double val;
if (i == 0)
val = 0;
else
val = Double.parseDouble(s.substring(start, i).toString());
sscanfPut(var, DoubleValue.create(val), isReturnArray);
return i;
}
}
static class ScanfHex extends ScanfSegment {
private final int _maxLen;;
ScanfHex(int maxLen)
{
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
}
@Override
public boolean isAssigned()
{
return true;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
if (sIndex == strlen) {
if (isReturnArray)
var.put(NullValue.NULL);
return sIndex;
}
int val = 0;
int sign = 1;
boolean isMatched = false;
int maxLen = _maxLen;
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '+') {
sIndex++;
maxLen--;
}
else if (ch == '-') {
sign = -1;
sIndex++;
maxLen--;
}
}
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if ('0' <= ch && ch <= '9') {
val = val * 16 + ch - '0';
isMatched = true;
}
else if ('a' <= ch && ch <= 'f') {
val = val * 16 + ch - 'a' + 10;
isMatched = true;
}
else if ('A' <= ch && ch <= 'F') {
val = val * 16 + ch - 'A' + 10;
isMatched = true;
}
else if (! isMatched) {
sscanfPut(var, NullValue.NULL, isReturnArray);
return sIndex;
}
else
break;
}
sscanfPut(var, LongValue.create(val * sign), isReturnArray);
return sIndex;
}
}
static class ScanfInteger extends ScanfSegment {
private final int _maxLen;
private final int _base;
private final boolean _isUnsigned;
ScanfInteger(int maxLen, int base, boolean isUnsigned)
{
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
_base = base;
_isUnsigned = isUnsigned;
}
@Override
public boolean isAssigned()
{
return true;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
if (sIndex == strlen) {
if (isReturnArray)
var.put(NullValue.NULL);
return sIndex;
}
// XXX: 32-bit vs 64-bit
int val = 0;
int sign = 1;
boolean isNotMatched = true;
int maxLen = _maxLen;
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '+') {
sIndex++;
maxLen--;
}
else if (ch == '-') {
sign = -1;
sIndex++;
maxLen--;
}
}
int base = _base;
int topRange = base + '0';
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if ('0' <= ch && ch < topRange) {
val = val * base + ch - '0';
isNotMatched = false;
}
else if (isNotMatched) {
sscanfPut(var, NullValue.NULL, isReturnArray);
return sIndex;
}
else
break;
}
if (_isUnsigned) {
if (sign == -1 && val != 0)
sscanfPut(
var, StringValue.create(0xffffffffL - val + 1), isReturnArray);
else
sscanfPut(var, LongValue.create(val), isReturnArray);
}
else
sscanfPut(var, LongValue.create(val * sign), isReturnArray);
return sIndex;
}
}
static class ScanfIntegerWithBaseDetection extends ScanfSegment {
private final int _maxLen;
ScanfIntegerWithBaseDetection(int maxLen)
{
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
}
@Override
public boolean isAssigned()
{
return true;
}
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
if (sIndex == strlen) {
if (isReturnArray)
var.put(NullValue.NULL);
return sIndex;
}
long val = 0;
int sign = 1;
int maxLen = _maxLen;
// detect sign
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '+') {
sIndex++;
maxLen--;
}
else if (ch == '-') {
sign = -1;
sIndex++;
maxLen--;
}
}
int base = 10;
// detect base
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '0') {
sIndex++;
if (sIndex < strlen
&& Character.toLowerCase(string.charAt(sIndex)) == 'x') {
base = 16;
sIndex++;
}
else {
base = 8;
}
}
}
boolean isNotMatched = true;
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
int digit = toNumber(base, ch);
if (digit < 0) {
if (isNotMatched) {
sscanfPut(var, NullValue.NULL, isReturnArray);
return sIndex;
}
else {
break;
}
}
val = val * base + digit;
isNotMatched = false;
}
sscanfPut(var, LongValue.create(val * sign), isReturnArray);
return sIndex;
}
private static int toNumber(int base, char ch)
{
switch (base) {
case 10:
return base10ToNumber(ch);
case 8:
return base8ToNumber(ch);
case 16:
return base16ToNumber(ch);
default:
throw new IllegalStateException();
}
}
private static int base10ToNumber(char ch)
{
if ('0' <= ch && ch <= '9') {
return ch - '0';
}
else {
return -1;
}
}
private static int base8ToNumber(char ch)
{
if ('0' <= ch && ch <= '7') {
return ch - '0';
}
else {
return -1;
}
}
private static int base16ToNumber(char ch)
{
if ('0' <= ch && ch <= '9') {
return ch - '0';
}
else if ('a' <= ch && ch <= 'f') {
return ch - 'a' + 10;
}
else if ('A' <= ch && ch <= 'F') {
return ch - 'A' + 10;
}
else {
return -1;
}
}
}
static class ScanfString extends ScanfSegment {
private final int _maxLen;
ScanfString(int maxLen)
{
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
}
@Override
public boolean isAssigned()
{
return true;
}
/**
* Scans a string with a given length.
*/
@Override
public int apply(StringValue string,
int strlen,
int sIndex,
Value var,
boolean isReturnArray)
{
if (sIndex == strlen) {
if (isReturnArray)
var.put(NullValue.NULL);
return sIndex;
}
StringValue sb = string.createStringBuilder();
int maxLen = _maxLen;
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if (isWhitespace(ch))
break;
sb.append(ch);
}
sscanfPut(var, sb, isReturnArray);
return sIndex;
}
}
static {
DEFAULT_DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols();
DEFAULT_DECIMAL_FORMAT_SYMBOLS.setDecimalSeparator('.');
DEFAULT_DECIMAL_FORMAT_SYMBOLS.setGroupingSeparator(',');
DEFAULT_DECIMAL_FORMAT_SYMBOLS.setZeroDigit('0');
}
}