PageRenderTime 62ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/tags/2.3.16/src/freemarker/template/utility/StringUtil.java

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