PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/freemarker/src/freemarker/template/utility/StringUtil.java

#
Java | 1324 lines | 1141 code | 36 blank | 147 comment | 231 complexity | b40e370b5c6bd7cd3070a37487c54fc1 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. /*
  2. * Copyright (c) 2003 The Visigoth Software Society. All rights
  3. * reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. *
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. *
  12. * 2. Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in
  14. * the documentation and/or other materials provided with the
  15. * distribution.
  16. *
  17. * 3. The end-user documentation included with the redistribution, if
  18. * any, must include the following acknowledgement:
  19. * "This product includes software developed by the
  20. * Visigoth Software Society (http://www.visigoths.org/)."
  21. * Alternately, this acknowledgement may appear in the software itself,
  22. * if and wherever such third-party acknowledgements normally appear.
  23. *
  24. * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
  25. * project contributors may be used to endorse or promote products derived
  26. * from this software without prior written permission. For written
  27. * permission, please contact visigoths@visigoths.org.
  28. *
  29. * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
  30. * nor may "FreeMarker" or "Visigoth" appear in their names
  31. * without prior written permission of the Visigoth Software Society.
  32. *
  33. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  34. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  36. * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
  37. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  39. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  40. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  41. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  42. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  43. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  44. * SUCH DAMAGE.
  45. * ====================================================================
  46. *
  47. * This software consists of voluntary contributions made by many
  48. * individuals on behalf of the Visigoth Software Society. For more
  49. * information on the Visigoth Software Society, please see
  50. * http://www.visigoths.org/
  51. */
  52. package freemarker.template.utility;
  53. import java.io.UnsupportedEncodingException;
  54. import java.util.*;
  55. import freemarker.template.Template;
  56. import freemarker.core.Environment;
  57. import freemarker.core.parser.ParseException;
  58. /**
  59. * Some text related utilities.
  60. *
  61. * @version $Id: StringUtil.java,v 1.48 2005/06/01 22:39:08 ddekany Exp $
  62. */
  63. public class StringUtil {
  64. private static final char[] ESCAPES = createEscapes();
  65. /*
  66. * For better performance most methods are folded down. Don't you scream... :)
  67. */
  68. /**
  69. * HTML encoding (does not convert line breaks).
  70. * Replaces all '>' '<' '&' and '"' with entity reference
  71. */
  72. public static String HTMLEnc(String s) {
  73. return XMLEncNA(s);
  74. }
  75. /**
  76. * XML Encoding.
  77. * Replaces all '>' '<' '&', "'" and '"' with entity reference
  78. */
  79. public static String XMLEnc(String s) {
  80. return XMLOrXHTMLEnc(s, "'");
  81. }
  82. /**
  83. * XHTML Encoding.
  84. * Replaces all '>' '<' '&', "'" and '"' with entity reference
  85. * suitable for XHTML decoding in common user agents (including legacy
  86. * user agents, which do not decode "'" to "'", so "'" is used
  87. * instead [see http://www.w3.org/TR/xhtml1/#C_16])
  88. */
  89. public static String XHTMLEnc(String s) {
  90. return XMLOrXHTMLEnc(s, "'");
  91. }
  92. private static String XMLOrXHTMLEnc(String s, String aposReplacement) {
  93. int ln = s.length();
  94. for (int i = 0; i < ln; i++) {
  95. char c = s.charAt(i);
  96. if (c == '<' || c == '>' || c == '&' || c == '"' || c == '\'') {
  97. StringBuilder b =
  98. new StringBuilder(s.substring(0, i));
  99. switch (c) {
  100. case '<': b.append("&lt;"); break;
  101. case '>': b.append("&gt;"); break;
  102. case '&': b.append("&amp;"); break;
  103. case '"': b.append("&quot;"); break;
  104. case '\'': b.append(aposReplacement); break;
  105. }
  106. i++;
  107. int next = i;
  108. while (i < ln) {
  109. c = s.charAt(i);
  110. if (c == '<' || c == '>' || c == '&' || c == '"' || c == '\'') {
  111. b.append(s.substring(next, i));
  112. switch (c) {
  113. case '<': b.append("&lt;"); break;
  114. case '>': b.append("&gt;"); break;
  115. case '&': b.append("&amp;"); break;
  116. case '"': b.append("&quot;"); break;
  117. case '\'': b.append(aposReplacement); break;
  118. }
  119. next = i + 1;
  120. }
  121. i++;
  122. }
  123. if (next < ln) b.append(s.substring(next));
  124. s = b.toString();
  125. break;
  126. } // if c ==
  127. } // for
  128. return s;
  129. }
  130. /**
  131. * XML encoding without replacing apostrophes.
  132. * @see #XMLEnc(String)
  133. */
  134. public static String XMLEncNA(String s) {
  135. int ln = s.length();
  136. for (int i = 0; i < ln; i++) {
  137. char c = s.charAt(i);
  138. if (c == '<' || c == '>' || c == '&' || c == '"') {
  139. StringBuilder b =
  140. new StringBuilder(s.substring(0, i));
  141. switch (c) {
  142. case '<': b.append("&lt;"); break;
  143. case '>': b.append("&gt;"); break;
  144. case '&': b.append("&amp;"); break;
  145. case '"': b.append("&quot;"); break;
  146. }
  147. i++;
  148. int next = i;
  149. while (i < ln) {
  150. c = s.charAt(i);
  151. if (c == '<' || c == '>' || c == '&' || c == '"') {
  152. b.append(s.substring(next, i));
  153. switch (c) {
  154. case '<': b.append("&lt;"); break;
  155. case '>': b.append("&gt;"); break;
  156. case '&': b.append("&amp;"); break;
  157. case '"': b.append("&quot;"); break;
  158. }
  159. next = i + 1;
  160. }
  161. i++;
  162. }
  163. if (next < ln) b.append(s.substring(next));
  164. s = b.toString();
  165. break;
  166. } // if c ==
  167. } // for
  168. return s;
  169. }
  170. /**
  171. * XML encoding for attributes valies quoted with <tt>"</tt> (not with <tt>'</tt>!).
  172. * Also can be used for HTML attributes that are quoted with <tt>"</tt>.
  173. * @see #XMLEnc(String)
  174. */
  175. public static String XMLEncQAttr(String s) {
  176. int ln = s.length();
  177. for (int i = 0; i < ln; i++) {
  178. char c = s.charAt(i);
  179. if (c == '<' || c == '&' || c == '"') {
  180. StringBuilder b =
  181. new StringBuilder(s.substring(0, i));
  182. switch (c) {
  183. case '<': b.append("&lt;"); break;
  184. case '&': b.append("&amp;"); break;
  185. case '"': b.append("&quot;"); break;
  186. }
  187. i++;
  188. int next = i;
  189. while (i < ln) {
  190. c = s.charAt(i);
  191. if (c == '<' || c == '&' || c == '"') {
  192. b.append(s.substring(next, i));
  193. switch (c) {
  194. case '<': b.append("&lt;"); break;
  195. case '&': b.append("&amp;"); break;
  196. case '"': b.append("&quot;"); break;
  197. }
  198. next = i + 1;
  199. }
  200. i++;
  201. }
  202. if (next < ln) {
  203. b.append(s.substring(next));
  204. }
  205. s = b.toString();
  206. break;
  207. } // if c ==
  208. } // for
  209. return s;
  210. }
  211. /**
  212. * XML encoding without replacing apostrophes and quotation marks and
  213. * greater-thans (except in {@code ]]>}).
  214. * @see #XMLEnc(String)
  215. */
  216. public static String XMLEncNQG(String s) {
  217. int ln = s.length();
  218. for (int i = 0; i < ln; i++) {
  219. char c = s.charAt(i);
  220. if (c == '<'
  221. || (c == '>' && i > 1
  222. && s.charAt(i - 1) == ']'
  223. && s.charAt(i - 2) == ']')
  224. || c == '&') {
  225. StringBuffer b =
  226. new StringBuffer(s.substring(0, i));
  227. switch (c) {
  228. case '<': b.append("&lt;"); break;
  229. case '>': b.append("&gt;"); break;
  230. case '&': b.append("&amp;"); break;
  231. default: throw new RuntimeException("Bug: unexpected char");
  232. }
  233. i++;
  234. int next = i;
  235. while (i < ln) {
  236. c = s.charAt(i);
  237. if (c == '<'
  238. || (c == '>' && i > 1
  239. && s.charAt(i - 1) == ']'
  240. && s.charAt(i - 2) == ']')
  241. || c == '&') {
  242. b.append(s.substring(next, i));
  243. switch (c) {
  244. case '<': b.append("&lt;"); break;
  245. case '>': b.append("&gt;"); break;
  246. case '&': b.append("&amp;"); break;
  247. default: throw new RuntimeException("Bug: unexpected char");
  248. }
  249. next = i + 1;
  250. }
  251. i++;
  252. }
  253. if (next < ln) {
  254. b.append(s.substring(next));
  255. }
  256. s = b.toString();
  257. break;
  258. } // if c ==
  259. } // for
  260. return s;
  261. }
  262. /**
  263. * Rich Text Format encoding (does not replace line breaks).
  264. * Escapes all '\' '{' '}' and '"'
  265. */
  266. public static String RTFEnc(String s) {
  267. int ln = s.length();
  268. for (int i = 0; i < ln; i++) {
  269. char c = s.charAt(i);
  270. if (c == '\\' || c == '{' || c == '}') {
  271. StringBuilder b =
  272. new StringBuilder(s.substring(0, i));
  273. switch (c) {
  274. case '\\': b.append("\\\\"); break;
  275. case '{': b.append("\\{"); break;
  276. case '}': b.append("\\}"); break;
  277. }
  278. i++;
  279. int next = i;
  280. while (i < ln) {
  281. c = s.charAt(i);
  282. if (c == '\\' || c == '{' || c == '}') {
  283. b.append(s.substring(next, i));
  284. switch (c) {
  285. case '\\': b.append("\\\\"); break;
  286. case '{': b.append("\\{"); break;
  287. case '}': b.append("\\}"); break;
  288. }
  289. next = i + 1;
  290. }
  291. i++;
  292. }
  293. if (next < ln) b.append(s.substring(next));
  294. s = b.toString();
  295. break;
  296. } // if c ==
  297. } // for
  298. return s;
  299. }
  300. /**
  301. * URL encoding (like%20this).
  302. */
  303. public static String URLEnc(String s, String charset)
  304. throws UnsupportedEncodingException {
  305. int ln = s.length();
  306. int i;
  307. for (i = 0; i < ln; i++) {
  308. char c = s.charAt(i);
  309. if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
  310. || c >= '0' && c <= '9'
  311. || c == '_' || c == '-' || c == '.' || c == '!' || c == '~'
  312. || c >= '\'' && c <= '*')) {
  313. break;
  314. }
  315. }
  316. if (i == ln) {
  317. // Nothing to escape
  318. return s;
  319. }
  320. StringBuilder b = new StringBuilder(ln + ln / 3 + 2);
  321. b.append(s.substring(0, i));
  322. int encstart = i;
  323. for (i++; i < ln; i++) {
  324. char c = s.charAt(i);
  325. if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
  326. || c >= '0' && c <= '9'
  327. || c == '_' || c == '-' || c == '.' || c == '!' || c == '~'
  328. || c >= '\'' && c <= '*') {
  329. if (encstart != -1) {
  330. byte[] o = s.substring(encstart, i).getBytes(charset);
  331. for (int j = 0; j < o.length; j++) {
  332. b.append('%');
  333. byte bc = o[j];
  334. int c1 = bc & 0x0F;
  335. int c2 = (bc >> 4) & 0x0F;
  336. b.append((char) (c2 < 10 ? c2 + '0' : c2 - 10 + 'A'));
  337. b.append((char) (c1 < 10 ? c1 + '0' : c1 - 10 + 'A'));
  338. }
  339. encstart = -1;
  340. }
  341. b.append(c);
  342. } else {
  343. if (encstart == -1) {
  344. encstart = i;
  345. }
  346. }
  347. }
  348. if (encstart != -1) {
  349. byte[] o = s.substring(encstart, i).getBytes(charset);
  350. for (int j = 0; j < o.length; j++) {
  351. b.append('%');
  352. byte bc = o[j];
  353. int c1 = bc & 0x0F;
  354. int c2 = (bc >> 4) & 0x0F;
  355. b.append((char) (c2 < 10 ? c2 + '0' : c2 - 10 + 'A'));
  356. b.append((char) (c1 < 10 ? c1 + '0' : c1 - 10 + 'A'));
  357. }
  358. }
  359. return b.toString();
  360. }
  361. private static char[] createEscapes()
  362. {
  363. char[] escapes = new char['\\' + 1];
  364. for(int i = 0; i < 32; ++i)
  365. {
  366. escapes[i] = 1;
  367. }
  368. escapes['\\'] = '\\';
  369. escapes['\''] = '\'';
  370. escapes['"'] = '"';
  371. escapes['<'] = 'l';
  372. escapes['>'] = 'g';
  373. escapes['&'] = 'a';
  374. escapes['\b'] = 'b';
  375. escapes['\t'] = 't';
  376. escapes['\n'] = 'n';
  377. escapes['\f'] = 'f';
  378. escapes['\r'] = 'r';
  379. escapes['$'] = '$';
  380. return escapes;
  381. }
  382. public static String FTLStringLiteralEnc(String s)
  383. {
  384. StringBuilder buf = null;
  385. int l = s.length();
  386. int el = ESCAPES.length;
  387. for(int i = 0; i < l; i++)
  388. {
  389. char c = s.charAt(i);
  390. if(c < el)
  391. {
  392. char escape = ESCAPES[c];
  393. switch(escape)
  394. {
  395. case 0:
  396. {
  397. if (buf != null) {
  398. buf.append(c);
  399. }
  400. break;
  401. }
  402. case 1:
  403. {
  404. if (buf == null) {
  405. buf = new StringBuilder(s.length() + 3);
  406. buf.append(s.substring(0, i));
  407. }
  408. // hex encoding for characters below 0x20
  409. // that have no other escape representation
  410. buf.append("\\x00");
  411. int c2 = (c >> 4) & 0x0F;
  412. c = (char) (c & 0x0F);
  413. buf.append((char) (c2 < 10 ? c2 + '0' : c2 - 10 + 'A'));
  414. buf.append((char) (c < 10 ? c + '0' : c - 10 + 'A'));
  415. break;
  416. }
  417. default:
  418. {
  419. if (buf == null) {
  420. buf = new StringBuilder(s.length() + 2);
  421. buf.append(s.substring(0, i));
  422. }
  423. buf.append('\\');
  424. buf.append(escape);
  425. }
  426. }
  427. } else {
  428. if (buf != null) {
  429. buf.append(c);
  430. }
  431. }
  432. }
  433. return buf == null ? s : buf.toString();
  434. }
  435. /**
  436. * FTL string literal decoding.
  437. *
  438. * \\, \", \', \n, \t, \r, \b and \f will be replaced according to
  439. * Java rules. In additional, it knows \g, \l, \a and \{ which are
  440. * replaced with &lt;, >, &amp; and { respectively.
  441. * \x works as hexadecimal character code escape. The character
  442. * codes are interpreted according to UCS basic plane (Unicode).
  443. * "f\x006Fo", "f\x06Fo" and "f\x6Fo" will be "foo".
  444. * "f\x006F123" will be "foo123" as the maximum number of digits is 4.
  445. *
  446. * All other \X (where X is any character not mentioned above or End-of-string)
  447. * will cause a ParseException.
  448. *
  449. * @param s String literal <em>without</em> the surrounding quotation marks
  450. * @return String with all escape sequences resolved
  451. * @throws ParseException if there string contains illegal escapes
  452. */
  453. public static String FTLStringLiteralDec(String s) throws ParseException {
  454. int idx = s.indexOf('\\');
  455. if (idx == -1) {
  456. return s;
  457. }
  458. int lidx = s.length() - 1;
  459. int bidx = 0;
  460. StringBuilder buf = new StringBuilder(lidx);
  461. do {
  462. buf.append(s.substring(bidx, idx));
  463. if (idx >= lidx) {
  464. throw new ParseException("The last character of string literal is backslash", 0,0);
  465. }
  466. char c = s.charAt(idx + 1);
  467. switch (c) {
  468. case '"':
  469. buf.append('"');
  470. bidx = idx + 2;
  471. break;
  472. case '\'':
  473. buf.append('\'');
  474. bidx = idx + 2;
  475. break;
  476. case '\\':
  477. buf.append('\\');
  478. bidx = idx + 2;
  479. break;
  480. case 'n':
  481. buf.append('\n');
  482. bidx = idx + 2;
  483. break;
  484. case 'r':
  485. buf.append('\r');
  486. bidx = idx + 2;
  487. break;
  488. case 't':
  489. buf.append('\t');
  490. bidx = idx + 2;
  491. break;
  492. case 'f':
  493. buf.append('\f');
  494. bidx = idx + 2;
  495. break;
  496. case 'b':
  497. buf.append('\b');
  498. bidx = idx + 2;
  499. break;
  500. case 'g':
  501. buf.append('>');
  502. bidx = idx + 2;
  503. break;
  504. case 'l':
  505. buf.append('<');
  506. bidx = idx + 2;
  507. break;
  508. case 'a':
  509. buf.append('&');
  510. bidx = idx + 2;
  511. break;
  512. case '{':
  513. buf.append('{');
  514. bidx = idx + 2;
  515. break;
  516. case 'x': {
  517. idx += 2;
  518. int x = idx;
  519. int y = 0;
  520. int z = lidx > idx + 3 ? idx + 3 : lidx;
  521. while (idx <= z) {
  522. char b = s.charAt(idx);
  523. if (b >= '0' && b <= '9') {
  524. y <<= 4;
  525. y += b - '0';
  526. } else if (b >= 'a' && b <= 'f') {
  527. y <<= 4;
  528. y += b - 'a' + 10;
  529. } else if (b >= 'A' && b <= 'F') {
  530. y <<= 4;
  531. y += b - 'A' + 10;
  532. } else {
  533. break;
  534. }
  535. idx++;
  536. }
  537. if (x < idx) {
  538. buf.append((char) y);
  539. } else {
  540. throw new ParseException("Invalid \\x escape in a string literal",0,0);
  541. }
  542. bidx = idx;
  543. break;
  544. }
  545. default:
  546. throw new ParseException("Invalid escape sequence (\\" + c + ") in a string literal",0,0);
  547. }
  548. idx = s.indexOf('\\', bidx);
  549. } while (idx != -1);
  550. buf.append(s.substring(bidx));
  551. return buf.toString();
  552. }
  553. public static Locale deduceLocale(String input) {
  554. Locale locale = Locale.getDefault();
  555. if (input.charAt(0) == '"') input = input.substring(1, input.length() -1);
  556. StringTokenizer st = new StringTokenizer(input, ",_ ");
  557. String lang = "", country = "";
  558. if (st.hasMoreTokens()) {
  559. lang = st.nextToken();
  560. }
  561. if (st.hasMoreTokens()) {
  562. country = st.nextToken();
  563. }
  564. if (!st.hasMoreTokens()) {
  565. locale = new Locale(lang, country);
  566. }
  567. else {
  568. locale = new Locale(lang, country, st.nextToken());
  569. }
  570. return locale;
  571. }
  572. public static String capitalize(String s) {
  573. StringTokenizer st = new StringTokenizer(s, " \t\r\n", true);
  574. StringBuilder buf = new StringBuilder(s.length());
  575. while (st.hasMoreTokens()) {
  576. String tok = st.nextToken();
  577. buf.append(tok.substring(0, 1).toUpperCase());
  578. buf.append(tok.substring(1).toLowerCase());
  579. }
  580. return buf.toString();
  581. }
  582. public static boolean getYesNo(String s) {
  583. if (s.startsWith("\"")) {
  584. s = s.substring(1, s.length() -1);
  585. }
  586. if (s.equalsIgnoreCase("n")
  587. || s.equalsIgnoreCase("no")
  588. || s.equalsIgnoreCase("f")
  589. || s.equalsIgnoreCase("false")) {
  590. return false;
  591. }
  592. else if (s.equalsIgnoreCase("y")
  593. || s.equalsIgnoreCase("yes")
  594. || s.equalsIgnoreCase("t")
  595. || s.equalsIgnoreCase("true")) {
  596. return true;
  597. }
  598. throw new IllegalArgumentException("Illegal boolean value: " + s);
  599. }
  600. /**
  601. * Splits a string at the specified character.
  602. */
  603. public static String[] split(String s, char c) {
  604. int i, b, e;
  605. int cnt;
  606. String res[];
  607. int ln = s.length();
  608. i = 0;
  609. cnt = 1;
  610. while ((i = s.indexOf(c, i)) != -1) {
  611. cnt++;
  612. i++;
  613. }
  614. res = new String[cnt];
  615. i = 0;
  616. b = 0;
  617. while (b <= ln) {
  618. e = s.indexOf(c, b);
  619. if (e == -1) e = ln;
  620. res[i++] = s.substring(b, e);
  621. b = e + 1;
  622. }
  623. return res;
  624. }
  625. /**
  626. * Splits a string at the specified string.
  627. */
  628. public static String[] split(String s, String sep, boolean caseInsensitive) {
  629. String splitString = caseInsensitive ? sep.toLowerCase() : sep;
  630. String input = caseInsensitive ? s.toLowerCase() : s;
  631. int i, b, e;
  632. int cnt;
  633. String res[];
  634. int ln = s.length();
  635. int sln = sep.length();
  636. if (sln == 0) throw new IllegalArgumentException(
  637. "The separator string has 0 length");
  638. i = 0;
  639. cnt = 1;
  640. while ((i = input.indexOf(splitString, i)) != -1) {
  641. cnt++;
  642. i += sln;
  643. }
  644. res = new String[cnt];
  645. i = 0;
  646. b = 0;
  647. while (b <= ln) {
  648. e = input.indexOf(splitString, b);
  649. if (e == -1) e = ln;
  650. res[i++] = s.substring(b, e);
  651. b = e + sln;
  652. }
  653. return res;
  654. }
  655. /**
  656. * Replaces all occurrences of a sub-string in a string.
  657. * @param text The string where it will replace <code>oldsub</code> with
  658. * <code>newsub</code>.
  659. * @return String The string after the replacements.
  660. */
  661. public static String replace(String text,
  662. String oldsub,
  663. String newsub,
  664. boolean caseInsensitive,
  665. boolean firstOnly)
  666. {
  667. StringBuilder buf;
  668. int tln;
  669. int oln = oldsub.length();
  670. if (oln == 0) {
  671. int nln = newsub.length();
  672. if (nln == 0) {
  673. return text;
  674. } else {
  675. if (firstOnly) {
  676. return newsub + text;
  677. } else {
  678. tln = text.length();
  679. buf = new StringBuilder(tln + (tln + 1) * nln);
  680. buf.append(newsub);
  681. for (int i = 0; i < tln; i++) {
  682. buf.append(text.charAt(i));
  683. buf.append(newsub);
  684. }
  685. return buf.toString();
  686. }
  687. }
  688. } else {
  689. oldsub = caseInsensitive ? oldsub.toLowerCase() : oldsub;
  690. String input = caseInsensitive ? text.toLowerCase() : text;
  691. int e = input.indexOf(oldsub);
  692. if (e == -1) {
  693. return text;
  694. }
  695. int b = 0;
  696. tln = text.length();
  697. buf = new StringBuilder(
  698. tln + Math.max(newsub.length() - oln, 0) * 3);
  699. do {
  700. buf.append(text.substring(b, e));
  701. buf.append(newsub);
  702. b = e + oln;
  703. e = input.indexOf(oldsub, b);
  704. } while (e != -1 && !firstOnly);
  705. buf.append(text.substring(b));
  706. return buf.toString();
  707. }
  708. }
  709. /**
  710. * Removes the line-break from the end of the string.
  711. */
  712. public static String chomp(String s) {
  713. if (s.endsWith("\r\n")) return s.substring(0, s.length() - 2);
  714. if (s.endsWith("\r") || s.endsWith("\n"))
  715. return s.substring(0, s.length() - 1);
  716. return s;
  717. }
  718. /**
  719. * Quotes string as Java Language string literal.
  720. * Returns string <code>"null"</code> if <code>s</code>
  721. * is <code>null</code>.
  722. */
  723. public static String jQuote(String s) {
  724. if (s == null) {
  725. return "null";
  726. }
  727. int ln = s.length();
  728. StringBuilder b = new StringBuilder(ln + 4);
  729. b.append('"');
  730. for (int i = 0; i < ln; i++) {
  731. char c = s.charAt(i);
  732. if (c == '"') {
  733. b.append("\\\"");
  734. } else if (c == '\\') {
  735. b.append("\\\\");
  736. } else if (c < 0x20) {
  737. if (c == '\n') {
  738. b.append("\\n");
  739. } else if (c == '\r') {
  740. b.append("\\r");
  741. } else if (c == '\f') {
  742. b.append("\\f");
  743. } else if (c == '\b') {
  744. b.append("\\b");
  745. } else if (c == '\t') {
  746. b.append("\\t");
  747. } else {
  748. b.append("\\u00");
  749. int x = c / 0x10;
  750. b.append((char) (x < 0xA ? x + '0' : x - 0xA + 'A'));
  751. x = c & 0xF;
  752. b.append((char) (x < 0xA ? x + '0' : x - 0xA + 'A'));
  753. }
  754. } else {
  755. b.append(c);
  756. }
  757. } // for each characters
  758. b.append('"');
  759. return b.toString();
  760. }
  761. /**
  762. * Escapes the <code>String</code> with the escaping rules of Java language
  763. * string literals, so it is safe to insert the value into a string literal.
  764. * The resulting string will not be quoted.
  765. *
  766. * <p>In additional, all characters under UCS code point 0x20, that has no
  767. * dedicated escape sequence in Java language, will be replaced with UNICODE
  768. * escape (<tt>\<!-- -->u<i>XXXX</i></tt>).
  769. *
  770. * @see #jQuote(String)
  771. */
  772. public static String javaStringEnc(String s) {
  773. int ln = s.length();
  774. for (int i = 0; i < ln; i++) {
  775. char c = s.charAt(i);
  776. if (c == '"' || c == '\\' || c < 0x20) {
  777. StringBuilder b = new StringBuilder(ln + 4);
  778. b.append(s.substring(0, i));
  779. while (true) {
  780. if (c == '"') {
  781. b.append("\\\"");
  782. } else if (c == '\\') {
  783. b.append("\\\\");
  784. } else if (c < 0x20) {
  785. if (c == '\n') {
  786. b.append("\\n");
  787. } else if (c == '\r') {
  788. b.append("\\r");
  789. } else if (c == '\f') {
  790. b.append("\\f");
  791. } else if (c == '\b') {
  792. b.append("\\b");
  793. } else if (c == '\t') {
  794. b.append("\\t");
  795. } else {
  796. b.append("\\u00");
  797. int x = c / 0x10;
  798. b.append((char)
  799. (x < 0xA ? x + '0' : x - 0xA + 'a'));
  800. x = c & 0xF;
  801. b.append((char)
  802. (x < 0xA ? x + '0' : x - 0xA + 'a'));
  803. }
  804. } else {
  805. b.append(c);
  806. }
  807. i++;
  808. if (i >= ln) {
  809. return b.toString();
  810. }
  811. c = s.charAt(i);
  812. }
  813. } // if has to be escaped
  814. } // for each characters
  815. return s;
  816. }
  817. /**
  818. * Escapes a <code>String</code> according the JavaScript string literal
  819. * escaping rules. The resulting string will not be quoted.
  820. *
  821. * <p>It escapes both <tt>'</tt> and <tt>"</tt>.
  822. * In additional it escapes <tt>></tt> as <tt>\></tt> (to avoid
  823. * <tt>&lt;/script></tt>). Furthermore, all characters under UCS code point
  824. * 0x20, that has no dedicated escape sequence in JavaScript language, will
  825. * be replaced with hexadecimal escape (<tt>\x<i>XX</i></tt>).
  826. */
  827. public static String javaScriptStringEnc(String s) {
  828. int ln = s.length();
  829. for (int i = 0; i < ln; i++) {
  830. char c = s.charAt(i);
  831. if (c == '"' || c == '\'' || c == '\\' || c == '>' || c < 0x20) {
  832. StringBuilder b = new StringBuilder(ln + 4);
  833. b.append(s.substring(0, i));
  834. while (true) {
  835. if (c == '"') {
  836. b.append("\\\"");
  837. } else if (c == '\'') {
  838. b.append("\\'");
  839. } else if (c == '\\') {
  840. b.append("\\\\");
  841. } else if (c == '>') {
  842. b.append("\\>");
  843. } else if (c < 0x20) {
  844. if (c == '\n') {
  845. b.append("\\n");
  846. } else if (c == '\r') {
  847. b.append("\\r");
  848. } else if (c == '\f') {
  849. b.append("\\f");
  850. } else if (c == '\b') {
  851. b.append("\\b");
  852. } else if (c == '\t') {
  853. b.append("\\t");
  854. } else {
  855. b.append("\\x");
  856. int x = c / 0x10;
  857. b.append((char)
  858. (x < 0xA ? x + '0' : x - 0xA + 'A'));
  859. x = c & 0xF;
  860. b.append((char)
  861. (x < 0xA ? x + '0' : x - 0xA + 'A'));
  862. }
  863. } else {
  864. b.append(c);
  865. }
  866. i++;
  867. if (i >= ln) {
  868. return b.toString();
  869. }
  870. c = s.charAt(i);
  871. }
  872. } // if has to be escaped
  873. } // for each characters
  874. return s;
  875. }
  876. /**
  877. * Parses a name-value pair list, where the pairs are separated with comma,
  878. * and the name and value is separated with colon.
  879. * The keys and values can contain only letters, digits and <tt>_</tt>. They
  880. * can't be quoted. White-space around the keys and values are ignored. The
  881. * value can be omitted if <code>defaultValue</code> is not null. When a
  882. * value is omitted, then the colon after the key must be omitted as well.
  883. * The same key can't be used for multiple times.
  884. *
  885. * @param s the string to parse.
  886. * For example: <code>"strong:100, soft:900"</code>.
  887. * @param defaultValue the value used when the value is omitted in a
  888. * key-value pair.
  889. *
  890. * @return the map that contains the name-value pairs.
  891. *
  892. * @throws java.text.ParseException if the string is not a valid name-value
  893. * pair list.
  894. */
  895. public static Map<String, String> parseNameValuePairList(String s, String defaultValue)
  896. throws java.text.ParseException {
  897. Map<String, String> map = new HashMap<String, String>();
  898. char c = ' ';
  899. int ln = s.length();
  900. int p = 0;
  901. int keyStart;
  902. int valueStart;
  903. String key;
  904. String value;
  905. fetchLoop: while (true) {
  906. // skip ws
  907. while (p < ln) {
  908. c = s.charAt(p);
  909. if (!Character.isWhitespace(c)) {
  910. break;
  911. }
  912. p++;
  913. }
  914. if (p == ln) {
  915. break fetchLoop;
  916. }
  917. keyStart = p;
  918. // seek key end
  919. while (p < ln) {
  920. c = s.charAt(p);
  921. if (!(Character.isLetterOrDigit(c) || c == '_')) {
  922. break;
  923. }
  924. p++;
  925. }
  926. if (keyStart == p) {
  927. throw new java.text.ParseException(
  928. "Expecting letter, digit or \"_\" "
  929. + "here, (the first character of the key) but found "
  930. + jQuote(String.valueOf(c))
  931. + " at position " + p + ".",
  932. p);
  933. }
  934. key = s.substring(keyStart, p);
  935. // skip ws
  936. while (p < ln) {
  937. c = s.charAt(p);
  938. if (!Character.isWhitespace(c)) {
  939. break;
  940. }
  941. p++;
  942. }
  943. if (p == ln) {
  944. if (defaultValue == null) {
  945. throw new java.text.ParseException(
  946. "Expecting \":\", but reached "
  947. + "the end of the string "
  948. + " at position " + p + ".",
  949. p);
  950. }
  951. value = defaultValue;
  952. } else if (c != ':') {
  953. if (defaultValue == null || c != ',') {
  954. throw new java.text.ParseException(
  955. "Expecting \":\" here, but found "
  956. + jQuote(String.valueOf(c))
  957. + " at position " + p + ".",
  958. p);
  959. }
  960. // skip ","
  961. p++;
  962. value = defaultValue;
  963. } else {
  964. // skip ":"
  965. p++;
  966. // skip ws
  967. while (p < ln) {
  968. c = s.charAt(p);
  969. if (!Character.isWhitespace(c)) {
  970. break;
  971. }
  972. p++;
  973. }
  974. if (p == ln) {
  975. throw new java.text.ParseException(
  976. "Expecting the value of the key "
  977. + "here, but reached the end of the string "
  978. + " at position " + p + ".",
  979. p);
  980. }
  981. valueStart = p;
  982. // seek value end
  983. while (p < ln) {
  984. c = s.charAt(p);
  985. if (!(Character.isLetterOrDigit(c) || c == '_')) {
  986. break;
  987. }
  988. p++;
  989. }
  990. if (valueStart == p) {
  991. throw new java.text.ParseException(
  992. "Expecting letter, digit or \"_\" "
  993. + "here, (the first character of the value) "
  994. + "but found "
  995. + jQuote(String.valueOf(c))
  996. + " at position " + p + ".",
  997. p);
  998. }
  999. value = s.substring(valueStart, p);
  1000. // skip ws
  1001. while (p < ln) {
  1002. c = s.charAt(p);
  1003. if (!Character.isWhitespace(c)) {
  1004. break;
  1005. }
  1006. p++;
  1007. }
  1008. // skip ","
  1009. if (p < ln) {
  1010. if (c != ',') {
  1011. throw new java.text.ParseException(
  1012. "Excpecting \",\" or the end "
  1013. + "of the string here, but found "
  1014. + jQuote(String.valueOf(c))
  1015. + " at position " + p + ".",
  1016. p);
  1017. } else {
  1018. p++;
  1019. }
  1020. }
  1021. }
  1022. // store the key-value pair
  1023. if (map.put(key, value) != null) {
  1024. throw new java.text.ParseException(
  1025. "Dublicated key: "
  1026. + jQuote(key), keyStart);
  1027. }
  1028. }
  1029. return map;
  1030. }
  1031. /**
  1032. * @return whether the name is a valid XML tagname.
  1033. * (This routine might only be 99% accurate. Should maybe REVISIT)
  1034. */
  1035. static public boolean isXMLID(String name) {
  1036. for (int i=0; i<name.length(); i++) {
  1037. char c = name.charAt(i);
  1038. if (i==0) {
  1039. if (c== '-' || c=='.' || Character.isDigit(c))
  1040. return false;
  1041. }
  1042. if (!Character.isLetterOrDigit(c) && c != ':' && c != '_' && c != '-' && c!='.') {
  1043. return false;
  1044. }
  1045. }
  1046. return true;
  1047. }
  1048. /**
  1049. * @return whether the qname matches the combination of nodeName, nsURI, and environment prefix settings.
  1050. */
  1051. static public boolean matchesName(String qname, String nodeName, String nsURI, Environment env) {
  1052. String defaultNS = env.getDefaultNS();
  1053. if ((defaultNS != null) && defaultNS.equals(nsURI)) {
  1054. return qname.equals(nodeName)
  1055. || qname.equals(Template.DEFAULT_NAMESPACE_PREFIX + ":" + nodeName);
  1056. }
  1057. if ("".equals(nsURI)) {
  1058. if (defaultNS != null) {
  1059. return qname.equals(Template.NO_NS_PREFIX + ":" + nodeName);
  1060. } else {
  1061. return qname.equals(nodeName) || qname.equals(Template.NO_NS_PREFIX + ":" + nodeName);
  1062. }
  1063. }
  1064. String prefix = env.getPrefixForNamespace(nsURI);
  1065. if (prefix == null) {
  1066. return false; // Is this the right thing here???
  1067. }
  1068. return qname.equals(prefix + ":" + nodeName);
  1069. }
  1070. /**
  1071. * Pads the string at the left with spaces until it reaches the desired
  1072. * length. If the string is longer than this length, then it returns the
  1073. * unchanged string.
  1074. *
  1075. * @param s the string that will be padded.
  1076. * @param minLength the length to reach.
  1077. */
  1078. public static String leftPad(String s, int minLength) {
  1079. return leftPad(s, minLength, ' ');
  1080. }
  1081. /**
  1082. * Pads the string at the left with the specified character until it reaches
  1083. * the desired length. If the string is longer than this length, then it
  1084. * returns the unchanged string.
  1085. *
  1086. * @param s the string that will be padded.
  1087. * @param minLength the length to reach.
  1088. * @param filling the filling pattern.
  1089. */
  1090. public static String leftPad(String s, int minLength, char filling) {
  1091. int ln = s.length();
  1092. if (minLength <= ln) {
  1093. return s;
  1094. }
  1095. StringBuilder res = new StringBuilder(minLength);
  1096. int dif = minLength - ln;
  1097. for (int i = 0; i < dif; i++) {
  1098. res.append(filling);
  1099. }
  1100. res.append(s);
  1101. return res.toString();
  1102. }
  1103. /**
  1104. * Pads the string at the left with a filling pattern until it reaches the
  1105. * desired length. If the string is longer than this length, then it returns
  1106. * the unchanged string. For example: <code>leftPad('ABC', 9, '1234')</code>
  1107. * returns <code>"123412ABC"</code>.
  1108. *
  1109. * @param s the string that will be padded.
  1110. * @param minLength the length to reach.
  1111. * @param filling the filling pattern. Must be at least 1 characters long.
  1112. * Can't be <code>null</code>.
  1113. */
  1114. public static String leftPad(String s, int minLength, String filling) {
  1115. int ln = s.length();
  1116. if (minLength <= ln) {
  1117. return s;
  1118. }
  1119. StringBuilder res = new StringBuilder(minLength);
  1120. int dif = minLength - ln;
  1121. int fln = filling.length();
  1122. if (fln == 0) {
  1123. throw new IllegalArgumentException(
  1124. "The \"filling\" argument can't be 0 length string.");
  1125. }
  1126. int cnt = dif / fln;
  1127. for (int i = 0; i < cnt; i++) {
  1128. res.append(filling);
  1129. }
  1130. cnt = dif % fln;
  1131. for (int i = 0; i < cnt; i++) {
  1132. res.append(filling.charAt(i));
  1133. }
  1134. res.append(s);
  1135. return res.toString();
  1136. }
  1137. /**
  1138. * Pads the string at the right with spaces until it reaches the desired
  1139. * length. If the string is longer than this length, then it returns the
  1140. * unchanged string.
  1141. *
  1142. * @param s the string that will be padded.
  1143. * @param minLength the length to reach.
  1144. */
  1145. public static String rightPad(String s, int minLength) {
  1146. return rightPad(s, minLength, ' ');
  1147. }
  1148. /**
  1149. * Pads the string at the right with the specified character until it
  1150. * reaches the desired length. If the string is longer than this length,
  1151. * then it returns the unchanged string.
  1152. *
  1153. * @param s the string that will be padded.
  1154. * @param minLength the length to reach.
  1155. * @param filling the filling pattern.
  1156. */
  1157. public static String rightPad(String s, int minLength, char filling) {
  1158. int ln = s.length();
  1159. if (minLength <= ln) {
  1160. return s;
  1161. }
  1162. StringBuilder res = new StringBuilder(minLength);
  1163. res.append(s);
  1164. int dif = minLength - ln;
  1165. for (int i = 0; i < dif; i++) {
  1166. res.append(filling);
  1167. }
  1168. return res.toString();
  1169. }
  1170. /**
  1171. * Pads the string at the right with a filling pattern until it reaches the
  1172. * desired length. If the string is longer than this length, then it returns
  1173. * the unchanged string. For example: <code>rightPad('ABC', 9, '1234')</code>
  1174. * returns <code>"ABC412341"</code>. Note that the filling pattern is
  1175. * started as if you overlay <code>"123412341"</code> with the left-aligned
  1176. * <code>"ABC"</code>, so it starts with <code>"4"</code>.
  1177. *
  1178. * @param s the string that will be padded.
  1179. * @param minLength the length to reach.
  1180. * @param filling the filling pattern. Must be at least 1 characters long.
  1181. * Can't be <code>null</code>.
  1182. */
  1183. public static String rightPad(String s, int minLength, String filling) {
  1184. int ln = s.length();
  1185. if (minLength <= ln) {
  1186. return s;
  1187. }
  1188. StringBuilder res = new StringBuilder(minLength);
  1189. res.append(s);
  1190. int dif = minLength - ln;
  1191. int fln = filling.length();
  1192. if (fln == 0) {
  1193. throw new IllegalArgumentException(
  1194. "The \"filling\" argument can't be 0 length string.");
  1195. }
  1196. int start = ln % fln;
  1197. int end = fln - start <= dif
  1198. ? fln
  1199. : start + dif;
  1200. for (int i = start; i < end; i++) {
  1201. res.append(filling.charAt(i));
  1202. }
  1203. dif -= end - start;
  1204. int cnt = dif / fln;
  1205. for (int i = 0; i < cnt; i++) {
  1206. res.append(filling);
  1207. }
  1208. cnt = dif % fln;
  1209. for (int i = 0; i < cnt; i++) {
  1210. res.append(filling.charAt(i));
  1211. }
  1212. return res.toString();
  1213. }
  1214. /**
  1215. * Is this a valid identifier in FTL?
  1216. */
  1217. public static boolean isFTLIdentifier(String s) {
  1218. char[] chars = s.toCharArray();
  1219. int size = chars.length;
  1220. if (size == 0) {
  1221. return false;
  1222. }
  1223. else {
  1224. char firstChar = chars[0];
  1225. if (firstChar != '_' && firstChar != '@' && !Character.isLetter(firstChar)) return false;
  1226. for (int i=1; i<size; i++) {
  1227. char c = chars[i];
  1228. if (c != '_' && !Character.isLetterOrDigit(c)) return false;
  1229. }
  1230. return true;
  1231. }
  1232. }
  1233. /**
  1234. * Takes a string and puts it in quotes if it cannot
  1235. * be used directly as an identifier in FTL.
  1236. */
  1237. public static String quoteStringIfNecessary(String s) {
  1238. if (isFTLIdentifier(s)) return s;
  1239. return "\"" + FTLStringLiteralEnc(s) + "\"";
  1240. }
  1241. }