PageRenderTime 39ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/bianca/src/main/java/com/clevercloud/bianca/lib/string/StringModule.java

http://github.com/CleverCloud/Bianca
Java | 6494 lines | 4926 code | 974 blank | 594 comment | 1090 complexity | f89a3a083427accd155fc2116ebd73b1 MD5 | raw file
Possible License(s): GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /*
  2. * Copyright (c) 1998-2010 Caucho Technology -- all rights reserved
  3. * Copyright (c) 2011-2012 Clever Cloud SAS -- all rights reserved
  4. *
  5. * This file is part of Bianca(R) Open Source
  6. *
  7. * Each copy or derived work must preserve the copyright notice and this
  8. * notice unmodified.
  9. *
  10. * Bianca Open Source is free software; you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License as published by
  12. * the Free Software Foundation; either version 2 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * Bianca Open Source is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
  18. * of NON-INFRINGEMENT. See the GNU General Public License for more
  19. * details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with Bianca Open Source; if not, write to the
  23. *
  24. * Free Software Foundation, Inc.
  25. * 59 Temple Place, Suite 330
  26. * Boston, MA 02111-1307 USA
  27. *
  28. * @author Scott Ferguson
  29. * @author Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
  30. */
  31. package com.clevercloud.bianca.lib.string;
  32. import com.clevercloud.bianca.BiancaException;
  33. import com.clevercloud.bianca.BiancaModuleException;
  34. import com.clevercloud.bianca.annotation.*;
  35. import com.clevercloud.bianca.env.*;
  36. import com.clevercloud.bianca.lib.file.BinaryOutput;
  37. import com.clevercloud.bianca.lib.file.FileModule;
  38. import com.clevercloud.bianca.module.AbstractBiancaModule;
  39. import com.clevercloud.util.*;
  40. import com.clevercloud.vfs.Path;
  41. import java.io.IOException;
  42. import java.io.InputStream;
  43. import java.math.BigInteger;
  44. import java.security.MessageDigest;
  45. import java.text.DecimalFormat;
  46. import java.text.DecimalFormatSymbols;
  47. import java.text.NumberFormat;
  48. import java.util.*;
  49. import java.util.logging.Level;
  50. import java.util.logging.Logger;
  51. /**
  52. * PHP functions implemented from the string module
  53. */
  54. public class StringModule extends AbstractBiancaModule {
  55. private static final Logger log =
  56. Logger.getLogger(StringModule.class.getName());
  57. private static final L10N L = new L10N(StringModule.class);
  58. public static final int CODESET = 14;
  59. public static final int CRYPT_SALT_LENGTH = 2;
  60. public static final int CRYPT_STD_DES = 0;
  61. public static final int CRYPT_EXT_DES = 0;
  62. public static final int CRYPT_MD5 = 0;
  63. public static final int CRYPT_BLOWFISH = 0;
  64. public static final int CHAR_MAX = 1;
  65. public static final int LC_CTYPE = 1;
  66. public static final int LC_NUMERIC = 2;
  67. public static final int LC_TIME = 3;
  68. public static final int LC_COLLATE = 4;
  69. public static final int LC_MONETARY = 5;
  70. public static final int LC_ALL = 6;
  71. public static final int LC_MESSAGES = 7;
  72. public static final int NOEXPR = (10 << 15) + 1;
  73. public static final int STR_PAD_LEFT = 1;
  74. public static final int STR_PAD_RIGHT = 0;
  75. public static final int STR_PAD_BOTH = 2;
  76. public static final int YESEXPR = 10 << 15;
  77. public static final int RADIXCHAR = 0x10000;
  78. public static final int DECIMAL_POINT = RADIXCHAR; // Returns same value as RADIXCHAR
  79. public static final int THOUSEP = 0x10001;
  80. public static final int THOUSANDS_SEP = THOUSEP; // Returns same value as THOUSEP
  81. public static final int ABDAY_1 = 0x20000;
  82. public static final int ABDAY_2 = 0x20001;
  83. public static final int ABDAY_3 = 0x20002;
  84. public static final int ABDAY_4 = 0x20003;
  85. public static final int ABDAY_5 = 0x20004;
  86. public static final int ABDAY_6 = 0x20005;
  87. public static final int ABDAY_7 = 0x20006;
  88. public static final int DAY_1 = 0x20007;
  89. public static final int DAY_2 = 0x20008;
  90. public static final int DAY_3 = 0x20009;
  91. public static final int DAY_4 = 0x2000a;
  92. public static final int DAY_5 = 0x2000b;
  93. public static final int DAY_6 = 0x2000c;
  94. public static final int DAY_7 = 0x2000d;
  95. public static final int ABMON_1 = 0x2000e;
  96. public static final int ABMON_2 = 0x2000f;
  97. public static final int ABMON_3 = 0x20010;
  98. public static final int ABMON_4 = 0x20011;
  99. public static final int ABMON_5 = 0x20012;
  100. public static final int ABMON_6 = 0x20013;
  101. public static final int ABMON_7 = 0x20014;
  102. public static final int ABMON_8 = 0x20015;
  103. public static final int ABMON_9 = 0x20016;
  104. public static final int ABMON_10 = 0x20017;
  105. public static final int ABMON_11 = 0x20018;
  106. public static final int ABMON_12 = 0x20019;
  107. public static final int MON_1 = 0x2001a;
  108. public static final int MON_2 = 0x2001b;
  109. public static final int MON_3 = 0x2001c;
  110. public static final int MON_4 = 0x2001d;
  111. public static final int MON_5 = 0x2001e;
  112. public static final int MON_6 = 0x2001f;
  113. public static final int MON_7 = 0x20020;
  114. public static final int MON_8 = 0x20021;
  115. public static final int MON_9 = 0x20022;
  116. public static final int MON_10 = 0x20023;
  117. public static final int MON_11 = 0x20024;
  118. public static final int MON_12 = 0x20025;
  119. public static final int AM_STR = 0x20026;
  120. public static final int PM_STR = 0x20027;
  121. public static final int D_T_FMT = 0x20028;
  122. public static final int D_FMT = 0x20029;
  123. public static final int T_FMT = 0x2002a;
  124. public static final int T_FMT_AMPM = 0x2002b;
  125. public static final int ERA = 0x2002c;
  126. public static final int ERA_D_FMT = 0x2002e;
  127. public static final int ERA_D_T_FMT = 0x20030;
  128. public static final int ERA_T_FMT = 0x20031;
  129. public static final int CRNCYSTR = 0x4000f;
  130. private static final DecimalFormatSymbols DEFAULT_DECIMAL_FORMAT_SYMBOLS;
  131. private static final BigInteger BIG_TEN = new BigInteger("10");
  132. private static final BigInteger BIG_2_64 = BigInteger.ONE.shiftLeft(64);
  133. private static final FreeList<MessageDigest> _md5FreeList = new FreeList<MessageDigest>(16);
  134. private static final int LEVENSHTEIN_MAX_LENGTH = 255;
  135. /**
  136. * Escapes a string using C syntax.
  137. *
  138. * @param source the source string to convert
  139. * @param characters the set of characters to convert
  140. * @return the escaped string
  141. * @see #stripcslashes
  142. */
  143. public static StringValue addcslashes(
  144. Env env, StringValue source, String characters) {
  145. if (characters == null) {
  146. characters = "";
  147. }
  148. boolean[] bitmap = parseCharsetBitmap(env, characters);
  149. int length = source.length();
  150. StringValue sb = new StringValue();
  151. for (int i = 0; i < length; i++) {
  152. char ch = source.charAt(i);
  153. if (ch >= 256 || !bitmap[ch]) {
  154. sb.append(ch);
  155. continue;
  156. }
  157. switch (ch) {
  158. case 0x07:
  159. sb.append("\\a");
  160. break;
  161. case '\b':
  162. sb.append("\\b");
  163. break;
  164. case '\t':
  165. sb.append("\\t");
  166. break;
  167. case '\n':
  168. sb.append("\\n");
  169. break;
  170. case 0xb:
  171. sb.append("\\v");
  172. break;
  173. case '\f':
  174. sb.append("\\f");
  175. break;
  176. case '\r':
  177. sb.append("\\r");
  178. break;
  179. default:
  180. if (ch < 0x20 || ch >= 0x7f) {
  181. // save as octal
  182. sb.append("\\");
  183. sb.append((char) ('0' + ((ch >> 6) & 7)));
  184. sb.append((char) ('0' + ((ch >> 3) & 7)));
  185. sb.append((char) ('0' + ((ch) & 7)));
  186. break;
  187. } else {
  188. sb.append("\\");
  189. sb.append(ch);
  190. break;
  191. }
  192. }
  193. }
  194. return sb;
  195. }
  196. /**
  197. * Parses the cslashes bitmap returning an actual bitmap.
  198. *
  199. * @param charset the bitmap string
  200. * @return the actual bitmap
  201. */
  202. private static boolean[] parseCharsetBitmap(Env env, String charset) {
  203. boolean[] bitmap = new boolean[256];
  204. int length = charset.length();
  205. for (int i = 0; i < length; i++) {
  206. char ch = charset.charAt(i);
  207. // TODO: the bitmap eventual might need to deal with unicode
  208. if (ch >= 256) {
  209. continue;
  210. }
  211. bitmap[ch] = true;
  212. if (length <= i + 3) {
  213. continue;
  214. }
  215. if (charset.charAt(i + 1) != '.' || charset.charAt(i + 2) != '.') {
  216. continue;
  217. }
  218. char last = charset.charAt(i + 3);
  219. if (last < ch) {
  220. env.warning(L.l("character set range is invalid: {0}..{1}",
  221. ch, last));
  222. continue;
  223. }
  224. i += 3;
  225. for (; ch <= last; ch++) {
  226. bitmap[ch] = true;
  227. }
  228. // TODO: handling of '@'?
  229. }
  230. return bitmap;
  231. }
  232. /**
  233. * Escapes a string for db characters.
  234. *
  235. * @param source the source string to convert
  236. * @return the escaped string
  237. */
  238. public static StringValue addslashes(StringValue source) {
  239. StringValue sb = new StringValue();
  240. int length = source.length();
  241. for (int i = 0; i < length; i++) {
  242. char ch = source.charAt(i);
  243. switch (ch) {
  244. case 0x0:
  245. sb.append("\\0");
  246. break;
  247. case '\'':
  248. sb.append("\\'");
  249. break;
  250. case '\"':
  251. sb.append("\\\"");
  252. break;
  253. case '\\':
  254. sb.append("\\\\");
  255. break;
  256. default:
  257. sb.append(ch);
  258. break;
  259. }
  260. }
  261. return sb;
  262. }
  263. /**
  264. * Converts a binary value to a hex value.
  265. */
  266. public static StringValue bin2hex(Env env, InputStream is) {
  267. try {
  268. StringValue sb = new StringValue();
  269. int ch;
  270. while ((ch = is.read()) >= 0) {
  271. int d = (ch >> 4) & 0xf;
  272. if (d < 10) {
  273. sb.append((char) (d + '0'));
  274. } else {
  275. sb.append((char) (d + 'a' - 10));
  276. }
  277. d = (ch) & 0xf;
  278. if (d < 10) {
  279. sb.append((char) (d + '0'));
  280. } else {
  281. sb.append((char) (d + 'a' - 10));
  282. }
  283. }
  284. return sb;
  285. } catch (IOException e) {
  286. throw new BiancaModuleException(e);
  287. }
  288. }
  289. /**
  290. * Alias of rtrim. Removes trailing whitespace.
  291. *
  292. * @param env the bianca environment
  293. * @param str the string to be trimmed
  294. * @param charset optional set of characters to trim
  295. * @return the trimmed string
  296. */
  297. public static StringValue chop(Env env,
  298. StringValue str,
  299. @Optional String charset) {
  300. return rtrim(env, str, charset);
  301. }
  302. /**
  303. * converts a number to its character equivalent
  304. *
  305. * @param value the integer value
  306. * @return the string equivalent
  307. */
  308. public static StringValue chr(Env env, long value) {
  309. StringValue sb = new StringValue();
  310. sb.append((char) value);
  311. return sb;
  312. }
  313. /**
  314. * Splits a string into chunks
  315. *
  316. * @param body the body string
  317. * @param chunkLen the optional chunk length, defaults to 76
  318. * @param end the optional end value, defaults to "\r\n"
  319. */
  320. public static String chunk_split(String body,
  321. @Optional("76") int chunkLen,
  322. @Optional("\"\\r\\n\"") String end) {
  323. if (body == null) {
  324. body = "";
  325. }
  326. if (end == null) {
  327. end = "";
  328. }
  329. if (chunkLen < 1) // TODO: real exn
  330. {
  331. throw new IllegalArgumentException(L.l("bad value {0}", chunkLen));
  332. }
  333. StringBuilder sb = new StringBuilder();
  334. int i = 0;
  335. for (; i + chunkLen <= body.length(); i += chunkLen) {
  336. sb.append(body.substring(i, i + chunkLen));
  337. sb.append(end);
  338. }
  339. if (i < body.length()) {
  340. sb.append(body.substring(i));
  341. sb.append(end);
  342. }
  343. return sb.toString();
  344. }
  345. /**
  346. * Converts from one cyrillic set to another.
  347. * <p/>
  348. * This implementation does nothing, because bianca stores strings as
  349. * 16 bit unicode.
  350. */
  351. public static String convert_cyr_string(Env env,
  352. String str,
  353. String from,
  354. String to) {
  355. env.stub("convert_cyr_string");
  356. return str;
  357. }
  358. public static Value convert_uudecode(Env env, StringValue source) {
  359. int length = source.length();
  360. if (length == 0) {
  361. return BooleanValue.FALSE;
  362. }
  363. StringBuilder builder = new StringBuilder();
  364. int i = 0;
  365. while (i < length) {
  366. int ch1 = source.charAt(i++);
  367. if (ch1 == 0x60 || ch1 == 0x20) {
  368. break;
  369. } else if (ch1 < 0x20 || 0x5f < ch1) {
  370. continue;
  371. }
  372. int sublen = ch1 - 0x20;
  373. while (sublen > 0) {
  374. int code;
  375. code = ((source.charAt(i++) - 0x20) & 0x3f) << 18;
  376. code += ((source.charAt(i++) - 0x20) & 0x3f) << 12;
  377. code += ((source.charAt(i++) - 0x20) & 0x3f) << 6;
  378. code += ((source.charAt(i++) - 0x20) & 0x3f);
  379. builder.append(code >> 16);
  380. if (sublen > 1) {
  381. builder.append(code >> 8);
  382. }
  383. if (sublen > 2) {
  384. builder.append(code);
  385. }
  386. sublen -= 3;
  387. }
  388. }
  389. return env.createString(builder.toString());
  390. }
  391. /**
  392. * uuencode a string.
  393. */
  394. public static Value convert_uuencode(StringValue source) {
  395. if (source.length() == 0) {
  396. return BooleanValue.FALSE;
  397. }
  398. StringValue result = new StringValue();
  399. int i = 0;
  400. int length = source.length();
  401. while (i < length) {
  402. int sublen = length - i;
  403. if (45 < sublen) {
  404. sublen = 45;
  405. }
  406. result.append((char) (sublen + 0x20));
  407. int end = i + sublen;
  408. while (i < end) {
  409. int code = source.charAt(i++) << 16;
  410. if (i < length) {
  411. code += source.charAt(i++) << 8;
  412. }
  413. if (i < length) {
  414. code += source.charAt(i++);
  415. }
  416. result.append(toUUChar(((code >> 18) & 0x3f)));
  417. result.append(toUUChar(((code >> 12) & 0x3f)));
  418. result.append(toUUChar(((code >> 6) & 0x3f)));
  419. result.append(toUUChar(((code) & 0x3f)));
  420. }
  421. result.append('\n');
  422. }
  423. result.append((char) 0x60);
  424. result.append('\n');
  425. return result;
  426. }
  427. /**
  428. * Returns an array of information about the characters.
  429. */
  430. public static Value count_chars(StringValue data,
  431. @Optional("0") int mode) {
  432. int[] count = new int[256];
  433. int length = data.length();
  434. for (int i = 0; i < length; i++) {
  435. count[data.charAt(i) & 0xff] += 1;
  436. }
  437. switch (mode) {
  438. case 0: {
  439. ArrayValue result = new ArrayValueImpl();
  440. for (int i = 0; i < count.length; i++) {
  441. result.put(LongValue.create(i), LongValue.create(count[i]));
  442. }
  443. return result;
  444. }
  445. case 1: {
  446. ArrayValue result = new ArrayValueImpl();
  447. for (int i = 0; i < count.length; i++) {
  448. if (count[i] > 0) {
  449. result.put(LongValue.create(i), LongValue.create(count[i]));
  450. }
  451. }
  452. return result;
  453. }
  454. case 2: {
  455. ArrayValue result = new ArrayValueImpl();
  456. for (int i = 0; i < count.length; i++) {
  457. if (count[i] == 0) {
  458. result.put(LongValue.create(i), LongValue.create(count[i]));
  459. }
  460. }
  461. return result;
  462. }
  463. case 3: {
  464. StringValue sb = new StringValue();
  465. for (int i = 0; i < count.length; i++) {
  466. if (count[i] > 0) {
  467. sb.append((char) i);
  468. }
  469. }
  470. return sb;
  471. }
  472. case 4: {
  473. StringValue sb = new StringValue();
  474. for (int i = 0; i < count.length; i++) {
  475. if (count[i] == 0) {
  476. sb.append((char) i);
  477. }
  478. }
  479. return sb;
  480. }
  481. default:
  482. return BooleanValue.FALSE;
  483. }
  484. }
  485. /**
  486. * Calculates the crc32 value for a string
  487. *
  488. * @param str the string value
  489. * @return the crc32 hash
  490. */
  491. public static long crc32(StringValue str) {
  492. return str.getCrc32Value();
  493. }
  494. public static String crypt(String string, @Optional String salt) {
  495. if (string == null) {
  496. string = "";
  497. }
  498. if (salt == null || salt.equals("")) {
  499. salt = ("" + Crypt.resultToChar(RandomUtil.nextInt(0x40))
  500. + Crypt.resultToChar(RandomUtil.nextInt(0x40)));
  501. }
  502. return Crypt.crypt(string, salt);
  503. }
  504. // TODO: echo
  505. /**
  506. * Explodes a string into an array
  507. *
  508. * @param separator the separator string
  509. * @param string the string to be exploded
  510. * @param limit the max number of elements
  511. * @return an array of exploded values
  512. */
  513. public static Value explode(Env env,
  514. StringValue separator,
  515. StringValue string,
  516. @Optional("0x7fffffff") long limit) {
  517. if (separator.length() == 0) {
  518. env.warning(L.l("Delimiter is empty"));
  519. return BooleanValue.FALSE;
  520. }
  521. int head = 0;
  522. ArrayValue array = new ArrayValueImpl();
  523. int separatorLength = separator.length();
  524. int stringLength = string.length();
  525. long ulimit;
  526. if (limit >= 0) {
  527. ulimit = limit;
  528. } else {
  529. ulimit = 0x7fffffff;
  530. }
  531. for (int i = 0; i < stringLength; ++i) {
  532. if (ulimit <= array.getSize() + 1) {
  533. break;
  534. }
  535. if (string.regionMatches(i, separator.toString(), 0)) {
  536. StringValue chunk = string.substring(head, i);
  537. array.append(chunk);
  538. head = i + separatorLength;
  539. i = head - 1;
  540. }
  541. }
  542. StringValue chunk = string.substring(head);
  543. array.append(chunk);
  544. while (array.getSize() > 0 && limit++ < 0) {
  545. array.pop(env);
  546. }
  547. return array;
  548. }
  549. /**
  550. * Use printf style formatting to write a string to a file.
  551. *
  552. * @param fd the file to write to
  553. * @param format the format string
  554. * @param args the valujes to apply to the format string
  555. */
  556. public static Value fprintf(Env env,
  557. @NotNull BinaryOutput os,
  558. StringValue format,
  559. Value[] args) {
  560. Value value = sprintf(env, format, args);
  561. return FileModule.fwrite(env, os, value.toInputStream(),
  562. Integer.MAX_VALUE);
  563. }
  564. /**
  565. * implodes an array into a string
  566. *
  567. * @param glueV the separator string
  568. * @param piecesV the array to be imploded
  569. * @return a string of imploded values
  570. */
  571. public static Value implode(Env env,
  572. Value glueV,
  573. @Optional Value piecesV) {
  574. StringValue glue;
  575. ArrayValue pieces;
  576. if ((piecesV.isArray() && glueV.isArray())
  577. || glueV.isArray()) {
  578. pieces = glueV.toArrayValue(env);
  579. glue = piecesV.toStringValue();
  580. } else if (piecesV.isArray()) {
  581. pieces = piecesV.toArrayValue(env);
  582. glue = glueV.toStringValue();
  583. } else {
  584. env.warning(L.l("neither argument to implode is an array: {0}, {1}",
  585. glueV.getClass().getName(), piecesV.getClass().getName()));
  586. return NullValue.NULL;
  587. }
  588. StringValue sb = new StringValue();
  589. boolean isFirst = true;
  590. Iterator<Value> iter = pieces.getValueIterator(env);
  591. while (iter.hasNext()) {
  592. if (!isFirst) {
  593. sb = sb.append(glue);
  594. }
  595. isFirst = false;
  596. sb = sb.append(iter.next());
  597. }
  598. return sb;
  599. }
  600. /**
  601. * implodes an array into a string
  602. *
  603. * @param glueV the separator string
  604. * @param piecesV the array to be imploded
  605. * @return a string of imploded values
  606. */
  607. public static Value join(Env env,
  608. Value glueV,
  609. Value piecesV) {
  610. return implode(env, glueV, piecesV);
  611. }
  612. /**
  613. * Lowercases the first character
  614. *
  615. * @param string the input string
  616. */
  617. public static StringValue lcfirst(Env env, StringValue string) {
  618. if (string == null) {
  619. return StringValue.EMPTY;
  620. } else if (string.length() == 0) {
  621. return string;
  622. }
  623. StringValue sb = new StringValue();
  624. sb = sb.append(Character.toLowerCase(string.charAt(0)));
  625. sb = sb.append(string, 1, string.length());
  626. return sb;
  627. }
  628. /**
  629. * Calculate Levenshtein distance between two strings
  630. *
  631. * @param str1 first string
  632. * @param str2 second string
  633. * @param cost_ins defines the cost of insertion
  634. * @param cost_rep defines the cost of replacement
  635. * @param cost_del defines the cost of deletion
  636. * @return int Levenshtein-Distance between the two argument strings
  637. */
  638. public static int levenshtein(Env env, String str1, String str2, @Optional("1") int cost_ins, @Optional("1") int cost_rep, @Optional("1") int cost_del) {
  639. int distance = -1;
  640. int i1, i2, c0, c1, c2, l1, l2;
  641. l1 = str1.length();
  642. l2 = str2.length();
  643. int[] p1 = new int[l2 + 1];
  644. int[] p2 = new int[l2 + 1];
  645. int[] tmp = new int[l2 + 1];
  646. if (l1 == 0) {
  647. return l2 * cost_ins;
  648. }
  649. if (l2 == 0) {
  650. return l1 * cost_del;
  651. }
  652. // TODO: keep this limitation ? really ?
  653. if ((l1 > LEVENSHTEIN_MAX_LENGTH) || (l2 > LEVENSHTEIN_MAX_LENGTH)) {
  654. distance = -1;
  655. } else {
  656. for (i2 = 0; i2 <= l2; i2++) {
  657. p1[i2] = i2 * cost_ins;
  658. }
  659. for (i1 = 0; i1 < l1; i1++) {
  660. p2[0] = p1[0] + cost_del;
  661. for (i2 = 0; i2 < l2; i2++) {
  662. c0 = p1[i2] + ((str1.charAt(i1) == str2.charAt(i2)) ? 0 : cost_rep);
  663. c1 = p1[i2 + 1] + cost_del;
  664. if (c1 < c0) {
  665. c0 = c1;
  666. }
  667. c2 = p2[i2] + cost_ins;
  668. if (c2 < c0) {
  669. c0 = c2;
  670. }
  671. p2[i2 + 1] = c0;
  672. }
  673. tmp = p1;
  674. p1 = p2;
  675. p2 = tmp;
  676. }
  677. c0 = p1[l2];
  678. distance = c0;
  679. }
  680. if (distance < 0) {
  681. env.warning(L.l("Argument string(s) too long"));
  682. }
  683. return distance;
  684. }
  685. /**
  686. * Gets locale-specific symbols.
  687. * XXX: locale charset
  688. */
  689. public static ArrayValue localeconv(Env env) {
  690. ArrayValueImpl array = new ArrayValueImpl();
  691. BiancaLocale money = env.getLocaleInfo().getMonetary();
  692. Locale locale = money.getLocale();
  693. DecimalFormatSymbols decimal = new DecimalFormatSymbols(locale);
  694. Currency currency = NumberFormat.getInstance(locale).getCurrency();
  695. array.put(env.createString("decimal_point"),
  696. new StringValue(decimal.getDecimalSeparator()));
  697. array.put(env.createString("thousands_sep"),
  698. new StringValue(decimal.getGroupingSeparator()));
  699. //array.put("grouping", "");
  700. array.put(env.createString("int_curr_symbol"),
  701. new StringValue(decimal.getInternationalCurrencySymbol()));
  702. array.put(env.createString("currency_symbol"),
  703. new StringValue(decimal.getCurrencySymbol()));
  704. array.put(env.createString("mon_decimal_point"),
  705. new StringValue(decimal.getMonetaryDecimalSeparator()));
  706. array.put(env.createString("mon_thousands_sep"),
  707. new StringValue(decimal.getGroupingSeparator()));
  708. //array.put("mon_grouping", "");
  709. array.put(new StringValue("positive_sign"), StringValue.EMPTY);
  710. array.put(env.createString("negative_sign"),
  711. new StringValue(decimal.getMinusSign()));
  712. array.put(env.createString("int_frac_digits"),
  713. LongValue.create(currency.getDefaultFractionDigits()));
  714. array.put(env.createString("frac_digits"),
  715. LongValue.create(currency.getDefaultFractionDigits()));
  716. //array.put("p_cs_precedes", "");
  717. //array.put("p_sep_by_space", "");
  718. //array.put("n_cs_precedes", "");
  719. //array.put("n_sep_by_space", "");
  720. //array.put("p_sign_posn", "");
  721. //array.put("n_sign_posn", "");
  722. return array;
  723. }
  724. /**
  725. * Removes leading whitespace.
  726. *
  727. * @param string the string to be trimmed
  728. * @param characters optional set of characters to trim
  729. * @return the trimmed string
  730. */
  731. public static StringValue ltrim(Env env,
  732. StringValue string,
  733. @Optional String characters) {
  734. if (characters == null) {
  735. characters = "";
  736. }
  737. boolean[] trim;
  738. if (characters.equals("")) {
  739. trim = TRIM_WHITESPACE;
  740. } else {
  741. trim = parseCharsetBitmap(env, characters);
  742. }
  743. for (int i = 0; i < string.length(); i++) {
  744. char ch = string.charAt(i);
  745. if (ch >= 256 || !trim[ch]) {
  746. if (i == 0) {
  747. return string;
  748. } else {
  749. return string.substring(i);
  750. }
  751. }
  752. }
  753. return StringValue.EMPTY;
  754. }
  755. /**
  756. * returns the md5 hash
  757. *
  758. * @param source the string
  759. * @param rawOutput if true, return the raw binary
  760. * @return a string of imploded values
  761. */
  762. public static Value md5(Env env,
  763. InputStream is,
  764. @Optional boolean rawOutput) {
  765. try {
  766. MessageDigest md = _md5FreeList.allocate();
  767. if (md == null) {
  768. md = MessageDigest.getInstance("MD5");
  769. }
  770. md.reset();
  771. int ch;
  772. while ((ch = is.read()) >= 0) {
  773. md.update((byte) ch);
  774. }
  775. byte[] digest = md.digest();
  776. _md5FreeList.free(md);
  777. return hashToValue(digest, rawOutput);
  778. } catch (Exception e) {
  779. throw new BiancaModuleException(e);
  780. }
  781. }
  782. /**
  783. * returns the md5 hash
  784. *
  785. * @param source the string
  786. * @param rawOutput if true, return the raw binary
  787. * @return a string of imploded values
  788. */
  789. public static Value md5_file(Env env,
  790. Path source,
  791. @Optional boolean rawOutput) {
  792. try {
  793. MessageDigest md = MessageDigest.getInstance("MD5");
  794. InputStream is = null;
  795. try {
  796. is = source.openRead();
  797. int d;
  798. while ((d = is.read()) >= 0) {
  799. md.update((byte) d);
  800. }
  801. byte[] digest = md.digest();
  802. return hashToValue(digest, rawOutput);
  803. } catch (IOException e) {
  804. log.log(Level.FINE, e.toString(), e);
  805. return BooleanValue.FALSE;
  806. } finally {
  807. try {
  808. if (is != null) {
  809. is.close();
  810. }
  811. } catch (IOException e) {
  812. }
  813. }
  814. } catch (Exception e) {
  815. throw new BiancaModuleException(e);
  816. }
  817. }
  818. /**
  819. * Returns the metaphone of a string.
  820. * This implementation produces identical results to the php version,
  821. * which does contain some bugs.
  822. */
  823. public static String metaphone(String string) {
  824. if (string == null) {
  825. string = "";
  826. }
  827. int length = string.length();
  828. int index = 0;
  829. char ch = 0;
  830. // ignore everything up until first letter
  831. for (; index < length; index++) {
  832. ch = toUpperCase(string.charAt(index));
  833. if ('A' <= ch && ch <= 'Z') {
  834. break;
  835. }
  836. }
  837. if (index == length) {
  838. return "";
  839. }
  840. int lastIndex = length - 1;
  841. StringBuilder result = new StringBuilder(length);
  842. // special case first letter
  843. char nextCh = index < lastIndex
  844. ? toUpperCase(string.charAt(index + 1))
  845. : 0;
  846. switch (ch) {
  847. case 'A':
  848. if (nextCh == 'E') {
  849. result.append('E');
  850. index += 2;
  851. } else {
  852. result.append('A');
  853. index += 1;
  854. }
  855. break;
  856. case 'E':
  857. case 'I':
  858. case 'O':
  859. case 'U':
  860. result.append(ch);
  861. index += 1;
  862. break;
  863. case 'G':
  864. case 'K':
  865. case 'P':
  866. if (nextCh == 'N') {
  867. result.append('N');
  868. index += 2;
  869. }
  870. break;
  871. case 'W':
  872. if (nextCh == 'H' || nextCh == 'R') {
  873. result.append(nextCh);
  874. index += 2;
  875. } else {
  876. switch (nextCh) {
  877. case 'A':
  878. case 'E':
  879. case 'I':
  880. case 'O':
  881. case 'U':
  882. result.append('W');
  883. index += 2;
  884. break;
  885. default:
  886. break;
  887. }
  888. }
  889. break;
  890. case 'X':
  891. result.append('S');
  892. index += 1;
  893. break;
  894. default:
  895. break;
  896. }
  897. // the rest of the letters
  898. char prevCh;
  899. for (; index < length; index++) {
  900. if (index > 0) {
  901. prevCh = toUpperCase(string.charAt(index - 1));
  902. } else {
  903. prevCh = 0;
  904. }
  905. ch = toUpperCase(string.charAt(index));
  906. if (ch < 'A' || ch > 'Z') {
  907. continue;
  908. }
  909. if (ch == prevCh && ch != 'C') {
  910. continue;
  911. }
  912. if (index + 1 < length) {
  913. nextCh = toUpperCase(string.charAt(index + 1));
  914. } else {
  915. nextCh = 0;
  916. }
  917. char nextnextCh;
  918. if (index + 2 < length) {
  919. nextnextCh = toUpperCase(string.charAt(index + 2));
  920. } else {
  921. nextnextCh = 0;
  922. }
  923. switch (ch) {
  924. case 'B':
  925. if (prevCh != 'M') {
  926. result.append('B');
  927. }
  928. break;
  929. case 'C':
  930. switch (nextCh) {
  931. case 'E':
  932. case 'I':
  933. case 'Y':
  934. // makesoft
  935. if (nextCh == 'I' && nextnextCh == 'A') {
  936. result.append('X');
  937. } else if (prevCh == 'S') {
  938. } else {
  939. result.append('S');
  940. }
  941. break;
  942. default:
  943. if (nextCh == 'H') {
  944. result.append('X');
  945. index++;
  946. } else {
  947. result.append('K');
  948. }
  949. break;
  950. }
  951. break;
  952. case 'D':
  953. if (nextCh == 'G') {
  954. switch (nextnextCh) {
  955. case 'E':
  956. case 'I':
  957. case 'Y':
  958. // makesoft
  959. result.append('J');
  960. index++;
  961. break;
  962. default:
  963. result.append('T');
  964. break;
  965. }
  966. } else {
  967. result.append('T');
  968. }
  969. break;
  970. case 'G':
  971. if (nextCh == 'H') {
  972. boolean isSilent = false;
  973. if (index - 3 >= 0) {
  974. char prev3Ch = toUpperCase(string.charAt(index - 3));
  975. switch (prev3Ch) {
  976. // noghtof
  977. case 'B':
  978. case 'D':
  979. case 'H':
  980. isSilent = true;
  981. break;
  982. default:
  983. break;
  984. }
  985. }
  986. if (!isSilent) {
  987. if (index - 4 >= 0) {
  988. char prev4Ch = toUpperCase(string.charAt(index - 4));
  989. isSilent = (prev4Ch == 'H');
  990. }
  991. }
  992. if (!isSilent) {
  993. result.append('F');
  994. index++;
  995. }
  996. } else if (nextCh == 'N') {
  997. char nextnextnextCh;
  998. if (index + 3 < length) {
  999. nextnextnextCh = toUpperCase(string.charAt(index + 3));
  1000. } else {
  1001. nextnextnextCh = 0;
  1002. }
  1003. if (nextnextCh < 'A' || nextnextCh > 'Z') {
  1004. } else if (nextnextCh == 'E' && nextnextnextCh == 'D') {
  1005. } else {
  1006. result.append('K');
  1007. }
  1008. } else if (prevCh == 'G') {
  1009. result.append('K');
  1010. } else {
  1011. switch (nextCh) {
  1012. case 'E':
  1013. case 'I':
  1014. case 'Y':
  1015. // makesoft
  1016. result.append('J');
  1017. break;
  1018. default:
  1019. result.append('K');
  1020. break;
  1021. }
  1022. }
  1023. break;
  1024. case 'H':
  1025. case 'W':
  1026. case 'Y':
  1027. switch (nextCh) {
  1028. case 'A':
  1029. case 'E':
  1030. case 'I':
  1031. case 'O':
  1032. case 'U':
  1033. // followed by a vowel
  1034. if (ch == 'H') {
  1035. switch (prevCh) {
  1036. case 'C':
  1037. case 'G':
  1038. case 'P':
  1039. case 'S':
  1040. case 'T':
  1041. // affecth
  1042. break;
  1043. default:
  1044. result.append('H');
  1045. break;
  1046. }
  1047. } else {
  1048. result.append(ch);
  1049. }
  1050. break;
  1051. default:
  1052. // not followed by a vowel
  1053. break;
  1054. }
  1055. break;
  1056. case 'K':
  1057. if (prevCh != 'C') {
  1058. result.append('K');
  1059. }
  1060. break;
  1061. case 'P':
  1062. if (nextCh == 'H') {
  1063. result.append('F');
  1064. } else {
  1065. result.append('P');
  1066. }
  1067. break;
  1068. case 'Q':
  1069. result.append('K');
  1070. break;
  1071. case 'S':
  1072. if (nextCh == 'I' && (nextnextCh == 'O' || nextnextCh == 'A')) {
  1073. result.append('X');
  1074. } else if (nextCh == 'H') {
  1075. result.append('X');
  1076. index++;
  1077. } else {
  1078. result.append('S');
  1079. }
  1080. break;
  1081. case 'T':
  1082. if (nextCh == 'I' && (nextnextCh == 'O' || nextnextCh == 'A')) {
  1083. result.append('X');
  1084. } else if (nextCh == 'H') {
  1085. result.append('0');
  1086. index++;
  1087. } else {
  1088. result.append('T');
  1089. }
  1090. break;
  1091. case 'V':
  1092. result.append('F');
  1093. break;
  1094. case 'X':
  1095. result.append('K');
  1096. result.append('S');
  1097. break;
  1098. case 'Z':
  1099. result.append('S');
  1100. break;
  1101. case 'F':
  1102. case 'J':
  1103. case 'L':
  1104. case 'M':
  1105. case 'N':
  1106. case 'R':
  1107. result.append(ch);
  1108. break;
  1109. default:
  1110. break;
  1111. }
  1112. }
  1113. return result.toString();
  1114. }
  1115. /**
  1116. * Returns a formatted money value.
  1117. * XXX: locale charset
  1118. *
  1119. * @param format the format
  1120. * @param value the value
  1121. * @return a string of formatted values
  1122. */
  1123. public static String money_format(Env env, String format, double value) {
  1124. BiancaLocale monetary = env.getLocaleInfo().getMonetary();
  1125. Locale locale = monetary.getLocale();
  1126. return NumberFormat.getCurrencyInstance(locale).format(value);
  1127. }
  1128. /**
  1129. * TODO: finish implementation of nl_langinfo
  1130. */
  1131. public static String nl_langinfo(Env env, int item) {
  1132. BiancaLocale money = env.getLocaleInfo().getMonetary();
  1133. Locale locale = money.getLocale();
  1134. DecimalFormatSymbols decimal = new DecimalFormatSymbols(locale);
  1135. Currency currency = NumberFormat.getInstance(locale).getCurrency();
  1136. String result;
  1137. switch (item) {
  1138. default:
  1139. result = Boolean.FALSE.toString();
  1140. break;
  1141. }
  1142. return result;
  1143. }
  1144. /**
  1145. * Returns a formatted number.
  1146. *
  1147. * @param value the value
  1148. * @param decimals the number of decimals
  1149. * @param pointValue the decimal point string
  1150. * @param groupValue the thousands separator
  1151. * @return a string of the formatted number
  1152. */
  1153. public static String number_format(Env env,
  1154. double value,
  1155. @Optional int decimals,
  1156. @Optional Value pointValue,
  1157. @Optional Value groupValue) {
  1158. boolean isGroupDefault = (groupValue instanceof DefaultValue);
  1159. boolean isPointDefault = (pointValue instanceof DefaultValue);
  1160. if (!isPointDefault && isGroupDefault) {
  1161. env.warning(L.l("wrong parameter count"));
  1162. return null;
  1163. }
  1164. String pattern;
  1165. char point = '.';
  1166. if (!pointValue.isNull()) {
  1167. String pointString = pointValue.toString();
  1168. point = (pointString.length() == 0) ? 0 : pointString.charAt(0);
  1169. }
  1170. char group = ',';
  1171. if (!groupValue.isNull()) {
  1172. String groupString = groupValue.toString();
  1173. group = (groupString.length() == 0) ? 0 : groupString.charAt(0);
  1174. }
  1175. if (decimals > 0) {
  1176. StringBuilder patternBuilder = new StringBuilder(6 + decimals);
  1177. patternBuilder.append(group == 0 ? "###0." : "#,##0.");
  1178. for (int i = 0; i < decimals; i++) {
  1179. patternBuilder.append('0');
  1180. }
  1181. pattern = patternBuilder.toString();
  1182. } else {
  1183. pattern = group == 0 ? "###0" : "#,##0";
  1184. }
  1185. DecimalFormatSymbols decimalFormatSymbols;
  1186. if (point == '.' && group == ',') {
  1187. decimalFormatSymbols = DEFAULT_DECIMAL_FORMAT_SYMBOLS;
  1188. } else {
  1189. decimalFormatSymbols = new DecimalFormatSymbols();
  1190. decimalFormatSymbols.setDecimalSeparator(point);
  1191. decimalFormatSymbols.setGroupingSeparator(group);
  1192. decimalFormatSymbols.setZeroDigit('0');
  1193. }
  1194. DecimalFormat format = new DecimalFormat(pattern, decimalFormatSymbols);
  1195. String result = format.format(value);
  1196. if (point == 0 && decimals > 0) {
  1197. // no way to get DecimalFormat to output nothing for the point,
  1198. // so remove it here
  1199. int i = result.lastIndexOf(point);
  1200. return result.substring(0, i) + result.substring(i + 1, result.length());
  1201. } else {
  1202. return result;
  1203. }
  1204. }
  1205. /**
  1206. * Converts the first character to an integer.
  1207. *
  1208. * @param string the string to be converted
  1209. * @return the integer value
  1210. */
  1211. public static long ord(StringValue string) {
  1212. if (string.length() == 0) {
  1213. return 0;
  1214. } else {
  1215. return string.charAt(0);
  1216. }
  1217. }
  1218. /**
  1219. * Parses the string as a query string.
  1220. *
  1221. * @param env the calling environment
  1222. * @param str the query string
  1223. * @param array the optional result array
  1224. */
  1225. @UsesSymbolTable
  1226. public static Value parse_str(Env env, StringValue str,
  1227. @Optional @Reference Value ref) {
  1228. boolean isRef = ref instanceof Var;
  1229. ArrayValue result = null;
  1230. if (isRef) {
  1231. result = new ArrayValueImpl();
  1232. ref.set(result);
  1233. } else if (ref instanceof ArrayValue) {
  1234. result = (ArrayValue) ref;
  1235. isRef = true;
  1236. } else {
  1237. result = new ArrayValueImpl();
  1238. }
  1239. return StringUtility.parseStr(env,
  1240. str,
  1241. result,
  1242. isRef,
  1243. env.getHttpInputEncoding());
  1244. }
  1245. /**
  1246. * Prints the string.
  1247. *
  1248. * @param env the bianca environment
  1249. * @param value the string to print
  1250. */
  1251. public static long print(Env env, Value value) {
  1252. value.print(env);
  1253. return 1;
  1254. }
  1255. /**
  1256. * print to the output with a formatter
  1257. *
  1258. * @param env the bianca environment
  1259. * @param format the format string
  1260. * @param args the format arguments
  1261. * @return the formatted string
  1262. */
  1263. public static int printf(Env env, StringValue format, Value[] args) {
  1264. Value str = sprintf(env, format, args);
  1265. str.print(env);
  1266. return str.length();
  1267. }
  1268. /**
  1269. * Converts a RFC2045 quoted printable string to a string.
  1270. */
  1271. // TODO: i18n
  1272. public static String quoted_printable_decode(String str) {
  1273. if (str == null) {
  1274. str = "";
  1275. }
  1276. StringBuilder sb = new StringBuilder();
  1277. int length = str.length();
  1278. for (int i = 0; i < length; i++) {
  1279. char ch = str.charAt(i);
  1280. if (33 <= ch && ch <= 60) {
  1281. sb.append(ch);
  1282. } else if (62 <= ch && ch <= 126) {
  1283. sb.append(ch);
  1284. } else if (ch == ' ' || ch == '\t') {
  1285. if (i + 1 < str.length()
  1286. && (str.charAt(i + 1) == '\r' || str.charAt(i + 1) == '\n')) {
  1287. sb.append('=');
  1288. sb.append(toUpperHexChar(ch >> 4));
  1289. sb.append(toUpperHexChar(ch));
  1290. } else {
  1291. sb.append(ch);
  1292. }
  1293. } else if (ch == '\r' || ch == '\n') {
  1294. sb.append(ch);
  1295. } else {
  1296. sb.append('=');
  1297. sb.append(toUpperHexChar(ch >> 4));
  1298. sb.append(toUpperHexChar(ch));
  1299. }
  1300. }
  1301. return sb.toString();
  1302. }
  1303. /**
  1304. * Escapes meta characters.
  1305. *
  1306. * @param string the string to be quoted
  1307. * @return the quoted
  1308. */
  1309. public static Value quotemeta(StringValue string) {
  1310. int len = string.length();
  1311. StringValue sb = new StringValue();
  1312. for (int i = 0; i < len; i++) {
  1313. char ch = string.charAt(i);
  1314. switch (ch) {
  1315. case '.':
  1316. case '\\':
  1317. case '+':
  1318. case '*':
  1319. case '?':
  1320. case '[':
  1321. case '^':
  1322. case ']':
  1323. case '(':
  1324. case ')':
  1325. case '$':
  1326. sb.append("\\");
  1327. sb.append(ch);
  1328. break;
  1329. default:
  1330. sb.append(ch);
  1331. break;
  1332. }
  1333. }
  1334. return sb;
  1335. }
  1336. private static final boolean[] TRIM_WHITESPACE = new boolean[256];
  1337. static {
  1338. TRIM_WHITESPACE['\0'] = true;
  1339. TRIM_WHITESPACE['\b'] = true;
  1340. TRIM_WHITESPACE[' '] = true;
  1341. TRIM_WHITESPACE['\t'] = true;
  1342. TRIM_WHITESPACE['\r'] = true;
  1343. TRIM_WHITESPACE['\n'] = true;
  1344. TRIM_WHITESPACE[0x0B] = true;
  1345. }
  1346. /**
  1347. * Removes trailing whitespace.
  1348. *
  1349. * @param env the bianca environment
  1350. * @param string the string to be trimmed
  1351. * @param characters optional set of characters to trim
  1352. * @return the trimmed string
  1353. */
  1354. public static StringValue rtrim(Env env,
  1355. StringValue string,
  1356. @Optional String characters) {
  1357. if (characters == null) {
  1358. characters = "";
  1359. }
  1360. boolean[] trim;
  1361. if (characters.equals("")) {
  1362. trim = TRIM_WHITESPACE;
  1363. } else {
  1364. trim = parseCharsetBitmap(env, characters);
  1365. }
  1366. for (int i = string.length() - 1; i >= 0; i--) {
  1367. char ch = string.charAt(i);
  1368. if (ch >= 256 || !trim[ch]) {
  1369. if (i == string.length()) {
  1370. return string;
  1371. } else {
  1372. return (StringValue) string.subSequence(0, i + 1);
  1373. }
  1374. }
  1375. }
  1376. return StringValue.EMPTY;
  1377. }
  1378. /**
  1379. * Sets locale configuration.
  1380. */
  1381. public static Value setlocale(Env env,
  1382. int category,
  1383. Value localeArg,
  1384. Value[] fallback) {
  1385. LocaleInfo localeInfo = env.getLocaleInfo();
  1386. if (localeArg instanceof ArrayValue) {
  1387. for (Value value : ((ArrayValue) localeArg).values()) {
  1388. BiancaLocale locale = setLocale(localeInfo,
  1389. category,
  1390. value.toString());
  1391. if (locale != null) {
  1392. return env.createString(locale.toString());
  1393. }
  1394. }
  1395. } else {
  1396. BiancaLocale locale = setLocale(localeInfo,
  1397. category,
  1398. localeArg.toString());
  1399. if (locale != null) {
  1400. return env.createString(locale.toString());
  1401. }
  1402. }
  1403. for (int i = 0; i < fallback.length; i++) {
  1404. BiancaLocale locale = setLocale(localeInfo,
  1405. category,
  1406. fallback[i].toString());
  1407. if (locale != null) {
  1408. return env.createString(locale.toString());
  1409. }
  1410. }
  1411. return BooleanValue.FALSE;
  1412. }
  1413. /**
  1414. * Sets locale configuration.
  1415. */
  1416. private static BiancaLocale setLocale(LocaleInfo localeInfo,
  1417. int category,
  1418. String localeName) {
  1419. BiancaLocale locale = findLocale(localeName);
  1420. if (locale == null) {
  1421. return null;
  1422. }
  1423. switch (category) {
  1424. case LC_ALL:
  1425. localeInfo.setAll(locale);
  1426. return localeInfo.getMessages();
  1427. case LC_COLLATE:
  1428. localeInfo.setCollate(locale);
  1429. return localeInfo.getCollate();
  1430. case LC_CTYPE:
  1431. localeInfo.setCtype(locale);
  1432. return localeInfo.getCtype();
  1433. case LC_MONETARY:
  1434. localeInfo.setMonetary(locale);
  1435. return localeInfo.getMonetary();
  1436. case LC_NUMERIC:
  1437. localeInfo.setNumeric(locale);
  1438. return localeInfo.getNumeric();
  1439. case LC_TIME:
  1440. localeInfo.setTime(locale);
  1441. return localeInfo.getTime();
  1442. case LC_MESSAGES:
  1443. localeInfo.setMessages(locale);
  1444. return localeInfo.getMessages();
  1445. default:
  1446. return null;
  1447. }
  1448. }
  1449. /*
  1450. * Example locale: fr_FR.UTF-8@euro, french (on Windows)
  1451. * (French, France, UTF-8, with euro currency support)
  1452. */
  1453. private static BiancaLocale findLocale(String localeName) {
  1454. String language;
  1455. String country;
  1456. String charset = null;
  1457. String variant = null;
  1458. CharBuffer sb = CharBuffer.allocate();
  1459. int len = localeName.length();
  1460. int i = 0;
  1461. char ch = 0;
  1462. while (i < len && (ch = localeName.charAt(i++)) != '-' && ch != '_') {
  1463. sb.append(ch);
  1464. }
  1465. language = sb.toString();
  1466. sb.clear();
  1467. while (i < len && (ch = localeName.charAt(i)) != '.' && ch != '@') {
  1468. sb.append(ch);
  1469. i++;
  1470. }
  1471. if (ch == '.') {
  1472. i++;
  1473. }
  1474. country = sb.toString();
  1475. sb.clear();
  1476. while (i < len && (ch = localeName.charAt(i)) != '@') {
  1477. sb.append(ch);
  1478. i++;
  1479. }
  1480. if (sb.length() > 0) {
  1481. charset = sb.toString();
  1482. }
  1483. if (i + 1 < len) {
  1484. variant = localeName.substring(i + 1);
  1485. }
  1486. Locale locale;
  1487. // java versions >= 1.5 should automatically use the euro sign
  1488. if (variant != null && !variant.equalsIgnoreCase("euro")) {
  1489. locale = new Locale(language, country, variant);
  1490. } else if (country != null) {
  1491. locale = new Locale(language, country);
  1492. } else {
  1493. locale = new Locale(language);
  1494. }
  1495. if (isValidLocale(locale)) {
  1496. return new BiancaLocale(locale, charset);
  1497. } else {
  1498. return null;
  1499. }
  1500. }
  1501. /**
  1502. * Returns true if the locale is supported.
  1503. */
  1504. private static boolean isValidLocale(Locale locale) {
  1505. Locale[] validLocales = Locale.getAvailableLocales();
  1506. for (int i = 0; i < validLocales.length; i++) {
  1507. if (validLocales[i].equals(locale)) {
  1508. return true;
  1509. }
  1510. }
  1511. return false;
  1512. }
  1513. /**
  1514. * returns the md5 hash
  1515. *
  1516. * @param source the string
  1517. * @param rawOutput if true, return the raw binary
  1518. * @return a string of imploded values
  1519. */
  1520. public static Value sha1(Env env,
  1521. String source,
  1522. @Optional boolean rawOutput) {
  1523. if (source == null) {
  1524. source = "";
  1525. }
  1526. try {
  1527. MessageDigest md = MessageDigest.getInstance("SHA1");
  1528. for (int i = 0; i < source.length(); i++) {
  1529. char ch = source.charAt(i);
  1530. md.update((byte) ch);
  1531. }
  1532. byte[] digest = md.digest();
  1533. return hashToValue(digest, rawOutput);
  1534. } catch (Exception e) {
  1535. throw new BiancaException(e);
  1536. }
  1537. }
  1538. /**
  1539. * returns the md5 hash
  1540. *
  1541. * @param source the string
  1542. * @param rawOutput if true, return the raw binary
  1543. * @return a string of imploded values
  1544. */
  1545. public static Value sha1_file(Env env,
  1546. Path source,
  1547. @Optional boolean rawOutput) {
  1548. try {
  1549. MessageDigest md = MessageDigest.getInstance("SHA1");
  1550. InputStream is = null;
  1551. try {
  1552. is = source.openRead();
  1553. int d;
  1554. while ((d = is.read()) >= 0) {
  1555. md.update((byte) d);
  1556. }
  1557. byte[] digest = md.digest();
  1558. return hashToValue(digest, rawOutput);
  1559. } catch (IOException e) {
  1560. log.log(Level.FINE, e.toString(), e);
  1561. return BooleanValue.FALSE;
  1562. } finally {
  1563. try {
  1564. if (is != null) {
  1565. is.close();
  1566. }
  1567. } catch (IOException e) {
  1568. }
  1569. }
  1570. } catch (Exception e) {
  1571. throw new BiancaException(e);
  1572. }
  1573. }
  1574. private static Value hashToValue(byte[] bytes, boolean isBinary) {
  1575. if (isBinary) {
  1576. StringValue v = new StringValue();
  1577. v.append(new String(bytes), 0, bytes.length);
  1578. return v;
  1579. } else {
  1580. StringValue v = new StringValue();
  1581. for (int i = 0; i < bytes.length; i++) {
  1582. int ch = bytes[i];
  1583. int d1 = (ch >> 4) & 0xf;
  1584. int d2 = (ch) & 0xf;
  1585. if (d1 < 10) {
  1586. v.append((char) ('0' + d1));
  1587. } else {
  1588. v.append((char) ('a' + d1 - 10));
  1589. }
  1590. if (d2 < 10) {
  1591. v.append((char) ('0' + d2));
  1592. } else {
  1593. v.append((char) ('a' + d2 - 10));
  1594. }
  1595. }
  1596. return v;
  1597. }
  1598. }
  1599. // TODO: similar_text
  1600. private static final char[] SOUNDEX_VALUES = "01230120022455012623010202".toCharArray();
  1601. public static Value soundex(StringValue string) {
  1602. int length = string.length();
  1603. if (length == 0) {
  1604. return BooleanValue.FALSE;
  1605. }
  1606. StringValue result = new StringValue();
  1607. int count = 0;
  1608. char lastCode = 0;
  1609. for (int i = 0; i < length && count < 4; i++) {
  1610. char ch = toUpperCase(string.charAt(i));
  1611. if ('A' <= ch && ch <= 'Z') {
  1612. char code = SOUNDEX_VALUES[ch - 'A'];
  1613. if (count == 0) {
  1614. result.append(ch);
  1615. count++;
  1616. } else if (code != '0' && code != lastCode) {
  1617. result.append(code);
  1618. count++;
  1619. }
  1620. lastCode = code;
  1621. }
  1622. }
  1623. for (; count < 4; count++) {
  1624. result.append('0');
  1625. }
  1626. return result;
  1627. }
  1628. /**
  1629. * Print to a string with a formatter
  1630. *
  1631. * @param format the format string
  1632. * @param args the format arguments
  1633. * @return the formatted string
  1634. */
  1635. public static Value sprintf(Env env, StringValue format, Value[] args) {
  1636. ArrayList<PrintfSegment> segments = parsePrintfFormat(env, format);
  1637. StringValue sb = new StringValue();
  1638. for (PrintfSegment segment : segments) {
  1639. if (!segment.apply(env, sb, args)) {
  1640. return BooleanValue.FALSE;
  1641. }
  1642. }
  1643. return sb;
  1644. }
  1645. private static ArrayList<PrintfSegment> parsePrintfFormat(Env env,
  1646. StringValue format) {
  1647. ArrayList<PrintfSegment> segments = new ArrayList<PrintfSegment>();
  1648. StringBuilder sb = new StringBuilder();
  1649. StringBuilder flags = new StringBuilder();
  1650. int length = format.length();
  1651. int index = 0;
  1652. for (int i = 0; i < length; i++) {
  1653. char ch = format.charAt(i);
  1654. if (i + 1 < length && ch == '%') {
  1655. // The C printf silently ignores invalid flags, so we need to
  1656. // remove them if present.
  1657. sb.append(ch);
  1658. boolean isLeft = false;
  1659. boolean isAlt = false;
  1660. boolean isShowSign = false;
  1661. int argIndex = -1;
  1662. int leftPadLength = 0;
  1663. int width = 0;
  1664. int padChar = -1;
  1665. flags.setLength(0);
  1666. int j = i + 1;
  1667. loop:
  1668. for (; j < length; j++) {
  1669. ch = format.charAt(j);
  1670. switch (ch) {
  1671. case '-':
  1672. isLeft = true;
  1673. if (j + 1 < length && format.charAt(j + 1) == '0') {
  1674. padChar = '0';
  1675. j++;
  1676. }
  1677. /*
  1678. for (int k = j + 1; k < length; k++) {
  1679. char digit = format.charAt(k);
  1680. if ('0' <= digit && digit <= '9') {
  1681. leftPadLength = leftPadLength * 10 + digit - '0';
  1682. j++;
  1683. }
  1684. else
  1685. break;
  1686. }
  1687. */
  1688. break;
  1689. case '#':
  1690. isAlt = true;
  1691. break;
  1692. case '0':
  1693. if (padChar < 0) {
  1694. padChar = '0';
  1695. } else {
  1696. int value = 0;
  1697. for (int k = j + 1; k < length; k++) {
  1698. char digit = format.charAt(k);
  1699. if ('0' <= digit && digit <= '9') {
  1700. value = value * 10 + digit - '0';
  1701. j++;
  1702. } else {
  1703. break;
  1704. }
  1705. }
  1706. if (j + 1 < length && format.charAt(j + 1) == '$') {
  1707. argIndex = value - 1;
  1708. j++;
  1709. } else {
  1710. width = value;
  1711. }
  1712. }
  1713. break;
  1714. case '1':
  1715. case '2':
  1716. case '3':
  1717. case '4':
  1718. case '5':
  1719. case '6':
  1720. case '7':
  1721. case '8':
  1722. case '9':
  1723. int value = ch - '0';
  1724. for (int k = j + 1; k < length; k++) {
  1725. char digit = format.charAt(k);
  1726. if ('0' <= digit && digit <= '9') {
  1727. value = value * 10 + digit - '0';
  1728. j++;
  1729. } else {
  1730. break;
  1731. }
  1732. }
  1733. if (j + 1 < length && format.charAt(j + 1) == '$') {
  1734. argIndex = value - 1;
  1735. j++;
  1736. } else {
  1737. width = value;
  1738. }
  1739. break;
  1740. case '\'':
  1741. padChar = format.charAt(j + 1);
  1742. j += 1;
  1743. break;
  1744. case '+':
  1745. isShowSign = true;
  1746. break;
  1747. case ' ':
  1748. case ',':
  1749. case '(':
  1750. flags.append(ch);
  1751. break;
  1752. default:
  1753. break loop;
  1754. }
  1755. }
  1756. int head = j;
  1757. if (argIndex < 0) {
  1758. argIndex = index;
  1759. }
  1760. loop:
  1761. for (; j < length; j++) {
  1762. ch = format.charAt(j);
  1763. switch (ch) {
  1764. case '%':
  1765. i = j;
  1766. segments.add(new TextPrintfSegment(sb));
  1767. sb.setLength(0);
  1768. break loop;
  1769. case '0':
  1770. case '1':
  1771. case '2':
  1772. case '3':
  1773. case '4':
  1774. case '5':
  1775. case '6':
  1776. case '7':
  1777. case '8':
  1778. case '9':
  1779. case '.':
  1780. case '$':
  1781. break;
  1782. case 's':
  1783. case 'S':
  1784. sb.setLength(sb.length() - 1);
  1785. if (width <= 0 && 0 < leftPadLength) {
  1786. width = leftPadLength;
  1787. }
  1788. index++;
  1789. segments.add(new StringPrintfSegment(
  1790. sb,
  1791. isLeft || isAlt,
  1792. padChar,
  1793. ch == 'S',
  1794. width,
  1795. format.substring(head, j).toString(),
  1796. argIndex));
  1797. sb.setLength(0);
  1798. i = j;
  1799. break loop;
  1800. case 'c':
  1801. case 'C':
  1802. sb.setLength(sb.length() - 1);
  1803. if (width <= 0 && 0 < leftPadLength) {
  1804. width = leftPadLength;
  1805. }
  1806. index++;
  1807. segments.add(new CharPrintfSegment(
  1808. sb,
  1809. isLeft || isAlt,
  1810. padChar,
  1811. ch == 'C',
  1812. width,
  1813. format.substring(head, j).toString(),
  1814. argIndex));
  1815. sb.setLength(0);
  1816. i = j;
  1817. break loop;
  1818. /*
  1819. case 'u':
  1820. sb.setLength(sb.length() - 1);
  1821. if (sb.length() > 0)
  1822. segments.add(new TextPrintfSegment(sb));
  1823. sb.setLength(0);
  1824. if (isLeft)
  1825. sb.append('-');
  1826. if (isAlt)
  1827. sb.append('#');
  1828. sb.append(flags);
  1829. sb.append(format, head, j);
  1830. sb.append(ch);
  1831. //segments.add(UnsignedLongPrintfSegment.create(
  1832. env, sb.toString(), index++));
  1833. sb.setLength(0);
  1834. i = j;
  1835. break loop;
  1836. */
  1837. case 'i':
  1838. ch = 'd';
  1839. case 'd':
  1840. case 'x':
  1841. case 'o':
  1842. case 'X':
  1843. case 'b':
  1844. case 'B':
  1845. case 'u':
  1846. sb.setLength(sb.length() - 1);
  1847. if (sb.length() > 0) {
  1848. segments.add(new TextPrintfSegment(sb));
  1849. }
  1850. sb.setLength(0);
  1851. if (isAlt) {
  1852. sb.append('#');
  1853. }
  1854. if (isShowSign) {
  1855. sb.append('+');
  1856. }
  1857. sb.append(flags);
  1858. if (width > 0) {
  1859. if (isLeft) {
  1860. sb.append('-');
  1861. } else if (padChar == '0') {
  1862. sb.append('0');
  1863. }
  1864. sb.append(width);
  1865. }
  1866. sb.append(format, head, j);
  1867. sb.append(ch);
  1868. index++;
  1869. segments.add(
  1870. LongPrintfSegment.create(env, sb.toString(), argIndex));
  1871. sb.setLength(0);
  1872. i = j;
  1873. break loop;
  1874. case 'e':
  1875. case 'E':
  1876. case 'f':
  1877. case 'g':
  1878. case 'G':
  1879. case 'F':
  1880. BiancaLocale locale = null;
  1881. if (ch == 'F') {
  1882. ch = 'f';
  1883. } else {
  1884. locale = env.getLocaleInfo().getNumeric();
  1885. }
  1886. sb.setLength(sb.length() - 1);
  1887. if (sb.length() > 0) {
  1888. segments.add(new TextPrintfSegment(sb));
  1889. }
  1890. sb.setLength(0);
  1891. if (isAlt) {
  1892. sb.append('#');
  1893. }
  1894. if (isShowSign) {
  1895. sb.append('+');
  1896. }
  1897. sb.append(flags);
  1898. if (width > 0) {
  1899. if (isLeft) {
  1900. sb.append('-');
  1901. } else if (padChar == '0') {
  1902. sb.append('0');
  1903. }
  1904. // '-' and '0' together is not supported by java.util.Formatter
  1905. //if (padChar == '0')
  1906. // sb.append((char) padChar);
  1907. sb.append(width);
  1908. }
  1909. sb.append(format, head, j);
  1910. sb.append(ch);
  1911. index++;
  1912. segments.add(new DoublePrintfSegment(sb.toString(),
  1913. isLeft && padChar == '0',
  1914. argIndex,
  1915. locale));
  1916. sb.setLength(0);
  1917. i = j;
  1918. break loop;
  1919. default:
  1920. if (isLeft) {
  1921. sb.append('-');
  1922. }
  1923. if (isAlt) {
  1924. sb.append('#');
  1925. }
  1926. sb.append(flags);
  1927. sb.append(format, head, j);
  1928. sb.append(ch);
  1929. i = j;
  1930. break loop;
  1931. }
  1932. }
  1933. } else {
  1934. sb.append(ch);
  1935. }
  1936. }
  1937. if (sb.length() > 0) {
  1938. segments.add(new TextPrintfSegment(sb));
  1939. }
  1940. return segments;
  1941. }
  1942. /**
  1943. * scans a string
  1944. *
  1945. * @param format the format string
  1946. * @param args the format arguments
  1947. * @return the formatted string
  1948. */
  1949. public static Value sscanf(Env env,
  1950. StringValue string,
  1951. StringValue format,
  1952. @Optional @Reference Value[] args) {
  1953. ScanfSegment[] formatArray = sscanfParseFormat(env, format);
  1954. int strlen = string.length();
  1955. int sIndex = 0;
  1956. boolean isReturnArray = args.length == 0;
  1957. int argIndex = 0;
  1958. if (strlen == 0) {
  1959. return isReturnArray ? NullValue.NULL : LongValue.MINUS_ONE;
  1960. }
  1961. ArrayValue array = new ArrayValueImpl();
  1962. for (int i = 0; i < formatArray.length; i++) {
  1963. ScanfSegment segment = formatArray[i];
  1964. Value var;
  1965. if (!segment.isAssigned()) {
  1966. var = null;
  1967. } else if (isReturnArray) {
  1968. var = array;
  1969. } else {
  1970. if (argIndex < args.length) {
  1971. var = args[argIndex];
  1972. if (sIndex < strlen) {
  1973. argIndex++;
  1974. }
  1975. } else {
  1976. env.warning(L.l("not enough vars passed in"));
  1977. var = NullValue.NULL;
  1978. }
  1979. }
  1980. sIndex = segment.apply(string,
  1981. strlen,
  1982. sIndex,
  1983. var,
  1984. isReturnArray);
  1985. if (sIndex < 0) {
  1986. if (isReturnArray) {
  1987. return sscanfFillNull(array, formatArray, i);
  1988. } else {
  1989. return LongValue.create(argIndex);
  1990. }
  1991. }
  1992. /*
  1993. else if (sIndex == strlen) {
  1994. if (isReturnArray)
  1995. return sscanfFillNull(array, formatArray, i + 1);
  1996. else
  1997. return LongValue.create(argIndex);
  1998. }
  1999. */
  2000. }
  2001. return sscanfReturn(env, array, args, argIndex, isReturnArray, false);
  2002. }
  2003. private static Value sscanfFillNull(
  2004. ArrayValue array, ScanfSegment[] formatArray, int fIndex) {
  2005. for (; fIndex < formatArray.length; fIndex++) {
  2006. ScanfSegment segment = formatArray[fIndex];
  2007. if (segment.isAssigned()) {
  2008. array.put(NullValue.NULL);
  2009. }
  2010. }
  2011. return array;
  2012. }
  2013. /**
  2014. * scans a string
  2015. *
  2016. * @param format the format string
  2017. * @param args the format arguments
  2018. * @return the formatted string
  2019. */
  2020. private static ScanfSegment[] sscanfParseFormat(Env env,
  2021. StringValue format) {
  2022. int fmtLen = format.length();
  2023. int fIndex = 0;
  2024. ArrayList<ScanfSegment> segmentList = new ArrayList<ScanfSegment>();
  2025. StringBuilder sb = new StringBuilder();
  2026. while (fIndex < fmtLen) {
  2027. char ch = format.charAt(fIndex++);
  2028. if (isWhitespace(ch)) {
  2029. for (;
  2030. (fIndex < fmtLen
  2031. && isWhitespace(ch = format.charAt(fIndex)));
  2032. fIndex++) {
  2033. }
  2034. scanfAddConstant(segmentList, sb);
  2035. segmentList.add(ScanfWhitespace.SEGMENT);
  2036. } else if (ch == '%') {
  2037. int maxLen = -1;
  2038. loop:
  2039. while (fIndex < fmtLen) {
  2040. ch = format.charAt(fIndex++);
  2041. switch (ch) {
  2042. case '%':
  2043. sb.append('%');
  2044. break loop;
  2045. case '0':
  2046. case '1':
  2047. case '2':
  2048. case '3':
  2049. case '4':
  2050. case '5':
  2051. case '6':
  2052. case '7':
  2053. case '8':
  2054. case '9':
  2055. if (maxLen < 0) {
  2056. maxLen = 0;
  2057. }
  2058. maxLen = 10 * maxLen + ch - '0';
  2059. break;
  2060. case 's': {
  2061. scanfAddConstant(segmentList, sb);
  2062. segmentList.add(new ScanfString(maxLen));
  2063. break loop;
  2064. }
  2065. case 'c': {
  2066. if (maxLen < 0) {
  2067. maxLen = 1;
  2068. }
  2069. scanfAddConstant(segmentList, sb);
  2070. segmentList.add(new ScanfString(maxLen));
  2071. break loop;
  2072. }
  2073. case 'n': {
  2074. scanfAddConstant(segmentList, sb);
  2075. segmentList.add(ScanfStringLength.SEGMENT);
  2076. break loop;
  2077. }
  2078. case 'd': {
  2079. scanfAddConstant(segmentList, sb);
  2080. segmentList.add(new ScanfInteger(maxLen, 10, false));
  2081. break loop;
  2082. }
  2083. case 'u': {
  2084. scanfAddConstant(segmentList, sb);
  2085. segmentList.add(new ScanfInteger(maxLen, 10, true));
  2086. break loop;
  2087. }
  2088. case 'o': {
  2089. scanfAddConstant(segmentList, sb);
  2090. segmentList.add(new ScanfInteger(maxLen, 8, false));
  2091. break loop;
  2092. }
  2093. case 'x':
  2094. case 'X': {
  2095. scanfAddConstant(segmentList, sb);
  2096. segmentList.add(new ScanfHex(maxLen));
  2097. break loop;
  2098. }
  2099. case 'e':
  2100. case 'f': {
  2101. scanfAddConstant(segmentList, sb);
  2102. segmentList.add(new ScanfScientific(maxLen));
  2103. break loop;
  2104. }
  2105. case '[': {
  2106. scanfAddConstant(segmentList, sb);
  2107. if (fmtLen <= fIndex) {
  2108. env.warning(L.l("expected ']', saw end of string"));
  2109. break loop;
  2110. }
  2111. boolean isNegated = false;
  2112. if (fIndex < fmtLen
  2113. && format.charAt(fIndex) == '^') {
  2114. isNegated = true;
  2115. fIndex++;
  2116. }
  2117. IntSet set = new IntSet();
  2118. while (true) {
  2119. if (fIndex == fmtLen) {
  2120. env.warning(L.l("expected ']', saw end of string"));
  2121. break loop;
  2122. }
  2123. char ch2 = format.charAt(fIndex++);
  2124. if (ch2 == ']') {
  2125. break;
  2126. } else {
  2127. set.union(ch2);
  2128. }
  2129. }
  2130. if (isNegated) {
  2131. segmentList.add(new ScanfSetNegated(set));
  2132. } else {
  2133. segmentList.add(new ScanfSet(set));
  2134. }
  2135. break loop;
  2136. }
  2137. default:
  2138. log.fine(L.l("'{0}' is a bad sscanf string.", format));
  2139. env.warning(L.l("'{0}' is a bad sscanf string.", format));
  2140. // TODO:
  2141. //return isAssign ? LongValue.create(argIndex) : array;
  2142. break loop;
  2143. }
  2144. }
  2145. } else {
  2146. sb.append(ch);
  2147. }
  2148. }
  2149. scanfAddConstant(segmentList, sb);
  2150. ScanfSegment[] segmentArray = new ScanfSegment[segmentList.size()];
  2151. return segmentList.toArray(segmentArray);
  2152. }
  2153. private static void scanfAddConstant(
  2154. ArrayList<ScanfSegment> segmentList, StringBuilder sb) {
  2155. if (sb.length() == 0) {
  2156. return;
  2157. }
  2158. segmentList.add(new ScanfConstant(sb.toString()));
  2159. sb.setLength(0);
  2160. }
  2161. /**
  2162. * scans a string
  2163. *
  2164. * @param format the format string
  2165. * @param args the format arguments
  2166. * @return the formatted string
  2167. */
  2168. public static Value sscanfOld(Env env,
  2169. StringValue string,
  2170. StringValue format,
  2171. @Optional @Reference Value[] args) {
  2172. int fmtLen = format.length();
  2173. int strlen = string.length();
  2174. int sIndex = 0;
  2175. int fIndex = 0;
  2176. boolean isAssign = args.length != 0;
  2177. boolean isReturnArray = !isAssign;
  2178. int argIndex = 0;
  2179. if (strlen == 0) {
  2180. return isAssign ? LongValue.MINUS_ONE : NullValue.NULL;
  2181. }
  2182. ArrayValue array = new ArrayValueImpl();
  2183. while (fIndex < fmtLen) {
  2184. char ch = format.charAt(fIndex++);
  2185. if (isWhitespace(ch)) {
  2186. for (;
  2187. (fIndex < fmtLen
  2188. && isWhitespace(ch = format.charAt(fIndex)));
  2189. fIndex++) {
  2190. }
  2191. /*ch = string.charAt(sIndex);
  2192. if (! isWhitespace(ch)) {
  2193. // TODO: return false?
  2194. return sscanfReturn(env, array, args, argIndex, isAssign, true);
  2195. }*/
  2196. for (;
  2197. sIndex < strlen && isWhitespace(string.charAt(sIndex));
  2198. sIndex++) {
  2199. }
  2200. } else if (ch == '%') {
  2201. int maxLen = -1;
  2202. loop:
  2203. while (fIndex < fmtLen) {
  2204. ch = format.charAt(fIndex++);
  2205. if (sIndex >= strlen && ch != 'n') {
  2206. array.append(NullValue.NULL);
  2207. break loop;
  2208. }
  2209. Value obj;
  2210. if (isAssign) {
  2211. if (argIndex < args.length) {
  2212. obj = args[argIndex++];
  2213. } else {
  2214. env.warning(L.l("not enough vars passed in"));
  2215. break loop;
  2216. }
  2217. } else {
  2218. obj = array;
  2219. }
  2220. switch (ch) {
  2221. case '%':
  2222. if (string.charAt(sIndex) != '%') {
  2223. return sscanfReturn(env, array, args, argIndex, isAssign, true);
  2224. } else {
  2225. break loop;
  2226. }
  2227. case '0':
  2228. case '1':
  2229. case '2':
  2230. case '3':
  2231. case '4':
  2232. case '5':
  2233. case '6':
  2234. case '7':
  2235. case '8':
  2236. case '9':
  2237. if (maxLen < 0) {
  2238. maxLen = 0;
  2239. }
  2240. maxLen = 10 * maxLen + ch - '0';
  2241. break;
  2242. case 's': {
  2243. ScanfSegment seg = new ScanfString(maxLen);
  2244. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2245. break loop;
  2246. }
  2247. case 'c': {
  2248. if (maxLen < 0) {
  2249. maxLen = 1;
  2250. }
  2251. ScanfSegment seg = new ScanfString(maxLen);
  2252. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2253. break loop;
  2254. }
  2255. case 'n': {
  2256. ScanfSegment seg = ScanfStringLength.SEGMENT;
  2257. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2258. break loop;
  2259. }
  2260. case 'd': {
  2261. ScanfSegment seg = new ScanfInteger(maxLen, 10, false);
  2262. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2263. break loop;
  2264. }
  2265. case 'u': {
  2266. ScanfSegment seg = new ScanfInteger(maxLen, 10, true);
  2267. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2268. break loop;
  2269. }
  2270. case 'o': {
  2271. ScanfSegment seg = new ScanfInteger(maxLen, 8, false);
  2272. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2273. break loop;
  2274. }
  2275. case 'x':
  2276. case 'X': {
  2277. ScanfSegment seg = new ScanfHex(maxLen);
  2278. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2279. break loop;
  2280. }
  2281. case 'e':
  2282. case 'f': {
  2283. ScanfSegment seg = new ScanfScientific(maxLen);
  2284. sIndex = seg.apply(string, strlen, sIndex, obj, isReturnArray);
  2285. break loop;
  2286. }
  2287. default:
  2288. log.fine(L.l("'{0}' is a bad sscanf string.", format));
  2289. env.warning(L.l("'{0}' is a bad sscanf string.", format));
  2290. return isAssign ? LongValue.create(argIndex) : array;
  2291. }
  2292. }
  2293. } else if (ch == string.charAt(sIndex)) {
  2294. sIndex++;
  2295. } else {
  2296. return sscanfReturn(env, array, args, argIndex, false, true);
  2297. }
  2298. }
  2299. return sscanfReturn(env, array, args, argIndex, isAssign, false);
  2300. }
  2301. private static Value sscanfReturn(Env env,
  2302. ArrayValue array,
  2303. Value[] args,
  2304. int argIndex,
  2305. boolean isReturnArray,
  2306. boolean isWarn) {
  2307. if (isReturnArray) {
  2308. return array;
  2309. } else {
  2310. if (isWarn && argIndex != args.length) {
  2311. env.warning(
  2312. L.l("{0} vars passed in but saw only {1} '%' args",
  2313. args.length, argIndex));
  2314. }
  2315. return LongValue.create(argIndex);
  2316. }
  2317. }
  2318. /**
  2319. * Scans a string with a given length.
  2320. */
  2321. private static int sscanfString(StringValue string,
  2322. int sIndex,
  2323. int maxLen,
  2324. Value obj,
  2325. boolean isAssignment) {
  2326. int strlen = string.length();
  2327. if (maxLen < 0) {
  2328. maxLen = Integer.MAX_VALUE;
  2329. }
  2330. StringValue sb = new StringValue();
  2331. for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
  2332. char ch = string.charAt(sIndex);
  2333. if (!isWhitespace(ch)) {
  2334. sb.append(ch);
  2335. } else {
  2336. break;
  2337. }
  2338. }
  2339. sscanfPut(obj, sb, isAssignment);
  2340. return sIndex;
  2341. }
  2342. private static void sscanfPut(Value obj, Value val, boolean isAssignment) {
  2343. if (isAssignment) {
  2344. obj.set(val);
  2345. } else {
  2346. obj.put(val);
  2347. }
  2348. }
  2349. /**
  2350. * Scans a integer with a given length.
  2351. */
  2352. private static int sscanfInteger(StringValue string,
  2353. int sIndex,
  2354. int maxLen,
  2355. Value obj,
  2356. boolean isAssign,
  2357. int base,
  2358. boolean isUnsigned) {
  2359. int strlen = string.length();
  2360. if (maxLen < 0) {
  2361. maxLen = Integer.MAX_VALUE;
  2362. }
  2363. int val = 0;
  2364. int sign = 1;
  2365. boolean isNotMatched = true;
  2366. if (sIndex < strlen) {
  2367. char ch = string.charAt(sIndex);
  2368. if (ch == '+') {
  2369. sIndex++;
  2370. maxLen--;
  2371. } else if (ch == '-') {
  2372. sign = -1;
  2373. sIndex++;
  2374. maxLen--;
  2375. }
  2376. }
  2377. int topRange = base + '0';
  2378. for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
  2379. char ch = string.charAt(sIndex);
  2380. if ('0' <= ch && ch < topRange) {
  2381. val = val * base + ch - '0';
  2382. isNotMatched = false;
  2383. } else if (isNotMatched) {
  2384. sscanfPut(obj, NullValue.NULL, isAssign);
  2385. return sIndex;
  2386. } else {
  2387. break;
  2388. }
  2389. }
  2390. if (isUnsigned) {
  2391. if (sign == -1 && val != 0) {
  2392. sscanfPut(obj, StringValue.create(0xFFFFFFFFL - val + 1), isAssign);
  2393. } else {
  2394. sscanfPut(obj, LongValue.create(val), isAssign);
  2395. }
  2396. } else {
  2397. sscanfPut(obj, LongValue.create(val * sign), isAssign);
  2398. }
  2399. return sIndex;
  2400. }
  2401. /**
  2402. * Scans a integer with a given length.
  2403. */
  2404. private static int sscanfHex(StringValue string,
  2405. int sIndex,
  2406. int maxLen,
  2407. Value obj,
  2408. boolean isAssign) {
  2409. int strlen = string.length();
  2410. if (maxLen < 0) {
  2411. maxLen = Integer.MAX_VALUE;
  2412. }
  2413. int val = 0;
  2414. int sign = 1;
  2415. boolean isMatched = false;
  2416. if (sIndex < strlen) {
  2417. char ch = string.charAt(sIndex);
  2418. if (ch == '+') {
  2419. sIndex++;
  2420. maxLen--;
  2421. } else if (ch == '-') {
  2422. sign = -1;
  2423. sIndex++;
  2424. maxLen--;
  2425. }
  2426. }
  2427. for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
  2428. char ch = string.charAt(sIndex);
  2429. if ('0' <= ch && ch <= '9') {
  2430. val = val * 16 + ch - '0';
  2431. isMatched = true;
  2432. } else if ('a' <= ch && ch <= 'f') {
  2433. val = val * 16 + ch - 'a' + 10;
  2434. isMatched = true;
  2435. } else if ('A' <= ch && ch <= 'F') {
  2436. val = val * 16 + ch - 'A' + 10;
  2437. isMatched = true;
  2438. } else if (!isMatched) {
  2439. sscanfPut(obj, NullValue.NULL, isAssign);
  2440. return sIndex;
  2441. } else {
  2442. break;
  2443. }
  2444. }
  2445. sscanfPut(obj, LongValue.create(val * sign), isAssign);
  2446. return sIndex;
  2447. }
  2448. /**
  2449. * Scans a integer with a given length.
  2450. */
  2451. private static int sscanfScientific(StringValue s,
  2452. int i,
  2453. int maxLen,
  2454. Value obj,
  2455. boolean isAssign) {
  2456. if (maxLen < 0) {
  2457. maxLen = Integer.MAX_VALUE;
  2458. }
  2459. int start = i;
  2460. int len = s.length();
  2461. int ch = 0;
  2462. if (i < len && maxLen > 0 && ((ch = s.charAt(i)) == '+' || ch == '-')) {
  2463. i++;
  2464. maxLen--;
  2465. }
  2466. for (; i < len && maxLen > 0
  2467. && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
  2468. maxLen--;
  2469. }
  2470. if (ch == '.') {
  2471. maxLen--;
  2472. for (i++; i < len && maxLen > 0
  2473. && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
  2474. maxLen--;
  2475. }
  2476. }
  2477. if (ch == 'e' || ch == 'E') {
  2478. maxLen--;
  2479. int e = i++;
  2480. if (start == e) {
  2481. sscanfPut(obj, NullValue.NULL, isAssign);
  2482. return start;
  2483. }
  2484. if (i < len && maxLen > 0 && (ch = s.charAt(i)) == '+' || ch == '-') {
  2485. i++;
  2486. maxLen--;
  2487. }
  2488. for (; i < len && maxLen > 0
  2489. && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
  2490. maxLen--;
  2491. }
  2492. if (i == e + 1) {
  2493. i = e;
  2494. }
  2495. }
  2496. double val;
  2497. if (i == 0) {
  2498. val = 0;
  2499. } else {
  2500. val = Double.parseDouble(s.substring(start, i).toString());
  2501. }
  2502. sscanfPut(obj, DoubleValue.create(val), isAssign);
  2503. return i;
  2504. }
  2505. // TODO: str_getcsv
  2506. /**
  2507. * replaces substrings.
  2508. *
  2509. * @param search search string
  2510. * @param replace replacement string
  2511. * @param subject replacement
  2512. * @param count return value
  2513. */
  2514. public static Value str_ireplace(Env env,
  2515. Value search,
  2516. Value replace,
  2517. Value subject,
  2518. @Reference @Optional Value count) {
  2519. return strReplace(env, search, replace, subject, count, true);
  2520. }
  2521. /**
  2522. * Pads strings
  2523. *
  2524. * @param string string
  2525. * @param length length
  2526. * @param pad padding string
  2527. * @param type padding type
  2528. */
  2529. public static StringValue str_pad(StringValue string,
  2530. int length,
  2531. @Optional("' '") String pad,
  2532. @Optional("STR_PAD_RIGHT") int type) {
  2533. int strLen = string.length();
  2534. int padLen = length - strLen;
  2535. if (padLen <= 0) {
  2536. return string;
  2537. }
  2538. if (pad == null || pad.length() == 0) {
  2539. pad = " ";
  2540. }
  2541. int leftPad = 0;
  2542. int rightPad = 0;
  2543. switch (type) {
  2544. case STR_PAD_LEFT:
  2545. leftPad = padLen;
  2546. break;
  2547. case STR_PAD_RIGHT:
  2548. default:
  2549. rightPad = padLen;
  2550. break;
  2551. case STR_PAD_BOTH:
  2552. leftPad = padLen / 2;
  2553. rightPad = padLen - leftPad;
  2554. break;
  2555. }
  2556. int padStringLen = pad.length();
  2557. StringValue sb = new StringValue();
  2558. for (int i = 0; i < leftPad; i++) {
  2559. sb.append(pad.charAt(i % padStringLen));
  2560. }
  2561. sb = sb.append(string);
  2562. for (int i = 0; i < rightPad; i++) {
  2563. sb.append(pad.charAt(i % padStringLen));
  2564. }
  2565. return sb;
  2566. }
  2567. /**
  2568. * repeats a string
  2569. *
  2570. * @param string string to repeat
  2571. * @param count number of times to repeat
  2572. */
  2573. public static Value str_repeat(StringValue string, int count) {
  2574. StringValue sb = new StringValue();
  2575. for (int i = 0; i < count; i++) {
  2576. sb = sb.append(string);
  2577. }
  2578. return sb;
  2579. }
  2580. /**
  2581. * replaces substrings.
  2582. *
  2583. * @param search search string
  2584. * @param replace replacement string
  2585. * @param subject replacement
  2586. * @param count return value
  2587. */
  2588. public static Value str_replace(Env env,
  2589. Value search,
  2590. Value replace,
  2591. Value subject,
  2592. @Reference @Optional Value count) {
  2593. return strReplace(env, search, replace, subject, count, false);
  2594. }
  2595. /**
  2596. * replaces substrings.
  2597. *
  2598. * @param search search string
  2599. * @param replace replacement string
  2600. * @param subject replacement
  2601. * @param count return value
  2602. */
  2603. private static Value strReplace(Env env,
  2604. Value search,
  2605. Value replace,
  2606. Value subject,
  2607. @Reference @Optional Value count,
  2608. boolean isInsensitive) {
  2609. count.set(LongValue.ZERO);
  2610. if (subject.isNull()) {
  2611. return StringValue.EMPTY;
  2612. }
  2613. if (search.isNull()) {
  2614. return subject;
  2615. }
  2616. if (subject instanceof ArrayValue) {
  2617. ArrayValue subjectArray = (ArrayValue) subject;
  2618. ArrayValue resultArray = new ArrayValueImpl();
  2619. for (Map.Entry<Value, Value> entry : subjectArray.entrySet()) {
  2620. if (entry.getValue() instanceof ArrayValue) {
  2621. resultArray.append(entry.getKey(), entry.getValue());
  2622. } else {
  2623. Value result = strReplaceImpl(env,
  2624. search,
  2625. replace,
  2626. entry.getValue().toStringValue(),
  2627. count,
  2628. isInsensitive);
  2629. resultArray.append(entry.getKey(), result);
  2630. }
  2631. }
  2632. return resultArray;
  2633. } else {
  2634. StringValue subjectString = subject.toStringValue();
  2635. if (subjectString.length() == 0) {
  2636. return StringValue.EMPTY;
  2637. }
  2638. return strReplaceImpl(env,
  2639. search,
  2640. replace,
  2641. subjectString,
  2642. count,
  2643. isInsensitive);
  2644. }
  2645. }
  2646. /**
  2647. * replaces substrings.
  2648. *
  2649. * @param search search string
  2650. * @param replace replacement string
  2651. * @param subject replacement
  2652. * @param count return value
  2653. */
  2654. private static Value strReplaceImpl(Env env,
  2655. Value search,
  2656. Value replace,
  2657. StringValue subject,
  2658. Value count,
  2659. boolean isInsensitive) {
  2660. if (!search.isArray()) {
  2661. StringValue searchString = search.toStringValue();
  2662. if (searchString.length() == 0) {
  2663. return subject;
  2664. }
  2665. if (replace instanceof ArrayValue) {
  2666. env.warning(L.l("Array to string conversion"));
  2667. }
  2668. subject = strReplaceImpl(env,
  2669. searchString,
  2670. replace.toStringValue(),
  2671. subject,
  2672. count,
  2673. isInsensitive);
  2674. } else if (replace instanceof ArrayValue) {
  2675. ArrayValue searchArray = (ArrayValue) search;
  2676. ArrayValue replaceArray = (ArrayValue) replace;
  2677. Iterator<Value> searchIter = searchArray.values().iterator();
  2678. Iterator<Value> replaceIter = replaceArray.values().iterator();
  2679. while (searchIter.hasNext()) {
  2680. Value searchItem = searchIter.next();
  2681. Value replaceItem = replaceIter.next();
  2682. if (replaceItem == null) {
  2683. replaceItem = NullValue.NULL;
  2684. }
  2685. subject = strReplaceImpl(env,
  2686. searchItem.toStringValue(),
  2687. replaceItem.toStringValue(),
  2688. subject,
  2689. count,
  2690. isInsensitive);
  2691. }
  2692. } else {
  2693. ArrayValue searchArray = (ArrayValue) search;
  2694. Iterator<Value> searchIter = searchArray.values().iterator();
  2695. while (searchIter.hasNext()) {
  2696. Value searchItem = searchIter.next();
  2697. subject = strReplaceImpl(env,
  2698. searchItem.toStringValue(),
  2699. replace.toStringValue(),
  2700. subject,
  2701. count,
  2702. isInsensitive);
  2703. }
  2704. }
  2705. return subject;
  2706. }
  2707. /**
  2708. * replaces substrings.
  2709. *
  2710. * @param search search string
  2711. * @param replace replacement string
  2712. * @param subject replacement
  2713. * @param countV return value
  2714. */
  2715. private static StringValue strReplaceImpl(Env env,
  2716. StringValue search,
  2717. StringValue replace,
  2718. StringValue subject,
  2719. Value countV,
  2720. boolean isInsensitive) {
  2721. long count = countV.toLong();
  2722. int head = 0;
  2723. int next;
  2724. int searchLen = search.length();
  2725. StringValue result = null;
  2726. while (head <= (next = indexOf(subject, search, head, isInsensitive))) {
  2727. if (result == null) {
  2728. result = new StringValue();
  2729. }
  2730. result = result.append(subject, head, next);
  2731. result = result.append(replace);
  2732. if (head < next + searchLen) {
  2733. head = next + searchLen;
  2734. } else {
  2735. head += 1;
  2736. }
  2737. count++;
  2738. }
  2739. if (count != 0 && result != null) {
  2740. countV.set(LongValue.create(count));
  2741. int subjectLength = subject.length();
  2742. if (head > 0 && head < subjectLength) {
  2743. result = result.append(subject, head, subjectLength);
  2744. }
  2745. return result;
  2746. } else {
  2747. return subject;
  2748. }
  2749. }
  2750. /**
  2751. * Returns the next index.
  2752. */
  2753. private static int indexOf(StringValue subject,
  2754. StringValue match,
  2755. int head,
  2756. boolean isInsensitive) {
  2757. if (!isInsensitive) {
  2758. return subject.indexOf(match, head);
  2759. } else {
  2760. int length = subject.length();
  2761. int matchLen = match.length();
  2762. if (matchLen <= 0) {
  2763. return -1;
  2764. }
  2765. char ch = Character.toLowerCase(match.charAt(0));
  2766. loop:
  2767. for (; head + matchLen <= length; head++) {
  2768. if (ch == Character.toLowerCase(subject.charAt(head))) {
  2769. for (int i = 1; i < matchLen; i++) {
  2770. if (Character.toLowerCase(subject.charAt(head + i))
  2771. != Character.toLowerCase(match.charAt(i))) {
  2772. continue loop;
  2773. }
  2774. }
  2775. return head;
  2776. }
  2777. }
  2778. return -1;
  2779. }
  2780. }
  2781. /**
  2782. * rot13 conversion
  2783. *
  2784. * @param string string to convert
  2785. */
  2786. public static Value str_rot13(StringValue string) {
  2787. if (string == null) {
  2788. return NullValue.NULL;
  2789. }
  2790. StringValue sb = new StringValue();
  2791. int len = string.length();
  2792. for (int i = 0; i < len; i++) {
  2793. char ch = string.charAt(i);
  2794. if ('a' <= ch && ch <= 'z') {
  2795. int off = ch - 'a';
  2796. sb.append((char) ('a' + (off + 13) % 26));
  2797. } else if ('A' <= ch && ch <= 'Z') {
  2798. int off = ch - 'A';
  2799. sb.append((char) ('A' + (off + 13) % 26));
  2800. } else {
  2801. sb.append(ch);
  2802. }
  2803. }
  2804. return sb;
  2805. }
  2806. /**
  2807. * shuffles a string
  2808. */
  2809. public static String str_shuffle(String string) {
  2810. if (string == null) {
  2811. string = "";
  2812. }
  2813. char[] chars = string.toCharArray();
  2814. int length = chars.length;
  2815. for (int i = 0; i < length; i++) {
  2816. int rand = RandomUtil.nextInt(length);
  2817. char temp = chars[rand];
  2818. chars[rand] = chars[i];
  2819. chars[i] = temp;
  2820. }
  2821. return new String(chars);
  2822. }
  2823. /**
  2824. * split into an array
  2825. *
  2826. * @param string string to split
  2827. * @param chunk chunk size
  2828. */
  2829. public static Value str_split(StringValue string,
  2830. @Optional("1") int chunk) {
  2831. ArrayValue array = new ArrayValueImpl();
  2832. if (string.length() == 0) {
  2833. array.put(string);
  2834. return array;
  2835. }
  2836. int strLen = string.length();
  2837. for (int i = 0; i < strLen; i += chunk) {
  2838. Value value;
  2839. if (i + chunk <= strLen) {
  2840. value = string.substring(i, i + chunk);
  2841. } else {
  2842. value = string.substring(i);
  2843. }
  2844. array.put(LongValue.create(i), value);
  2845. }
  2846. return array;
  2847. }
  2848. public static Value str_word_count(StringValue string,
  2849. @Optional int format,
  2850. @Optional String additionalWordCharacters) {
  2851. if (format < 0 || format > 2) {
  2852. return NullValue.NULL;
  2853. }
  2854. int strlen = string.length();
  2855. boolean isAdditionalWordCharacters = false;
  2856. if (additionalWordCharacters != null) {
  2857. isAdditionalWordCharacters = additionalWordCharacters.length() > 0;
  2858. }
  2859. ArrayValueImpl resultArray = null;
  2860. if (format > 0) {
  2861. resultArray = new ArrayValueImpl();
  2862. }
  2863. boolean isBetweenWords = true;
  2864. int wordCount = 0;
  2865. int lastWordStart = 0;
  2866. for (int i = 0; i <= strlen; i++) {
  2867. boolean isWordCharacter;
  2868. if (i < strlen) {
  2869. int ch = string.charAt(i);
  2870. isWordCharacter = Character.isLetter(ch)
  2871. || ch == '-'
  2872. || ch == '\''
  2873. || (isAdditionalWordCharacters
  2874. && additionalWordCharacters.indexOf(ch) > -1);
  2875. } else {
  2876. isWordCharacter = false;
  2877. }
  2878. if (isWordCharacter) {
  2879. if (isBetweenWords) {
  2880. // starting a word
  2881. isBetweenWords = false;
  2882. lastWordStart = i;
  2883. wordCount++;
  2884. }
  2885. } else {
  2886. if (!isBetweenWords) {
  2887. // finished a word
  2888. isBetweenWords = true;
  2889. if (format > 0) {
  2890. StringValue word = string.substring(lastWordStart, i);
  2891. if (format == 1) {
  2892. resultArray.append(word);
  2893. } else if (format == 2) {
  2894. resultArray.put(LongValue.create(lastWordStart), word);
  2895. }
  2896. }
  2897. }
  2898. }
  2899. }
  2900. if (resultArray == null) {
  2901. return LongValue.create(wordCount);
  2902. } else {
  2903. return resultArray;
  2904. }
  2905. }
  2906. /**
  2907. * Case-insensitive comparison
  2908. *
  2909. * @param a left value
  2910. * @param b right value
  2911. * @return -1, 0, or 1
  2912. */
  2913. public static int strcasecmp(StringValue a, StringValue b) {
  2914. int aLen = a.length();
  2915. int bLen = b.length();
  2916. for (int i = 0; i < aLen && i < bLen; i++) {
  2917. char chA = a.charAt(i);
  2918. char chB = b.charAt(i);
  2919. if (chA == chB) {
  2920. continue;
  2921. }
  2922. if (Character.isUpperCase(chA)) {
  2923. chA = Character.toLowerCase(chA);
  2924. }
  2925. if (Character.isUpperCase(chB)) {
  2926. chB = Character.toLowerCase(chB);
  2927. }
  2928. if (chA == chB) {
  2929. continue;
  2930. } else if (chA < chB) {
  2931. return -1;
  2932. } else {
  2933. return 1;
  2934. }
  2935. }
  2936. if (aLen == bLen) {
  2937. return 0;
  2938. } else if (aLen < bLen) {
  2939. return -1;
  2940. } else {
  2941. return 1;
  2942. }
  2943. }
  2944. /**
  2945. * Finds the index of a substring
  2946. *
  2947. * @param env the calling environment
  2948. */
  2949. public static Value strchr(Env env, StringValue haystack, Value needle) {
  2950. return strstr(env, haystack, needle);
  2951. }
  2952. /**
  2953. * Case-sensitive comparison
  2954. *
  2955. * @param a left value
  2956. * @param b right value
  2957. * @return -1, 0, or 1
  2958. */
  2959. public static int strcmp(StringValue a, StringValue b) {
  2960. int aLen = a.length();
  2961. int bLen = b.length();
  2962. for (int i = 0; i < aLen && i < bLen; i++) {
  2963. char chA = a.charAt(i);
  2964. char chB = b.charAt(i);
  2965. if (chA == chB) {
  2966. continue;
  2967. }
  2968. if (chA == chB) {
  2969. continue;
  2970. } else if (chA < chB) {
  2971. return -1;
  2972. } else {
  2973. return 1;
  2974. }
  2975. }
  2976. if (aLen == bLen) {
  2977. return 0;
  2978. } else if (aLen < bLen) {
  2979. return -1;
  2980. } else {
  2981. return 1;
  2982. }
  2983. }
  2984. /**
  2985. * Locale-based comparison
  2986. * XXX: i18n
  2987. *
  2988. * @param a left value
  2989. * @param b right value
  2990. * @return -1, 0, or 1
  2991. */
  2992. public static Value strcoll(String a, String b) {
  2993. if (a == null) {
  2994. a = "";
  2995. }
  2996. if (b == null) {
  2997. b = "";
  2998. }
  2999. int cmp = a.compareTo(b);
  3000. if (cmp == 0) {
  3001. return LongValue.ZERO;
  3002. } else if (cmp < 0) {
  3003. return LongValue.MINUS_ONE;
  3004. } else {
  3005. return LongValue.ONE;
  3006. }
  3007. }
  3008. /**
  3009. * Finds the number of initial characters in <i>string</i> that do not match
  3010. * one of the characters in <i>characters</i>
  3011. *
  3012. * @param string the string to search in
  3013. * @param characters the character set
  3014. * @param offset the starting offset
  3015. * @param length the length
  3016. * @return the length of the match or FALSE if
  3017. * the offset or length are invalid
  3018. */
  3019. public static Value strcspn(StringValue string,
  3020. StringValue characters,
  3021. @Optional("0") int offset,
  3022. @Optional("-2147483648") int length) {
  3023. if (characters.length() == 0) {
  3024. characters = StringValue.create((char) 0);
  3025. }
  3026. return strspnImpl(string, characters, offset, length, false);
  3027. }
  3028. /**
  3029. * Removes tags from a string.
  3030. *
  3031. * @param string the string to remove
  3032. * @param allowTags the allowable tags
  3033. */
  3034. public static StringValue strip_tags(StringValue string,
  3035. @Optional Value allowTags) {
  3036. StringValue result = new StringValue();
  3037. HashSet<StringValue> allowedTagMap = null;
  3038. if (!allowTags.isDefault()) {
  3039. allowedTagMap = getAllowedTags(allowTags.toStringValue());
  3040. }
  3041. int len = string.length();
  3042. for (int i = 0; i < len; i++) {
  3043. char ch = string.charAt(i);
  3044. if (i + 1 >= len || ch != '<') {
  3045. result.append(ch);
  3046. continue;
  3047. }
  3048. ch = string.charAt(i + 1);
  3049. if (Character.isWhitespace(ch)) {
  3050. i++;
  3051. result.append('<');
  3052. result.append(ch);
  3053. continue;
  3054. }
  3055. int tagNameStart = i + 1;
  3056. if (ch == '/') {
  3057. tagNameStart++;
  3058. }
  3059. int j = tagNameStart;
  3060. while (j < len
  3061. && (ch = string.charAt(j)) != '>'
  3062. // && ch != '/'
  3063. && !Character.isWhitespace(ch)) {
  3064. j++;
  3065. }
  3066. StringValue tagName = string.substring(tagNameStart, j);
  3067. int tagEnd = 0;
  3068. if (allowedTagMap != null && allowedTagMap.contains(tagName)) {
  3069. result.append(string, i, Math.min(j + 1, len));
  3070. } else {
  3071. while (j < len && (ch = string.charAt(j)) != '<') {
  3072. if (ch == '>') {
  3073. tagEnd = j;
  3074. }
  3075. j++;
  3076. }
  3077. }
  3078. i = (tagEnd != 0) ? tagEnd : j;
  3079. }
  3080. return result;
  3081. }
  3082. private static HashSet<StringValue> getAllowedTags(StringValue str) {
  3083. int len = str.length();
  3084. HashSet<StringValue> set = new HashSet<StringValue>();
  3085. for (int i = 0; i < len; i++) {
  3086. char ch = str.charAt(i);
  3087. switch (ch) {
  3088. case '<':
  3089. int j = i + 1;
  3090. while (j < len
  3091. && (ch = str.charAt(j)) != '>'
  3092. //&& ch != '/'
  3093. && !Character.isWhitespace(ch)) {
  3094. j++;
  3095. }
  3096. if (ch == '>'
  3097. && i + 1 < j
  3098. && j < len) {
  3099. set.add(str.substring(i + 1, j));
  3100. }
  3101. i = j;
  3102. default:
  3103. continue;
  3104. }
  3105. }
  3106. return set;
  3107. }
  3108. /**
  3109. * Strip out the backslashes, recognizing the escape sequences, octal,
  3110. * and hexadecimal representations.
  3111. *
  3112. * @param source the string to clean
  3113. * @see #addcslashes
  3114. */
  3115. public static String stripcslashes(String source) {
  3116. if (source == null) {
  3117. source = "";
  3118. }
  3119. StringBuilder result = new StringBuilder(source.length());
  3120. int length = source.length();
  3121. for (int i = 0; i < length; i++) {
  3122. int ch = source.charAt(i);
  3123. if (ch == '\\') {
  3124. i++;
  3125. if (i == length) {
  3126. ch = '\\';
  3127. } else {
  3128. ch = source.charAt(i);
  3129. switch (ch) {
  3130. case 'a':
  3131. ch = 0x07;
  3132. break;
  3133. case 'b':
  3134. ch = '\b';
  3135. break;
  3136. case 't':
  3137. ch = '\t';
  3138. break;
  3139. case 'n':
  3140. ch = '\n';
  3141. break;
  3142. case 'v':
  3143. ch = 0xb;
  3144. break;
  3145. case 'f':
  3146. ch = '\f';
  3147. break;
  3148. case 'r':
  3149. ch = '\r';
  3150. break;
  3151. case 'x':
  3152. // up to two digits for a hex number
  3153. if (i + 1 == length) {
  3154. break;
  3155. }
  3156. int digitValue = hexToDigit(source.charAt(i + 1));
  3157. if (digitValue < 0) {
  3158. break;
  3159. }
  3160. ch = digitValue;
  3161. i++;
  3162. if (i + 1 == length) {
  3163. break;
  3164. }
  3165. digitValue = hexToDigit(source.charAt(i + 1));
  3166. if (digitValue < 0) {
  3167. break;
  3168. }
  3169. ch = ((ch << 4) | digitValue);
  3170. i++;
  3171. break;
  3172. default:
  3173. // up to three digits from 0 to 7 for an octal number
  3174. digitValue = octToDigit((char) ch);
  3175. if (digitValue < 0) {
  3176. break;
  3177. }
  3178. ch = digitValue;
  3179. if (i + 1 == length) {
  3180. break;
  3181. }
  3182. digitValue = octToDigit(source.charAt(i + 1));
  3183. if (digitValue < 0) {
  3184. break;
  3185. }
  3186. ch = ((ch << 3) | digitValue);
  3187. i++;
  3188. if (i + 1 == length) {
  3189. break;
  3190. }
  3191. digitValue = octToDigit(source.charAt(i + 1));
  3192. if (digitValue < 0) {
  3193. break;
  3194. }
  3195. ch = ((ch << 3) | digitValue);
  3196. i++;
  3197. }
  3198. }
  3199. } // if ch == '/'
  3200. result.append((char) ch);
  3201. }
  3202. return result.toString();
  3203. }
  3204. /**
  3205. * Returns the position of a substring, testing case insensitive.
  3206. *
  3207. * @param haystack the full argument to check
  3208. * @param needleV the substring argument to check
  3209. * @param offsetV optional starting position
  3210. */
  3211. public static Value stripos(Env env, StringValue haystack,
  3212. Value needleV,
  3213. @Optional int offset) {
  3214. StringValue needle;
  3215. int len = haystack.length();
  3216. if (len < offset) {
  3217. env.warning(L.l("offset cannot exceed string length"));
  3218. return BooleanValue.FALSE;
  3219. }
  3220. if (needleV instanceof StringValue) {
  3221. needle = (StringValue) needleV;
  3222. } else {
  3223. needle = StringValue.create((char) needleV.toInt());
  3224. }
  3225. haystack = haystack.toLowerCase();
  3226. needle = needle.toLowerCase();
  3227. int pos = haystack.indexOf(needle, offset);
  3228. if (pos < 0) {
  3229. return BooleanValue.FALSE;
  3230. } else {
  3231. return LongValue.create(pos);
  3232. }
  3233. }
  3234. /**
  3235. * Strips out the backslashes.
  3236. *
  3237. * @param string the string to clean
  3238. */
  3239. public static StringValue stripslashes(StringValue string) {
  3240. StringValue sb = new StringValue();
  3241. int len = string.length();
  3242. for (int i = 0; i < len; i++) {
  3243. char ch = string.charAt(i);
  3244. if (ch == '\\') {
  3245. if (i + 1 < len) {
  3246. char ch2 = string.charAt(i + 1);
  3247. if (ch2 == '0') {
  3248. ch2 = 0x0;
  3249. }
  3250. sb.append(ch2);
  3251. i++;
  3252. }
  3253. } else {
  3254. sb.append(ch);
  3255. }
  3256. }
  3257. return sb;
  3258. }
  3259. /**
  3260. * Finds the first instance of a substring, testing case insensitively
  3261. *
  3262. * @param haystack the string to search in
  3263. * @param needleV the string to search for
  3264. * @return the trailing match or FALSE
  3265. */
  3266. public static Value stristr(StringValue haystack,
  3267. Value needleV) {
  3268. CharSequence needleLower;
  3269. if (needleV instanceof StringValue) {
  3270. needleLower = ((StringValue) needleV).toLowerCase();
  3271. } else {
  3272. char lower = Character.toLowerCase((char) needleV.toLong());
  3273. needleLower = String.valueOf(lower);
  3274. }
  3275. StringValue haystackLower = haystack.toLowerCase();
  3276. int i = haystackLower.indexOf(needleLower);
  3277. if (i >= 0) {
  3278. return haystack.substring(i);
  3279. } else {
  3280. return BooleanValue.FALSE;
  3281. }
  3282. }
  3283. /**
  3284. * Returns the length of a string.
  3285. *
  3286. * @param value the argument value
  3287. */
  3288. public static Value strlen(Value value) {
  3289. return LongValue.create(value.length());
  3290. }
  3291. /**
  3292. * Case-insensitive comparison
  3293. *
  3294. * @param a left value
  3295. * @param b right value
  3296. * @return -1, 0, or 1
  3297. */
  3298. public static int strnatcasecmp(StringValue a, StringValue b) {
  3299. return naturalOrderCompare(a, b, true);
  3300. }
  3301. /**
  3302. * Case-sensitive comparison
  3303. *
  3304. * @param a left value
  3305. * @param b right value
  3306. * @return -1, 0, or 1
  3307. */
  3308. public static int strnatcmp(StringValue a, StringValue b) {
  3309. return naturalOrderCompare(a, b, false);
  3310. }
  3311. /**
  3312. * http://sourcefrog.net/projects/natsort/
  3313. */
  3314. private static int naturalOrderCompare(StringValue a,
  3315. StringValue b,
  3316. boolean ignoreCase) {
  3317. SimpleStringReader aIn = new SimpleStringReader(a);
  3318. SimpleStringReader bIn = new SimpleStringReader(b);
  3319. int aChar = aIn.read();
  3320. int bChar = bIn.read();
  3321. if (aChar == -1 && bChar >= 0) {
  3322. return -1;
  3323. } else if (aChar >= 0 && bChar == -1) {
  3324. return 1;
  3325. }
  3326. while (true) {
  3327. while (Character.isWhitespace(aChar)) {
  3328. aChar = aIn.read();
  3329. }
  3330. while (Character.isWhitespace(bChar)) {
  3331. bChar = bIn.read();
  3332. }
  3333. if (aChar == -1 && bChar == -1) {
  3334. return 0;
  3335. }
  3336. // leading zeros
  3337. // '01' < '2'
  3338. // '0a' > 'a'
  3339. if (aChar == '0' && bChar == '0') {
  3340. while (true) {
  3341. aChar = aIn.read();
  3342. bChar = bIn.read();
  3343. if (aChar == '0' && bChar == '0') {
  3344. continue;
  3345. } else if (aChar == '0') {
  3346. if ('1' <= bChar && bChar <= '9') {
  3347. return -1;
  3348. } else {
  3349. return 1;
  3350. }
  3351. } else if (bChar == 0) {
  3352. if ('1' <= aChar && aChar <= '9') {
  3353. return 1;
  3354. } else {
  3355. return -1;
  3356. }
  3357. } else {
  3358. break;
  3359. }
  3360. }
  3361. } else if ('0' < aChar && aChar <= '9'
  3362. && '0' < bChar && bChar <= '9') {
  3363. int aInteger = aIn.readInt(aChar);
  3364. int bInteger = bIn.readInt(bChar);
  3365. if (aInteger > bInteger) {
  3366. return 1;
  3367. } else if (aInteger < bInteger) {
  3368. return -1;
  3369. } else {
  3370. aChar = aIn.read();
  3371. bChar = bIn.read();
  3372. }
  3373. }
  3374. if (ignoreCase) {
  3375. aChar = Character.toUpperCase(aChar);
  3376. bChar = Character.toUpperCase(bChar);
  3377. }
  3378. if (aChar > bChar) {
  3379. return 1;
  3380. } else if (aChar < bChar) {
  3381. return -1;
  3382. }
  3383. aChar = aIn.read();
  3384. bChar = bIn.read();
  3385. // trailing spaces
  3386. // "abc " > "abc"
  3387. if (aChar >= 0 && bChar == -1) {
  3388. return 1;
  3389. } else if (aChar == -1 && bChar >= 0) {
  3390. return -1;
  3391. }
  3392. }
  3393. }
  3394. /**
  3395. * Case-insensitive comparison
  3396. *
  3397. * @param a left value
  3398. * @param b right value
  3399. * @return -1, 0, or 1
  3400. */
  3401. public static Value strncasecmp(
  3402. Env env, StringValue a, StringValue b, int length) {
  3403. if (length < 0) {
  3404. env.warning(L.l("strncasecmp() length '{0}' must be non-negative",
  3405. length));
  3406. return BooleanValue.FALSE;
  3407. }
  3408. int aLen = a.length();
  3409. int bLen = b.length();
  3410. for (int i = 0; i < length; i++) {
  3411. if (aLen <= i) {
  3412. return LongValue.MINUS_ONE;
  3413. } else if (bLen <= i) {
  3414. return LongValue.ONE;
  3415. }
  3416. char aChar = Character.toUpperCase(a.charAt(i));
  3417. char bChar = Character.toUpperCase(b.charAt(i));
  3418. if (aChar < bChar) {
  3419. return LongValue.MINUS_ONE;
  3420. } else if (bChar < aChar) {
  3421. return LongValue.ONE;
  3422. }
  3423. }
  3424. return LongValue.ZERO;
  3425. }
  3426. /**
  3427. * Case-sensitive comparison
  3428. *
  3429. * @param a left value
  3430. * @param b right value
  3431. * @return -1, 0, or 1
  3432. */
  3433. public static Value strncmp(Env env, StringValue a, StringValue b, int length) {
  3434. if (length < 0) {
  3435. env.warning(L.l("strncmp() length '{0}' must be non-negative",
  3436. length));
  3437. return BooleanValue.FALSE;
  3438. }
  3439. if (length < a.length()) {
  3440. a = a.substring(0, length);
  3441. }
  3442. if (length < b.length()) {
  3443. b = b.substring(0, length);
  3444. }
  3445. return LongValue.create(strcmp(a, b));
  3446. }
  3447. /**
  3448. * Returns a substring of <i>haystack</i> starting from the earliest
  3449. * occurence of any char in <i>charList</i>
  3450. *
  3451. * @param haystack the string to search in
  3452. * @param charList list of chars that would trigger match
  3453. * @return substring, else FALSE
  3454. */
  3455. public static Value strpbrk(StringValue haystack,
  3456. StringValue charList) {
  3457. int len = haystack.length();
  3458. int sublen = charList.length();
  3459. for (int i = 0; i < len; i++) {
  3460. for (int j = 0; j < sublen; j++) {
  3461. if (haystack.charAt(i) == charList.charAt(j)) {
  3462. return haystack.substring(i);
  3463. }
  3464. }
  3465. }
  3466. return BooleanValue.FALSE;
  3467. }
  3468. /**
  3469. * Returns the position of a substring.
  3470. *
  3471. * @param haystack the string to search in
  3472. * @param needleV the string to search for
  3473. */
  3474. public static Value strpos(Env env,
  3475. StringValue haystack,
  3476. Value needleV,
  3477. @Optional int offset) {
  3478. StringValue needle;
  3479. if (offset > haystack.length()) {
  3480. env.warning(L.l("offset cannot exceed string length"));
  3481. return BooleanValue.FALSE;
  3482. }
  3483. if (needleV.isString()) {
  3484. needle = needleV.toStringValue();
  3485. } else {
  3486. needle = StringValue.create((char) needleV.toInt());
  3487. }
  3488. int pos = haystack.indexOf(needle, offset);
  3489. if (pos < 0) {
  3490. return BooleanValue.FALSE;
  3491. } else {
  3492. return LongValue.create(pos);
  3493. }
  3494. }
  3495. /**
  3496. * Finds the last instance of a substring
  3497. *
  3498. * @param haystack the string to search in
  3499. * @param needleV the string to search for
  3500. * @return the trailing match or FALSE
  3501. */
  3502. public static Value strrchr(StringValue haystack,
  3503. Value needleV) {
  3504. CharSequence needle;
  3505. if (needleV instanceof StringValue) {
  3506. needle = (StringValue) needleV;
  3507. } else {
  3508. needle = String.valueOf((char) needleV.toLong());
  3509. }
  3510. int i = haystack.lastIndexOf(needle);
  3511. if (i > 0) {
  3512. return haystack.substring(i);
  3513. } else {
  3514. return BooleanValue.FALSE;
  3515. }
  3516. }
  3517. /**
  3518. * Reverses a string.
  3519. */
  3520. public static Value strrev(StringValue string) {
  3521. StringValue sb = new StringValue();
  3522. for (int i = string.length() - 1; i >= 0; i--) {
  3523. sb.append(string.charAt(i));
  3524. }
  3525. return sb;
  3526. }
  3527. /**
  3528. * Returns the position of a substring, testing case-insensitive.
  3529. *
  3530. * @param haystack the full string to test
  3531. * @param needleV the substring string to test
  3532. * @param offsetV the optional offset to start searching
  3533. */
  3534. public static Value strripos(Env env,
  3535. String haystack,
  3536. Value needleV,
  3537. @Optional Value offsetV) {
  3538. if (haystack == null) {
  3539. haystack = "";
  3540. }
  3541. String needle;
  3542. if (needleV instanceof StringValue) {
  3543. needle = needleV.toString();
  3544. } else {
  3545. needle = String.valueOf((char) needleV.toInt());
  3546. }
  3547. int offset;
  3548. if (offsetV instanceof DefaultValue) {
  3549. offset = haystack.length();
  3550. } else {
  3551. offset = offsetV.toInt();
  3552. if (haystack.length() < offset) {
  3553. env.warning(L.l("offset cannot exceed string length"));
  3554. return BooleanValue.FALSE;
  3555. }
  3556. }
  3557. haystack = haystack.toLowerCase();
  3558. needle = needle.toLowerCase();
  3559. int pos = haystack.lastIndexOf(needle, offset);
  3560. if (pos < 0) {
  3561. return BooleanValue.FALSE;
  3562. } else {
  3563. return LongValue.create(pos);
  3564. }
  3565. }
  3566. /**
  3567. * Returns the position of a substring.
  3568. *
  3569. * @param haystack the string to search in
  3570. * @param needleV the string to search for
  3571. */
  3572. public static Value strrpos(Env env,
  3573. StringValue haystack,
  3574. Value needleV,
  3575. @Optional Value offsetV) {
  3576. StringValue needle;
  3577. if (needleV instanceof StringValue) {
  3578. needle = needleV.toStringValue();
  3579. } else {
  3580. needle = StringValue.create((char) needleV.toInt());
  3581. }
  3582. int offset = haystack.length() - offsetV.toInt();
  3583. if (offset < 0) {
  3584. env.warning(L.l("offset cannot exceed string length"));
  3585. return BooleanValue.FALSE;
  3586. }
  3587. int pos = haystack.lastIndexOf(needle, offset);
  3588. if (pos < 0) {
  3589. return BooleanValue.FALSE;
  3590. } else {
  3591. return LongValue.create(pos);
  3592. }
  3593. }
  3594. /**
  3595. * Finds the number of initial characters in <i>string</i> that match one of
  3596. * the characters in <i>characters</i>
  3597. *
  3598. * @param string the string to search in
  3599. * @param characters the character set
  3600. * @param offset the starting offset
  3601. * @param length the length
  3602. * @return the length of the match or FALSE
  3603. * if the offset or length are invalid
  3604. */
  3605. public static Value strspn(StringValue string,
  3606. StringValue characters,
  3607. @Optional int offset,
  3608. @Optional("-2147483648") int length) {
  3609. return strspnImpl(string, characters, offset, length, true);
  3610. }
  3611. private static Value strspnImpl(StringValue string,
  3612. StringValue characters,
  3613. int offset,
  3614. int length,
  3615. boolean isMatch) {
  3616. int strlen = string.length();
  3617. // see also strcspn which uses the same procedure for determining
  3618. // effective offset and length
  3619. if (offset < 0) {
  3620. offset += strlen;
  3621. if (offset < 0) {
  3622. offset = 0;
  3623. }
  3624. }
  3625. if (offset > strlen) {
  3626. return BooleanValue.FALSE;
  3627. }
  3628. if (length == -2147483648) {
  3629. length = strlen;
  3630. } else if (length < 0) {
  3631. length += (strlen - offset);
  3632. if (length < 0) {
  3633. length = 0;
  3634. }
  3635. }
  3636. int end = offset + length;
  3637. if (strlen < end) {
  3638. end = strlen;
  3639. }
  3640. int count = 0;
  3641. for (; offset < end; offset++) {
  3642. char ch = string.charAt(offset);
  3643. boolean isPresent = characters.indexOf(ch) > -1;
  3644. if (isPresent == isMatch) {
  3645. count++;
  3646. } else {
  3647. return LongValue.create(count);
  3648. }
  3649. }
  3650. return LongValue.create(count);
  3651. }
  3652. /**
  3653. * Finds the first instance of a needle in haystack and returns
  3654. * the portion of haystack from the beginning of
  3655. * needle to the end of haystack.
  3656. *
  3657. * @param env the calling environment
  3658. * @param haystackV the string to search in
  3659. * @param needleV the string to search for, or the
  3660. * original value of a character
  3661. * @return the trailing match or FALSE if needle is not found
  3662. */
  3663. public static Value strstr(Env env,
  3664. StringValue haystackV,
  3665. Value needleV) {
  3666. if (haystackV == null) {
  3667. haystackV = StringValue.EMPTY;
  3668. }
  3669. String needle;
  3670. if (needleV instanceof StringValue) {
  3671. needle = needleV.toString();
  3672. } else {
  3673. needle = String.valueOf((char) needleV.toLong());
  3674. }
  3675. if (needle.length() == 0) {
  3676. env.warning("empty needle");
  3677. return BooleanValue.FALSE;
  3678. }
  3679. int i = haystackV.indexOf(needle);
  3680. if (i >= 0) {
  3681. return haystackV.substring(i);
  3682. } else {
  3683. return BooleanValue.FALSE;
  3684. }
  3685. }
  3686. /**
  3687. * Split a string into tokens using any character
  3688. * in another string as a delimiter.
  3689. * <p/>
  3690. * The first call establishes the string to
  3691. * search and the characters to use as tokens,
  3692. * the first token is returned:
  3693. * <pre>
  3694. * strtok("hello, world", ", ")
  3695. * => "hello"
  3696. * </pre>
  3697. * <p/>
  3698. * Subsequent calls pass only the token
  3699. * characters, the next token is returned:
  3700. * <pre>
  3701. * strtok("hello, world", ", ")
  3702. * => "hello"
  3703. * strtok(", ")
  3704. * => "world"
  3705. * </pre>
  3706. * <p/>
  3707. * False is returned if there are no more tokens:
  3708. * <pre>
  3709. * strtok("hello, world", ", ")
  3710. * => "hello"
  3711. * strtok(", ")
  3712. * => "world"
  3713. * strtok(", ")
  3714. * => false
  3715. * </pre>
  3716. * <p/>
  3717. * Calls that pass two arguments reset the search string:
  3718. * <pre>
  3719. * strtok("hello, world", ", ")
  3720. * => "hello"
  3721. * strtok("goodbye, world", ", ")
  3722. * => "goodbye"
  3723. * strtok("world")
  3724. * => false
  3725. * strtok(", ")
  3726. * => false
  3727. * </pre>
  3728. */
  3729. public static Value strtok(Env env,
  3730. StringValue string1,
  3731. @Optional Value string2) {
  3732. StringValue string;
  3733. StringValue characters;
  3734. int offset;
  3735. //StringValue savedToken = null;
  3736. if (string2.isNull()) {
  3737. StringValue savedString = (StringValue) env.getSpecialValue("clevercloud.strtok_string");
  3738. Integer savedOffset = (Integer) env.getSpecialValue("clevercloud.strtok_offset");
  3739. //savedToken = (StringValue) env.getSpecialValue("clevercloud.strtok_token");
  3740. string = savedString == null ? StringValue.EMPTY : savedString;
  3741. offset = savedOffset == null ? 0 : savedOffset;
  3742. //savedToken = savedToken == null ? env.getEmptyString() : savedToken;
  3743. characters = string1;
  3744. } else {
  3745. string = string1;
  3746. offset = 0;
  3747. characters = string2.toStringValue();
  3748. env.setSpecialValue("clevercloud.strtok_string", string);
  3749. //env.setSpecialValue("clevercloud.strtok_token", string2);
  3750. }
  3751. int strlen = string.length();
  3752. // skip any at beginning
  3753. for (; offset < strlen; offset++) {
  3754. char ch = string.charAt(offset);
  3755. if (characters.indexOf(ch) < 0) {
  3756. break;
  3757. }
  3758. }
  3759. Value result;
  3760. if (offset == strlen) {
  3761. result = BooleanValue.FALSE;
  3762. } else {
  3763. //if (string2.isNull() && !(string1.eq(savedToken))) {
  3764. // offset = offset + savedToken.length();
  3765. //}
  3766. int start = offset;
  3767. int end = start;
  3768. // find end
  3769. for (; end < strlen; end++) {
  3770. char ch = string.charAt(end);
  3771. if (characters.indexOf(ch) > -1) {
  3772. break;
  3773. }
  3774. }
  3775. for (offset = end; offset < strlen; offset++) {
  3776. char ch = string.charAt(offset);
  3777. if (characters.indexOf(ch) < 0) {
  3778. break;
  3779. }
  3780. }
  3781. result = string.substring(start, end);
  3782. }
  3783. env.setSpecialValue("clevercloud.strtok_offset", offset);
  3784. return result;
  3785. }
  3786. /**
  3787. * Converts to lower case.
  3788. *
  3789. * @param string the input string
  3790. */
  3791. public static StringValue strtolower(StringValue string) {
  3792. return string.toLowerCase();
  3793. }
  3794. /**
  3795. * Converts to upper case.
  3796. *
  3797. * @param string the input string
  3798. */
  3799. public static StringValue strtoupper(StringValue string) {
  3800. return string.toUpperCase();
  3801. }
  3802. /**
  3803. * Translates characters in a string to target values.
  3804. *
  3805. * @param string the source string
  3806. * @param fromV the from characters
  3807. * @param to the to character map
  3808. */
  3809. public static StringValue strtr(Env env,
  3810. StringValue string,
  3811. Value fromV,
  3812. @Optional StringValue to) {
  3813. if (fromV instanceof ArrayValue) {
  3814. return strtrArray(string, (ArrayValue) fromV);
  3815. }
  3816. StringValue from = fromV.toStringValue();
  3817. int len = from.length();
  3818. if (to.length() < len) {
  3819. len = to.length();
  3820. }
  3821. char[] map = new char[256];
  3822. for (int i = len - 1; i >= 0; i--) {
  3823. map[from.charAt(i)] = to.charAt(i);
  3824. }
  3825. StringValue sb = new StringValue();
  3826. len = string.length();
  3827. for (int i = 0; i < len; i++) {
  3828. char ch = string.charAt(i);
  3829. if (map[ch] != 0) {
  3830. sb.append(map[ch]);
  3831. } else {
  3832. sb.append(ch);
  3833. }
  3834. }
  3835. return sb;
  3836. }
  3837. /**
  3838. * Translates characters in a string to target values.
  3839. *
  3840. * @param string the source string
  3841. * @param map the character map
  3842. */
  3843. private static StringValue strtrArray(StringValue string, ArrayValue map) {
  3844. int size = map.getSize();
  3845. StringValue[] fromList = new StringValue[size];
  3846. StringValue[] toList = new StringValue[size];
  3847. Map.Entry<Value, Value>[] entryArray = new Map.Entry[size];
  3848. int i = 0;
  3849. for (Map.Entry<Value, Value> entry : map.entrySet()) {
  3850. entryArray[i++] = entry;
  3851. }
  3852. // sort entries in descending fashion
  3853. Arrays.sort(entryArray, new StrtrComparator<Map.Entry<Value, Value>>());
  3854. boolean[] charSet = new boolean[256];
  3855. for (i = 0; i < size; i++) {
  3856. fromList[i] = entryArray[i].getKey().toStringValue();
  3857. toList[i] = entryArray[i].getValue().toStringValue();
  3858. charSet[fromList[i].charAt(0)] = true;
  3859. }
  3860. StringValue result = new StringValue();
  3861. int len = string.length();
  3862. int head = 0;
  3863. top:
  3864. while (head < len) {
  3865. char ch = string.charAt(head);
  3866. if (charSet.length <= ch || charSet[ch]) {
  3867. fromLoop:
  3868. for (i = 0; i < fromList.length; i++) {
  3869. StringValue from = fromList[i];
  3870. int fromLen = from.length();
  3871. if (head + fromLen > len) {
  3872. continue;
  3873. }
  3874. if (ch != from.charAt(0)) {
  3875. continue;
  3876. }
  3877. for (int j = 0; j < fromLen; j++) {
  3878. if (string.charAt(head + j) != from.charAt(j)) {
  3879. continue fromLoop;
  3880. }
  3881. }
  3882. result = result.append(toList[i]);
  3883. head = head + fromLen;
  3884. continue top;
  3885. }
  3886. }
  3887. result.append(ch);
  3888. head++;
  3889. }
  3890. return result;
  3891. }
  3892. /*
  3893. * Comparator for sorting in descending fashion based on length.
  3894. */
  3895. static class StrtrComparator<T extends Map.Entry<Value, Value>>
  3896. implements Comparator<T> {
  3897. @Override
  3898. public int compare(T a, T b) {
  3899. int lenA = a.getKey().length();
  3900. int lenB = b.getKey().length();
  3901. if (lenA < lenB) {
  3902. return 1;
  3903. } else if (lenA == lenB) {
  3904. return 0;
  3905. } else {
  3906. return -1;
  3907. }
  3908. }
  3909. }
  3910. /**
  3911. * Returns a substring
  3912. *
  3913. * @param env the calling environment
  3914. * @param string the string
  3915. * @param start the start offset
  3916. * @param lenV the optional length
  3917. */
  3918. public static Value substr(Env env,
  3919. StringValue string,
  3920. int start,
  3921. @Optional Value lenV) {
  3922. int len = lenV.toInt();
  3923. int strLen = string.length();
  3924. if (start < 0) {
  3925. start = strLen + start;
  3926. }
  3927. if (start < 0 || start >= strLen) {
  3928. return BooleanValue.FALSE;
  3929. }
  3930. if (lenV.isDefault()) {
  3931. return string.substring(start);
  3932. } else if (len == 0) {
  3933. return StringValue.EMPTY;
  3934. } else {
  3935. int end;
  3936. if (len < 0) {
  3937. end = strLen + len;
  3938. } else {
  3939. end = (strLen < len) ? strLen : start + len;
  3940. }
  3941. if (end <= start) {
  3942. return BooleanValue.FALSE;
  3943. } else if (strLen <= end) {
  3944. return string.substring(start);
  3945. } else {
  3946. return string.substring(start, end);
  3947. }
  3948. }
  3949. }
  3950. public static Value substr_compare(Env env,
  3951. StringValue mainStr,
  3952. StringValue str,
  3953. int offset,
  3954. @Optional Value lenV,
  3955. @Optional boolean isCaseInsensitive) {
  3956. int strLen = mainStr.length();
  3957. int len = lenV.toInt();
  3958. if (!lenV.isDefault() && len == 0) {
  3959. return BooleanValue.FALSE;
  3960. }
  3961. if (strLen < offset) {
  3962. env.warning(L.l("offset can not be greater than length of string"));
  3963. return BooleanValue.FALSE;
  3964. }
  3965. if (len > strLen
  3966. || len + offset > strLen) {
  3967. return BooleanValue.FALSE;
  3968. }
  3969. mainStr = substr(env, mainStr, offset, lenV).toStringValue();
  3970. str = substr(env, str, 0, lenV).toStringValue();
  3971. if (isCaseInsensitive) {
  3972. return LongValue.create(strcasecmp(mainStr, str));
  3973. } else {
  3974. return LongValue.create(strcmp(mainStr, str));
  3975. }
  3976. }
  3977. public static Value substr_count(Env env,
  3978. StringValue haystackV,
  3979. StringValue needleV,
  3980. @Optional int offset,
  3981. @Optional("-1") int length) {
  3982. String haystack = haystackV.toString();
  3983. String needle = needleV.toString();
  3984. if (needle.length() == 0) {
  3985. env.warning(L.l("empty substr"));
  3986. return BooleanValue.FALSE;
  3987. }
  3988. int haystackLength = haystack.length();
  3989. if (offset < 0 || offset > haystackLength) {
  3990. env.warning(L.l("offset cannot exceed string length", offset));
  3991. return BooleanValue.FALSE;
  3992. }
  3993. if (length >= 0) {
  3994. int newLength = offset + length;
  3995. if (newLength < 0 || newLength > haystackLength) {
  3996. env.warning(L.l("length cannot exceed string length", length));
  3997. return BooleanValue.FALSE;
  3998. }
  3999. haystackLength = newLength;
  4000. }
  4001. int needleLength = needle.length();
  4002. int count = 0;
  4003. int end = haystackLength - needleLength + 1;
  4004. int i = offset;
  4005. while (i < end) {
  4006. if (haystack.startsWith(needle, i)) {
  4007. count++;
  4008. i += needleLength;
  4009. } else {
  4010. i++;
  4011. }
  4012. }
  4013. return LongValue.create(count);
  4014. }
  4015. /**
  4016. * Replaces a substring with a replacement
  4017. *
  4018. * @param subjectV a string to modify, or an array of strings to modify
  4019. * @param replacement the replacement string
  4020. * @param startV the start offset
  4021. * @param lengthV the optional length
  4022. */
  4023. public static Value substr_replace(Value subjectV,
  4024. StringValue replacement,
  4025. Value startV,
  4026. @Optional Value lengthV) {
  4027. int start = 0;
  4028. int length = Integer.MAX_VALUE / 2;
  4029. if (!(lengthV.isNull() || lengthV.isArray())) {
  4030. length = lengthV.toInt();
  4031. }
  4032. if (!(startV.isNull() || startV.isArray())) {
  4033. start = startV.toInt();
  4034. }
  4035. Iterator<Value> startIterator =
  4036. startV.isArray()
  4037. ? ((ArrayValue) startV).values().iterator()
  4038. : null;
  4039. Iterator<Value> lengthIterator =
  4040. lengthV.isArray()
  4041. ? ((ArrayValue) lengthV).values().iterator()
  4042. : null;
  4043. if (subjectV.isArray()) {
  4044. ArrayValue resultArray = new ArrayValueImpl();
  4045. ArrayValue subjectArray = (ArrayValue) subjectV;
  4046. for (Value value : subjectArray.values()) {
  4047. if (lengthIterator != null && lengthIterator.hasNext()) {
  4048. length = lengthIterator.next().toInt();
  4049. }
  4050. if (startIterator != null && startIterator.hasNext()) {
  4051. start = startIterator.next().toInt();
  4052. }
  4053. Value result = substrReplaceImpl(
  4054. value.toStringValue(), replacement, start, length);
  4055. resultArray.append(result);
  4056. }
  4057. return resultArray;
  4058. } else {
  4059. if (lengthIterator != null && lengthIterator.hasNext()) {
  4060. length = lengthIterator.next().toInt();
  4061. }
  4062. if (startIterator != null && startIterator.hasNext()) {
  4063. start = startIterator.next().toInt();
  4064. }
  4065. return substrReplaceImpl(
  4066. subjectV.toStringValue(), replacement, start, length);
  4067. }
  4068. }
  4069. private static Value substrReplaceImpl(StringValue string,
  4070. StringValue replacement,
  4071. int start,
  4072. int len) {
  4073. int strLen = string.length();
  4074. if (start > strLen) {
  4075. start = strLen;
  4076. } else if (start < 0) {
  4077. start = Math.max(strLen + start, 0);
  4078. }
  4079. int end;
  4080. if (len < 0) {
  4081. end = Math.max(strLen + len, start);
  4082. } else {
  4083. end = (strLen < len) ? strLen : (start + len);
  4084. }
  4085. StringValue result = new StringValue();
  4086. result = result.append(string.substring(0, start));
  4087. result = result.append(replacement);
  4088. result = result.append(string.substring(end));
  4089. return result;
  4090. }
  4091. /**
  4092. * Removes leading and trailing whitespace.
  4093. *
  4094. * @param string the string to be trimmed
  4095. * @param characters optional set of characters to trim
  4096. * @return the trimmed string
  4097. */
  4098. public static Value trim(Env env,
  4099. StringValue string,
  4100. @Optional String characters) {
  4101. boolean[] trim;
  4102. if (characters == null || characters.equals("")) {
  4103. trim = TRIM_WHITESPACE;
  4104. } else {
  4105. trim = parseCharsetBitmap(env, characters.toString());
  4106. }
  4107. int len = string.length();
  4108. int head = 0;
  4109. for (; head < len; head++) {
  4110. char ch = string.charAt(head);
  4111. if (ch >= 256 || !trim[ch]) {
  4112. break;
  4113. }
  4114. }
  4115. int tail = len - 1;
  4116. for (; tail >= 0; tail--) {
  4117. char ch = string.charAt(tail);
  4118. if (ch >= 256 || !trim[ch]) {
  4119. break;
  4120. }
  4121. }
  4122. if (tail < head) {
  4123. return StringValue.EMPTY;
  4124. } else {
  4125. return (StringValue) string.subSequence(head, tail + 1);
  4126. }
  4127. }
  4128. /**
  4129. * Uppercases the first character
  4130. *
  4131. * @param string the input string
  4132. */
  4133. public static StringValue ucfirst(Env env, StringValue string) {
  4134. if (string == null) {
  4135. return StringValue.EMPTY;
  4136. } else if (string.length() == 0) {
  4137. return string;
  4138. }
  4139. StringValue sb = new StringValue();
  4140. sb = sb.append(Character.toUpperCase(string.charAt(0)));
  4141. sb = sb.append(string, 1, string.length());
  4142. return sb;
  4143. }
  4144. /**
  4145. * Uppercases the first character of each word
  4146. *
  4147. * @param string the input string
  4148. */
  4149. public static String ucwords(String string) {
  4150. if (string == null) {
  4151. string = "";
  4152. }
  4153. int strLen = string.length();
  4154. boolean isStart = true;
  4155. StringBuilder sb = new StringBuilder();
  4156. for (int i = 0; i < strLen; i++) {
  4157. char ch = string.charAt(i);
  4158. switch (ch) {
  4159. case ' ':
  4160. case '\t':
  4161. case '\r':
  4162. case '\n':
  4163. isStart = true;
  4164. sb.append(ch);
  4165. break;
  4166. default:
  4167. if (isStart) {
  4168. sb.append(Character.toUpperCase(ch));
  4169. } else {
  4170. sb.append(ch);
  4171. }
  4172. isStart = false;
  4173. break;
  4174. }
  4175. }
  4176. return sb.toString();
  4177. }
  4178. /**
  4179. * Formatted strings with array arguments
  4180. *
  4181. * @param format the format string
  4182. * @param array the arguments to apply to the format string
  4183. */
  4184. public static int vprintf(Env env,
  4185. StringValue format,
  4186. @NotNull ArrayValue array) {
  4187. Value[] args;
  4188. if (array != null) {
  4189. args = new Value[array.getSize()];
  4190. int i = 0;
  4191. for (Value value : array.values()) {
  4192. args[i++] = value;
  4193. }
  4194. } else {
  4195. args = new Value[0];
  4196. }
  4197. return printf(env, format, args);
  4198. }
  4199. /**
  4200. * Formatted strings with array arguments
  4201. *
  4202. * @param format the format string
  4203. * @param array the arguments to apply to the format string
  4204. */
  4205. public static Value vsprintf(Env env,
  4206. StringValue format,
  4207. @NotNull ArrayValue array) {
  4208. Value[] args;
  4209. if (array != null) {
  4210. args = new Value[array.getSize()];
  4211. int i = 0;
  4212. for (Value value : array.values()) {
  4213. args[i++] = value;
  4214. }
  4215. } else {
  4216. args = new Value[0];
  4217. }
  4218. return sprintf(env, format, args);
  4219. }
  4220. /**
  4221. * Wraps a string to the given number of characters.
  4222. *
  4223. * @param string the input string
  4224. * @param width the width
  4225. * @param breakString the break string
  4226. * @param cut if true, break on exact match
  4227. */
  4228. public static Value wordwrap(Env env,
  4229. @Expect(type = Expect.Type.STRING) Value value,
  4230. @Optional @Expect(type = Expect.Type.NUMERIC) Value widthV,
  4231. @Optional @Expect(type = Expect.Type.STRING) Value breakV,
  4232. @Optional @Expect(type = Expect.Type.BOOLEAN) Value cutV) {
  4233. if (value instanceof UnexpectedValue) {
  4234. env.warning(L.l("word must be a string, but {0} given",
  4235. value.getType()));
  4236. return NullValue.NULL;
  4237. }
  4238. if (widthV instanceof UnexpectedValue) {
  4239. env.warning(L.l("width must be numeric, but {0} given",
  4240. widthV.getType()));
  4241. return NullValue.NULL;
  4242. }
  4243. int width = 0;
  4244. if (widthV.isDefault()) {
  4245. width = 75;
  4246. } else {
  4247. width = widthV.toInt();
  4248. }
  4249. String string = value.toString();
  4250. if (cutV instanceof UnexpectedValue) {
  4251. env.warning(L.l("cut must be a boolean, but {0} given",
  4252. cutV.getType()));
  4253. return NullValue.NULL;
  4254. }
  4255. boolean isCut = cutV.toBoolean();
  4256. if (isCut && width == 0 && string.length() > 0) {
  4257. env.warning(L.l("cannot cut string to width 0"));
  4258. return BooleanValue.FALSE;
  4259. }
  4260. int len = string != null ? string.length() : 0;
  4261. if (breakV instanceof UnexpectedValue) {
  4262. env.warning(L.l("break string must be a string, but {0} given",
  4263. breakV.getType()));
  4264. return NullValue.NULL;
  4265. }
  4266. String breakString = "\n";
  4267. if (!breakV.isDefault()) {
  4268. breakString = breakV.toString();
  4269. }
  4270. if (breakString == null || breakString.length() == 0) {
  4271. env.warning(L.l("break string cannot be empty"));
  4272. return BooleanValue.FALSE;
  4273. }
  4274. int breakLen = breakString.length();
  4275. int breakChar;
  4276. if (breakLen == 0) {
  4277. breakChar = -1;
  4278. } else {
  4279. breakChar = breakString.charAt(0);
  4280. }
  4281. int head = 0;
  4282. int lastSpace = 0;
  4283. StringValue sb = new StringValue();
  4284. for (int i = 0; i < len; i++) {
  4285. char ch = string.charAt(i);
  4286. if (ch == breakChar && string.regionMatches(
  4287. i, breakString, 0, breakLen)) {
  4288. sb.append(string, head, i + breakLen);
  4289. head = i + breakLen;
  4290. } else if (width <= i - head) {
  4291. if (ch == ' ') {
  4292. sb.append(string, head, i);
  4293. sb.append(breakString);
  4294. head = i + 1;
  4295. } else if (head < lastSpace) {
  4296. sb.append(string, head, lastSpace);
  4297. sb.append(breakString);
  4298. head = lastSpace + 1;
  4299. } else if (isCut) {
  4300. sb.append(string, head, i);
  4301. sb.append(breakString);
  4302. head = i;
  4303. }
  4304. } else if (ch == ' ') {
  4305. lastSpace = i;
  4306. }
  4307. }
  4308. if (head < len) {
  4309. sb.append(string, head, len);
  4310. }
  4311. return sb;
  4312. }
  4313. /**
  4314. * Returns true if the character is a whitespace character.
  4315. */
  4316. protected static boolean isWhitespace(char ch) {
  4317. return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
  4318. }
  4319. /**
  4320. * Returns the uppercase equivalent of the caharacter
  4321. */
  4322. protected static char toUpperCase(char ch) {
  4323. if (ch >= 'a' && ch <= 'z') {
  4324. return (char) ('A' + (ch - 'a'));
  4325. } else {
  4326. return ch;
  4327. }
  4328. }
  4329. /**
  4330. * Converts an integer digit to a uuencoded char.
  4331. */
  4332. protected static char toUUChar(int d) {
  4333. if (d == 0) {
  4334. return (char) 0x60;
  4335. } else {
  4336. return (char) (0x20 + (d & 0x3f));
  4337. }
  4338. }
  4339. protected static char toHexChar(int d) {
  4340. d &= 0xf;
  4341. if (d < 10) {
  4342. return (char) (d + '0');
  4343. } else {
  4344. return (char) (d - 10 + 'a');
  4345. }
  4346. }
  4347. protected static char toUpperHexChar(int d) {
  4348. d &= 0xf;
  4349. if (d < 10) {
  4350. return (char) (d + '0');
  4351. } else {
  4352. return (char) (d - 10 + 'A');
  4353. }
  4354. }
  4355. protected static int hexToDigit(char ch) {
  4356. if ('0' <= ch && ch <= '9') {
  4357. return ch - '0';
  4358. } else if ('a' <= ch && ch <= 'f') {
  4359. return ch - 'a' + 10;
  4360. } else if ('A' <= ch && ch <= 'F') {
  4361. return ch - 'A' + 10;
  4362. } else {
  4363. return -1;
  4364. }
  4365. }
  4366. protected static int octToDigit(char ch) {
  4367. if ('0' <= ch && ch <= '7') {
  4368. return ch - '0';
  4369. } else {
  4370. return -1;
  4371. }
  4372. }
  4373. abstract static class PrintfSegment {
  4374. abstract public boolean apply(Env env, StringValue sb, Value[] args);
  4375. static boolean hasIndex(String format) {
  4376. return format.indexOf('$') >= 0;
  4377. }
  4378. static int getIndex(String format) {
  4379. int value = 0;
  4380. for (int i = 0; i < format.length(); i++) {
  4381. char ch;
  4382. if ('0' <= (ch = format.charAt(i)) && ch <= '9') {
  4383. value = 10 * value + ch - '0';
  4384. } else {
  4385. break;
  4386. }
  4387. }
  4388. return value - 1;
  4389. }
  4390. static String getIndexFormat(String format) {
  4391. int p = format.indexOf('$');
  4392. return '%' + format.substring(p + 1);
  4393. }
  4394. }
  4395. static class TextPrintfSegment extends PrintfSegment {
  4396. private final char[] _text;
  4397. TextPrintfSegment(StringBuilder text) {
  4398. _text = new char[text.length()];
  4399. text.getChars(0, _text.length, _text, 0);
  4400. }
  4401. @Override
  4402. public boolean apply(Env env, StringValue sb, Value[] args) {
  4403. sb.append(_text, 0, _text.length);
  4404. return true;
  4405. }
  4406. }
  4407. static class LongPrintfSegment extends PrintfSegment {
  4408. private final String _format;
  4409. private final int _index;
  4410. private final BiancaLocale _locale;
  4411. private LongPrintfSegment(String format, int index, BiancaLocale locale) {
  4412. _format = format;
  4413. _index = index;
  4414. _locale = locale;
  4415. }
  4416. static PrintfSegment create(Env env, String format, int index) {
  4417. if (hasIndex(format)) {
  4418. index = getIndex(format);
  4419. format = getIndexFormat(format);
  4420. } else {
  4421. format = '%' + format;
  4422. //index = index;
  4423. }
  4424. // php/115b
  4425. // strip out illegal precision specifier from phpBB vote function
  4426. if (format.length() > 1 && format.charAt(1) == '.') {
  4427. int i;
  4428. for (i = 2; i < format.length(); i++) {
  4429. char ch = format.charAt(i);
  4430. if (!('0' <= ch && ch <= '9')) {
  4431. break;
  4432. }
  4433. }
  4434. format = '%' + format.substring(i);
  4435. }
  4436. if (format.charAt(format.length() - 1) == 'x'
  4437. || format.charAt(format.length() - 1) == 'X') {
  4438. HexPrintfSegment hex = HexPrintfSegment.create(format, index);
  4439. if (hex != null) {
  4440. return hex;
  4441. }
  4442. }
  4443. if (format.charAt(format.length() - 1) == 'b'
  4444. || format.charAt(format.length() - 1) == 'B') {
  4445. BinaryPrintfSegment bin = BinaryPrintfSegment.create(format, index);
  4446. if (bin != null) {
  4447. return bin;
  4448. }
  4449. }
  4450. if (format.charAt(format.length() - 1) == 'u') {
  4451. UnsignedPrintfSegment unsign = UnsignedPrintfSegment.create(format, index);
  4452. if (unsign != null) {
  4453. return unsign;
  4454. }
  4455. }
  4456. return new LongPrintfSegment(format, index,
  4457. env.getLocaleInfo().getNumeric());
  4458. }
  4459. @Override
  4460. public boolean apply(Env env, StringValue sb, Value[] args) {
  4461. long value;
  4462. if (_index < args.length) {
  4463. value = args[_index].toLong();
  4464. } else {
  4465. env.warning(L.l("printf(): not enough arguments to match format."));
  4466. return false;
  4467. }
  4468. sb.append(String.format(_locale.getLocale(), _format, value));
  4469. return true;
  4470. }
  4471. }
  4472. static class HexPrintfSegment extends PrintfSegment {
  4473. private final int _index;
  4474. private final int _min;
  4475. private final char _pad;
  4476. private boolean _isUpper;
  4477. HexPrintfSegment(int index, int min, int pad, boolean isUpper) {
  4478. _index = index;
  4479. _min = min;
  4480. if (pad >= 0) {
  4481. _pad = (char) pad;
  4482. } else {
  4483. _pad = ' ';
  4484. }
  4485. _isUpper = isUpper;
  4486. }
  4487. static HexPrintfSegment create(String format, int index) {
  4488. int length = format.length();
  4489. int offset = 1;
  4490. boolean isUpper = format.charAt(length - 1) == 'X';
  4491. char pad = ' ';
  4492. if (format.charAt(offset) == ' ') {
  4493. pad = ' ';
  4494. offset++;
  4495. } else if (format.charAt(offset) == '0') {
  4496. pad = '0';
  4497. offset++;
  4498. }
  4499. int min = 0;
  4500. for (; offset < length - 1; offset++) {
  4501. char ch = format.charAt(offset);
  4502. if ('0' <= ch && ch <= '9') {
  4503. min = 10 * min + ch - '0';
  4504. } else {
  4505. return null;
  4506. }
  4507. }
  4508. return new HexPrintfSegment(index, min, pad, isUpper);
  4509. }
  4510. @Override
  4511. public boolean apply(Env env, StringValue sb, Value[] args) {
  4512. long value;
  4513. if (_index >= 0 && _index < args.length) {
  4514. value = args[_index].toLong();
  4515. } else {
  4516. env.warning(L.l("printf(): not enough arguments to match format."));
  4517. return false;
  4518. }
  4519. int digits = 0;
  4520. long shift = value;
  4521. for (int i = 0; i < 16; i++) {
  4522. if (shift != 0) {
  4523. digits = i;
  4524. }
  4525. shift = shift >>> 4;
  4526. }
  4527. for (int i = digits + 1; i < _min; i++) {
  4528. sb.append(_pad);
  4529. }
  4530. for (; digits >= 0; digits--) {
  4531. int digit = (int) (value >>> (4 * digits)) & 0xf;
  4532. if (digit <= 9) {
  4533. sb.append((char) ('0' + digit));
  4534. } else if (_isUpper) {
  4535. sb.append((char) ('A' + digit - 10));
  4536. } else {
  4537. sb.append((char) ('a' + digit - 10));
  4538. }
  4539. }
  4540. return true;
  4541. }
  4542. }
  4543. static class UnsignedPrintfSegment extends PrintfSegment {
  4544. private final int _index;
  4545. private final int _min;
  4546. private final char _pad;
  4547. UnsignedPrintfSegment(int index, int min, int pad) {
  4548. _index = index;
  4549. _min = min;
  4550. if (pad >= 0) {
  4551. _pad = (char) pad;
  4552. } else {
  4553. _pad = ' ';
  4554. }
  4555. }
  4556. static UnsignedPrintfSegment create(String format, int index) {
  4557. int length = format.length();
  4558. int offset = 1;
  4559. if (format.charAt(offset) == '+') {
  4560. offset++;
  4561. }
  4562. char pad = ' ';
  4563. if (format.charAt(offset) == ' ') {
  4564. pad = ' ';
  4565. offset++;
  4566. } else if (format.charAt(offset) == '0') {
  4567. pad = '0';
  4568. offset++;
  4569. }
  4570. int min = 0;
  4571. for (; offset < length - 1; offset++) {
  4572. char ch = format.charAt(offset);
  4573. if ('0' <= ch && ch <= '9') {
  4574. min = 10 * min + ch - '0';
  4575. } else {
  4576. return null;
  4577. }
  4578. }
  4579. return new UnsignedPrintfSegment(index, min, pad);
  4580. }
  4581. @Override
  4582. public boolean apply(Env env, StringValue sb, Value[] args) {
  4583. long value;
  4584. if (_index >= 0 && _index < args.length) {
  4585. value = args[_index].toLong();
  4586. } else {
  4587. env.warning(L.l("printf(): not enough arguments to match format."));
  4588. return false;
  4589. }
  4590. char[] buf = new char[32];
  4591. int digits = buf.length;
  4592. if (value == 0) {
  4593. buf[--digits] = '0';
  4594. } else if (value > 0) {
  4595. while (value != 0) {
  4596. int digit = (int) (value % 10);
  4597. buf[--digits] = (char) ('0' + digit);
  4598. value = value / 10;
  4599. }
  4600. } else {
  4601. BigInteger bigInt = new BigInteger(String.valueOf(value));
  4602. bigInt = bigInt.add(BIG_2_64);
  4603. while (bigInt.compareTo(BigInteger.ZERO) != 0) {
  4604. int digit = bigInt.mod(BIG_TEN).intValue();
  4605. buf[--digits] = (char) ('0' + digit);
  4606. bigInt = bigInt.divide(BIG_TEN);
  4607. }
  4608. }
  4609. for (int i = buf.length - digits; i < _min; i++) {
  4610. sb.append(_pad);
  4611. }
  4612. for (; digits < buf.length; digits++) {
  4613. sb.append(buf[digits]);
  4614. }
  4615. return true;
  4616. }
  4617. }
  4618. static class BinaryPrintfSegment extends PrintfSegment {
  4619. private final int _index;
  4620. private final int _min;
  4621. private final char _pad;
  4622. BinaryPrintfSegment(int index, int min, int pad) {
  4623. _index = index;
  4624. _min = min;
  4625. if (pad >= 0) {
  4626. _pad = (char) pad;
  4627. } else {
  4628. _pad = ' ';
  4629. }
  4630. }
  4631. static BinaryPrintfSegment create(String format, int index) {
  4632. int length = format.length();
  4633. int offset = 1;
  4634. char pad = ' ';
  4635. if (format.charAt(offset) == ' ') {
  4636. pad = ' ';
  4637. offset++;
  4638. } else if (format.charAt(offset) == '0') {
  4639. pad = '0';
  4640. offset++;
  4641. }
  4642. int min = 0;
  4643. for (; offset < length - 1; offset++) {
  4644. char ch = format.charAt(offset);
  4645. if ('0' <= ch && ch <= '9') {
  4646. min = 10 * min + ch - '0';
  4647. } else {
  4648. return null;
  4649. }
  4650. }
  4651. return new BinaryPrintfSegment(index, min, pad);
  4652. }
  4653. @Override
  4654. public boolean apply(Env env, StringValue sb, Value[] args) {
  4655. long value;
  4656. if (_index >= 0 && _index < args.length) {
  4657. value = args[_index].toLong();
  4658. } else {
  4659. env.warning(L.l("printf(): not enough arguments to match format."));
  4660. return false;
  4661. }
  4662. int digits = 0;
  4663. long shift = value;
  4664. for (int i = 0; i < 64; i++) {
  4665. if (shift != 0) {
  4666. digits = i;
  4667. }
  4668. shift = shift >>> 1;
  4669. }
  4670. for (int i = digits + 1; i < _min; i++) {
  4671. sb.append(_pad);
  4672. }
  4673. for (; digits >= 0; digits--) {
  4674. int digit = (int) (value >>> (digits)) & 0x1;
  4675. sb.append((char) ('0' + digit));
  4676. }
  4677. return true;
  4678. }
  4679. }
  4680. static class DoublePrintfSegment extends PrintfSegment {
  4681. private final String _format;
  4682. private final boolean _isLeftZero;
  4683. private final int _index;
  4684. private final BiancaLocale _locale;
  4685. DoublePrintfSegment(String format,
  4686. boolean isLeftZero,
  4687. int index,
  4688. BiancaLocale locale) {
  4689. if (hasIndex(format)) {
  4690. _index = getIndex(format);
  4691. _format = getIndexFormat(format);
  4692. } else {
  4693. _format = '%' + format;
  4694. _index = index;
  4695. }
  4696. _isLeftZero = isLeftZero;
  4697. _locale = locale;
  4698. }
  4699. @Override
  4700. public boolean apply(Env env, StringValue sb, Value[] args) {
  4701. double value;
  4702. if (_index < args.length) {
  4703. value = args[_index].toDouble();
  4704. } else {
  4705. env.warning(L.l("printf(): not enough arguments to match format."));
  4706. return false;
  4707. }
  4708. String s;
  4709. if (_locale == null) {
  4710. s = String.format(_format, value);
  4711. } else {
  4712. s = String.format(_locale.getLocale(), _format, value);
  4713. }
  4714. if (_isLeftZero) {
  4715. int len = s.length();
  4716. // php/1174 "-0" not allowed by java formatter
  4717. for (int i = 0; i < len; i++) {
  4718. char ch = s.charAt(i);
  4719. if (ch == ' ') {
  4720. sb.append('0');
  4721. } else {
  4722. sb.append(ch);
  4723. }
  4724. }
  4725. } else {
  4726. sb.append(s);
  4727. }
  4728. return true;
  4729. }
  4730. }
  4731. static class StringPrintfSegment extends PrintfSegment {
  4732. protected final char[] _prefix;
  4733. protected final int _min;
  4734. protected final int _max;
  4735. protected final boolean _isLeft;
  4736. protected final boolean _isUpper;
  4737. protected final char _pad;
  4738. protected final int _index;
  4739. StringPrintfSegment(StringBuilder prefix,
  4740. boolean isLeft, int pad, boolean isUpper,
  4741. int width,
  4742. String format, int index) {
  4743. _prefix = new char[prefix.length()];
  4744. _isLeft = isLeft;
  4745. _isUpper = isUpper;
  4746. if (pad >= 0) {
  4747. _pad = (char) pad;
  4748. } else {
  4749. _pad = ' ';
  4750. }
  4751. prefix.getChars(0, _prefix.length, _prefix, 0);
  4752. if (hasIndex(format)) {
  4753. index = getIndex(format);
  4754. format = getIndexFormat(format);
  4755. }
  4756. int i = 0;
  4757. int len = format.length();
  4758. int min = width;
  4759. int max = Integer.MAX_VALUE;
  4760. char ch = ' ';
  4761. /*
  4762. for (; i < len && '0' <= (ch = format.charAt(i)) && ch <= '9'; i++) {
  4763. min = 10 * min + ch - '0';
  4764. }
  4765. */
  4766. if (0 < len && format.charAt(0) == '.') {
  4767. max = 0;
  4768. for (i++; i < len && '0' <= (ch = format.charAt(i)) && ch <= '9'; i++) {
  4769. max = 10 * max + ch - '0';
  4770. }
  4771. }
  4772. _min = min;
  4773. _max = max;
  4774. _index = index;
  4775. }
  4776. protected String toValue(Value[] args) {
  4777. return args[_index].toString();
  4778. }
  4779. @Override
  4780. public boolean apply(Env env, StringValue sb, Value[] args) {
  4781. sb.append(_prefix, 0, _prefix.length);
  4782. String value;
  4783. if (_index < args.length) {
  4784. value = toValue(args);
  4785. } else {
  4786. env.warning(L.l("printf(): not enough arguments to match format."));
  4787. return false;
  4788. }
  4789. int len = value.length();
  4790. if (_max < len) {
  4791. value = value.substring(0, _max);
  4792. len = _max;
  4793. }
  4794. if (_isUpper) {
  4795. value = value.toUpperCase();
  4796. }
  4797. if (!_isLeft) {
  4798. for (int i = len; i < _min; i++) {
  4799. sb.append(_pad);
  4800. }
  4801. }
  4802. sb.append(value);
  4803. if (_isLeft) {
  4804. for (int i = len; i < _min; i++) {
  4805. sb.append(_pad);
  4806. }
  4807. }
  4808. return true;
  4809. }
  4810. }
  4811. static class CharPrintfSegment extends StringPrintfSegment {
  4812. CharPrintfSegment(StringBuilder prefix,
  4813. boolean isLeft, int pad, boolean isUpper,
  4814. int width,
  4815. String format, int index) {
  4816. super(prefix, isLeft, pad, isUpper, width, format, index);
  4817. }
  4818. @Override
  4819. protected String toValue(Value[] args) {
  4820. if (args.length <= _index) {
  4821. return "";
  4822. }
  4823. Value v = args[_index];
  4824. if (v.isLongConvertible()) {
  4825. return String.valueOf((char) v.toLong());
  4826. } else {
  4827. return v.charValueAt(0).toString();
  4828. }
  4829. }
  4830. }
  4831. static class SimpleStringReader {
  4832. StringValue _str;
  4833. int _length;
  4834. int _index;
  4835. SimpleStringReader(StringValue str) {
  4836. _str = str;
  4837. _length = str.length();
  4838. _index = 0;
  4839. }
  4840. int read() {
  4841. if (_index < _length) {
  4842. return _str.charAt(_index++);
  4843. } else {
  4844. return -1;
  4845. }
  4846. }
  4847. int peek() {
  4848. if (_index < _length) {
  4849. return _str.charAt(_index);
  4850. } else {
  4851. return -1;
  4852. }
  4853. }
  4854. int readInt(int currChar) {
  4855. int number = currChar - '0';
  4856. while (true) {
  4857. currChar = peek();
  4858. if ('0' <= currChar && currChar <= '9') {
  4859. number = number * 10 + currChar - '0';
  4860. _index++;
  4861. } else {
  4862. break;
  4863. }
  4864. }
  4865. return number;
  4866. }
  4867. }
  4868. // sscanf
  4869. abstract static class ScanfSegment {
  4870. abstract public boolean isAssigned();
  4871. abstract public int apply(StringValue string,
  4872. int strlen,
  4873. int sIndex,
  4874. Value var,
  4875. boolean isReturnArray);
  4876. void sscanfPut(Value var, Value val, boolean isReturnArray) {
  4877. if (isReturnArray) {
  4878. var.put(val);
  4879. } else {
  4880. var.set(val);
  4881. }
  4882. }
  4883. }
  4884. static class ScanfConstant extends ScanfSegment {
  4885. private final String _string;
  4886. private final int _strlen;
  4887. private ScanfConstant(String string) {
  4888. _string = string;
  4889. _strlen = string.length();
  4890. }
  4891. @Override
  4892. public boolean isAssigned() {
  4893. return false;
  4894. }
  4895. @Override
  4896. public int apply(StringValue string,
  4897. int strlen,
  4898. int sIndex,
  4899. Value var,
  4900. boolean isReturnArray) {
  4901. int fStrlen = _strlen;
  4902. String fString = _string;
  4903. if (strlen - sIndex < fStrlen) {
  4904. return -1;
  4905. }
  4906. for (int i = 0; i < fStrlen; i++) {
  4907. if (string.charAt(sIndex++) != fString.charAt(i)) {
  4908. return -1;
  4909. }
  4910. }
  4911. return sIndex;
  4912. }
  4913. }
  4914. static class ScanfWhitespace extends ScanfSegment {
  4915. static final ScanfWhitespace SEGMENT = new ScanfWhitespace();
  4916. private ScanfWhitespace() {
  4917. }
  4918. @Override
  4919. public boolean isAssigned() {
  4920. return false;
  4921. }
  4922. @Override
  4923. public int apply(StringValue string,
  4924. int strlen,
  4925. int sIndex,
  4926. Value var,
  4927. boolean isReturnArray) {
  4928. for (;
  4929. sIndex < strlen && isWhitespace(string.charAt(sIndex));
  4930. sIndex++) {
  4931. }
  4932. return sIndex;
  4933. }
  4934. }
  4935. static class ScanfStringLength extends ScanfSegment {
  4936. static final ScanfStringLength SEGMENT = new ScanfStringLength();
  4937. private ScanfStringLength() {
  4938. }
  4939. @Override
  4940. public boolean isAssigned() {
  4941. return true;
  4942. }
  4943. @Override
  4944. public int apply(StringValue string,
  4945. int strlen,
  4946. int sIndex,
  4947. Value var,
  4948. boolean isReturnArray) {
  4949. sscanfPut(var, LongValue.create(sIndex), isReturnArray);
  4950. return sIndex;
  4951. }
  4952. }
  4953. static class ScanfSet extends ScanfSegment {
  4954. private IntSet _set;
  4955. private ScanfSet(IntSet set) {
  4956. _set = set;
  4957. }
  4958. @Override
  4959. public boolean isAssigned() {
  4960. return true;
  4961. }
  4962. @Override
  4963. public int apply(StringValue string,
  4964. int strlen,
  4965. int sIndex,
  4966. Value var,
  4967. boolean isReturnArray) {
  4968. StringValue sb = new StringValue();
  4969. for (; sIndex < strlen; sIndex++) {
  4970. char ch = string.charAt(sIndex);
  4971. if (_set.contains(ch)) {
  4972. sb.append(ch);
  4973. } else {
  4974. break;
  4975. }
  4976. }
  4977. if (sb.length() > 0) {
  4978. sscanfPut(var, sb, isReturnArray);
  4979. } else if (isReturnArray) {
  4980. var.put(NullValue.NULL);
  4981. }
  4982. return sIndex;
  4983. }
  4984. }
  4985. static class ScanfSetNegated extends ScanfSegment {
  4986. private IntSet _set;
  4987. private ScanfSetNegated(IntSet set) {
  4988. _set = set;
  4989. }
  4990. @Override
  4991. public boolean isAssigned() {
  4992. return true;
  4993. }
  4994. @Override
  4995. public int apply(StringValue string,
  4996. int strlen,
  4997. int sIndex,
  4998. Value var,
  4999. boolean isReturnArray) {
  5000. StringValue sb = new StringValue();
  5001. for (; sIndex < strlen; sIndex++) {
  5002. char ch = string.charAt(sIndex);
  5003. if (!_set.contains(ch)) {
  5004. sb.append(ch);
  5005. } else {
  5006. break;
  5007. }
  5008. }
  5009. if (sb.length() > 0) {
  5010. sscanfPut(var, sb, isReturnArray);
  5011. } else if (isReturnArray) {
  5012. var.put(NullValue.NULL);
  5013. }
  5014. return sIndex;
  5015. }
  5016. }
  5017. static class ScanfScientific extends ScanfSegment {
  5018. private final int _maxLen;
  5019. ScanfScientific(int maxLen) {
  5020. if (maxLen < 0) {
  5021. maxLen = Integer.MAX_VALUE;
  5022. }
  5023. _maxLen = maxLen;
  5024. }
  5025. @Override
  5026. public boolean isAssigned() {
  5027. return true;
  5028. }
  5029. @Override
  5030. public int apply(StringValue s,
  5031. int strlen,
  5032. int i,
  5033. Value var,
  5034. boolean isReturnArray) {
  5035. if (i == strlen) {
  5036. if (isReturnArray) {
  5037. var.put(NullValue.NULL);
  5038. }
  5039. return i;
  5040. }
  5041. int start = i;
  5042. int len = strlen;
  5043. int ch = 0;
  5044. int maxLen = _maxLen;
  5045. if (i < len && maxLen > 0 && ((ch = s.charAt(i)) == '+' || ch == '-')) {
  5046. i++;
  5047. maxLen--;
  5048. }
  5049. for (; i < len && maxLen > 0
  5050. && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
  5051. maxLen--;
  5052. }
  5053. if (ch == '.') {
  5054. maxLen--;
  5055. for (i++; i < len && maxLen > 0
  5056. && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
  5057. maxLen--;
  5058. }
  5059. }
  5060. if (ch == 'e' || ch == 'E') {
  5061. maxLen--;
  5062. int e = i++;
  5063. if (start == e) {
  5064. sscanfPut(var, NullValue.NULL, isReturnArray);
  5065. return start;
  5066. }
  5067. if (i < len && maxLen > 0 && (ch = s.charAt(i)) == '+' || ch == '-') {
  5068. i++;
  5069. maxLen--;
  5070. }
  5071. for (; i < len && maxLen > 0
  5072. && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
  5073. maxLen--;
  5074. }
  5075. if (i == e + 1) {
  5076. i = e;
  5077. }
  5078. }
  5079. double val;
  5080. if (i == 0) {
  5081. val = 0;
  5082. } else {
  5083. val = Double.parseDouble(s.substring(start, i).toString());
  5084. }
  5085. sscanfPut(var, DoubleValue.create(val), isReturnArray);
  5086. return i;
  5087. }
  5088. }
  5089. static class ScanfHex extends ScanfSegment {
  5090. private final int _maxLen;
  5091. ;
  5092. ScanfHex(int maxLen) {
  5093. if (maxLen < 0) {
  5094. maxLen = Integer.MAX_VALUE;
  5095. }
  5096. _maxLen = maxLen;
  5097. }
  5098. @Override
  5099. public boolean isAssigned() {
  5100. return true;
  5101. }
  5102. @Override
  5103. public int apply(StringValue string,
  5104. int strlen,
  5105. int sIndex,
  5106. Value var,
  5107. boolean isReturnArray) {
  5108. if (sIndex == strlen) {
  5109. if (isReturnArray) {
  5110. var.put(NullValue.NULL);
  5111. }
  5112. return sIndex;
  5113. }
  5114. int val = 0;
  5115. int sign = 1;
  5116. boolean isMatched = false;
  5117. int maxLen = _maxLen;
  5118. if (sIndex < strlen) {
  5119. char ch = string.charAt(sIndex);
  5120. if (ch == '+') {
  5121. sIndex++;
  5122. maxLen--;
  5123. } else if (ch == '-') {
  5124. sign = -1;
  5125. sIndex++;
  5126. maxLen--;
  5127. }
  5128. }
  5129. for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
  5130. char ch = string.charAt(sIndex);
  5131. if ('0' <= ch && ch <= '9') {
  5132. val = val * 16 + ch - '0';
  5133. isMatched = true;
  5134. } else if ('a' <= ch && ch <= 'f') {
  5135. val = val * 16 + ch - 'a' + 10;
  5136. isMatched = true;
  5137. } else if ('A' <= ch && ch <= 'F') {
  5138. val = val * 16 + ch - 'A' + 10;
  5139. isMatched = true;
  5140. } else if (!isMatched) {
  5141. sscanfPut(var, NullValue.NULL, isReturnArray);
  5142. return sIndex;
  5143. } else {
  5144. break;
  5145. }
  5146. }
  5147. sscanfPut(var, LongValue.create(val * sign), isReturnArray);
  5148. return sIndex;
  5149. }
  5150. }
  5151. static class ScanfInteger extends ScanfSegment {
  5152. private final int _maxLen;
  5153. private final int _base;
  5154. private final boolean _isUnsigned;
  5155. ScanfInteger(int maxLen, int base, boolean isUnsigned) {
  5156. if (maxLen < 0) {
  5157. maxLen = Integer.MAX_VALUE;
  5158. }
  5159. _maxLen = maxLen;
  5160. _base = base;
  5161. _isUnsigned = isUnsigned;
  5162. }
  5163. @Override
  5164. public boolean isAssigned() {
  5165. return true;
  5166. }
  5167. @Override
  5168. public int apply(StringValue string,
  5169. int strlen,
  5170. int sIndex,
  5171. Value var,
  5172. boolean isReturnArray) {
  5173. if (sIndex == strlen) {
  5174. if (isReturnArray) {
  5175. var.put(NullValue.NULL);
  5176. }
  5177. return sIndex;
  5178. }
  5179. // TODO: 32-bit vs 64-bit
  5180. int val = 0;
  5181. int sign = 1;
  5182. boolean isNotMatched = true;
  5183. int maxLen = _maxLen;
  5184. if (sIndex < strlen) {
  5185. char ch = string.charAt(sIndex);
  5186. if (ch == '+') {
  5187. sIndex++;
  5188. maxLen--;
  5189. } else if (ch == '-') {
  5190. sign = -1;
  5191. sIndex++;
  5192. maxLen--;
  5193. }
  5194. }
  5195. int base = _base;
  5196. int topRange = base + '0';
  5197. for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
  5198. char ch = string.charAt(sIndex);
  5199. if ('0' <= ch && ch < topRange) {
  5200. val = val * base + ch - '0';
  5201. isNotMatched = false;
  5202. } else if (isNotMatched) {
  5203. sscanfPut(var, NullValue.NULL, isReturnArray);
  5204. return sIndex;
  5205. } else {
  5206. break;
  5207. }
  5208. }
  5209. if (_isUnsigned) {
  5210. if (sign == -1 && val != 0) {
  5211. sscanfPut(
  5212. var, StringValue.create(0xffffffffL - val + 1), isReturnArray);
  5213. } else {
  5214. sscanfPut(var, LongValue.create(val), isReturnArray);
  5215. }
  5216. } else {
  5217. sscanfPut(var, LongValue.create(val * sign), isReturnArray);
  5218. }
  5219. return sIndex;
  5220. }
  5221. }
  5222. static class ScanfString extends ScanfSegment {
  5223. private final int _maxLen;
  5224. ScanfString(int maxLen) {
  5225. if (maxLen < 0) {
  5226. maxLen = Integer.MAX_VALUE;
  5227. }
  5228. _maxLen = maxLen;
  5229. }
  5230. @Override
  5231. public boolean isAssigned() {
  5232. return true;
  5233. }
  5234. /**
  5235. * Scans a string with a given length.
  5236. */
  5237. @Override
  5238. public int apply(StringValue string,
  5239. int strlen,
  5240. int sIndex,
  5241. Value var,
  5242. boolean isReturnArray) {
  5243. if (sIndex == strlen) {
  5244. if (isReturnArray) {
  5245. var.put(NullValue.NULL);
  5246. }
  5247. return sIndex;
  5248. }
  5249. StringValue sb = new StringValue();
  5250. int maxLen = _maxLen;
  5251. for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
  5252. char ch = string.charAt(sIndex);
  5253. if (isWhitespace(ch)) {
  5254. break;
  5255. }
  5256. sb.append(ch);
  5257. }
  5258. sscanfPut(var, sb, isReturnArray);
  5259. return sIndex;
  5260. }
  5261. }
  5262. static {
  5263. DEFAULT_DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols();
  5264. DEFAULT_DECIMAL_FORMAT_SYMBOLS.setDecimalSeparator('.');
  5265. DEFAULT_DECIMAL_FORMAT_SYMBOLS.setGroupingSeparator(',');
  5266. DEFAULT_DECIMAL_FORMAT_SYMBOLS.setZeroDigit('0');
  5267. }
  5268. }