0001: /*
0002: * Copyright (c) 2003 The Visigoth Software Society. All rights
0003: * reserved.
0004: *
0005: * Redistribution and use in source and binary forms, with or without
0006: * modification, are permitted provided that the following conditions
0007: * are met:
0008: *
0009: * 1. Redistributions of source code must retain the above copyright
0010: * notice, this list of conditions and the following disclaimer.
0011: *
0012: * 2. Redistributions in binary form must reproduce the above copyright
0013: * notice, this list of conditions and the following disclaimer in
0014: * the documentation and/or other materials provided with the
0015: * distribution.
0016: *
0017: * 3. The end-user documentation included with the redistribution, if
0018: * any, must include the following acknowledgement:
0019: * "This product includes software developed by the
0020: * Visigoth Software Society (http://www.visigoths.org/)."
0021: * Alternately, this acknowledgement may appear in the software itself,
0022: * if and wherever such third-party acknowledgements normally appear.
0023: *
0024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
0025: * project contributors may be used to endorse or promote products derived
0026: * from this software without prior written permission. For written
0027: * permission, please contact visigoths@visigoths.org.
0028: *
0029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
0030: * nor may "FreeMarker" or "Visigoth" appear in their names
0031: * without prior written permission of the Visigoth Software Society.
0032: *
0033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
0037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0044: * SUCH DAMAGE.
0045: * ====================================================================
0046: *
0047: * This software consists of voluntary contributions made by many
0048: * individuals on behalf of the Visigoth Software Society. For more
0049: * information on the Visigoth Software Society, please see
0050: * http://www.visigoths.org/
0051: */
0052:
0053: package freemarker.template.utility;
0054:
0055: import java.io.UnsupportedEncodingException;
0056: import java.util.*;
0057: import freemarker.template.Template;
0058: import freemarker.core.ParseException;
0059: import freemarker.core.Environment;
0060:
0061: /**
0062: * Some text related utilities.
0063: *
0064: * @version $Id: StringUtil.java,v 1.48 2005/06/01 22:39:08 ddekany Exp $
0065: */
0066: public class StringUtil {
0067: private static final char[] ESCAPES = createEscapes();
0068:
0069: /*
0070: * For better performance most methods are folded down. Don't you scream... :)
0071: */
0072:
0073: /**
0074: * HTML encoding (does not convert line breaks).
0075: * Replaces all '>' '<' '&' and '"' with entity reference
0076: */
0077: public static String HTMLEnc(String s) {
0078: int ln = s.length();
0079: for (int i = 0; i < ln; i++) {
0080: char c = s.charAt(i);
0081: if (c == '<' || c == '>' || c == '&' || c == '"') {
0082: StringBuffer b = new StringBuffer(s.substring(0, i));
0083: switch (c) {
0084: case '<':
0085: b.append("<");
0086: break;
0087: case '>':
0088: b.append(">");
0089: break;
0090: case '&':
0091: b.append("&");
0092: break;
0093: case '"':
0094: b.append(""");
0095: break;
0096: }
0097: i++;
0098: int next = i;
0099: while (i < ln) {
0100: c = s.charAt(i);
0101: if (c == '<' || c == '>' || c == '&' || c == '"') {
0102: b.append(s.substring(next, i));
0103: switch (c) {
0104: case '<':
0105: b.append("<");
0106: break;
0107: case '>':
0108: b.append(">");
0109: break;
0110: case '&':
0111: b.append("&");
0112: break;
0113: case '"':
0114: b.append(""");
0115: break;
0116: }
0117: next = i + 1;
0118: }
0119: i++;
0120: }
0121: if (next < ln)
0122: b.append(s.substring(next));
0123: s = b.toString();
0124: break;
0125: } // if c ==
0126: } // for
0127: return s;
0128: }
0129:
0130: /**
0131: * XML Encoding.
0132: * Replaces all '>' '<' '&', "'" and '"' with entity reference
0133: */
0134: public static String XMLEnc(String s) {
0135: int ln = s.length();
0136: for (int i = 0; i < ln; i++) {
0137: char c = s.charAt(i);
0138: if (c == '<' || c == '>' || c == '&' || c == '"'
0139: || c == '\'') {
0140: StringBuffer b = new StringBuffer(s.substring(0, i));
0141: switch (c) {
0142: case '<':
0143: b.append("<");
0144: break;
0145: case '>':
0146: b.append(">");
0147: break;
0148: case '&':
0149: b.append("&");
0150: break;
0151: case '"':
0152: b.append(""");
0153: break;
0154: case '\'':
0155: b.append("'");
0156: break;
0157: }
0158: i++;
0159: int next = i;
0160: while (i < ln) {
0161: c = s.charAt(i);
0162: if (c == '<' || c == '>' || c == '&' || c == '"'
0163: || c == '\'') {
0164: b.append(s.substring(next, i));
0165: switch (c) {
0166: case '<':
0167: b.append("<");
0168: break;
0169: case '>':
0170: b.append(">");
0171: break;
0172: case '&':
0173: b.append("&");
0174: break;
0175: case '"':
0176: b.append(""");
0177: break;
0178: case '\'':
0179: b.append("'");
0180: break;
0181: }
0182: next = i + 1;
0183: }
0184: i++;
0185: }
0186: if (next < ln)
0187: b.append(s.substring(next));
0188: s = b.toString();
0189: break;
0190: } // if c ==
0191: } // for
0192: return s;
0193: }
0194:
0195: /**
0196: * XML encoding without replacing apostrophes.
0197: * @see #XMLEnc(String)
0198: */
0199: public static String XMLEncNA(String s) {
0200: int ln = s.length();
0201: for (int i = 0; i < ln; i++) {
0202: char c = s.charAt(i);
0203: if (c == '<' || c == '>' || c == '&' || c == '"') {
0204: StringBuffer b = new StringBuffer(s.substring(0, i));
0205: switch (c) {
0206: case '<':
0207: b.append("<");
0208: break;
0209: case '>':
0210: b.append(">");
0211: break;
0212: case '&':
0213: b.append("&");
0214: break;
0215: case '"':
0216: b.append(""");
0217: break;
0218: }
0219: i++;
0220: int next = i;
0221: while (i < ln) {
0222: c = s.charAt(i);
0223: if (c == '<' || c == '>' || c == '&' || c == '"') {
0224: b.append(s.substring(next, i));
0225: switch (c) {
0226: case '<':
0227: b.append("<");
0228: break;
0229: case '>':
0230: b.append(">");
0231: break;
0232: case '&':
0233: b.append("&");
0234: break;
0235: case '"':
0236: b.append(""");
0237: break;
0238: }
0239: next = i + 1;
0240: }
0241: i++;
0242: }
0243: if (next < ln)
0244: b.append(s.substring(next));
0245: s = b.toString();
0246: break;
0247: } // if c ==
0248: } // for
0249: return s;
0250: }
0251:
0252: /**
0253: * XML encoding for attributes valies quoted with <tt>"</tt> (not with <tt>'</tt>!).
0254: * Also can be used for HTML attributes that are quoted with <tt>"</tt>.
0255: * @see #XMLEnc(String)
0256: */
0257: public static String XMLEncQAttr(String s) {
0258: int ln = s.length();
0259: for (int i = 0; i < ln; i++) {
0260: char c = s.charAt(i);
0261: if (c == '<' || c == '&' || c == '"') {
0262: StringBuffer b = new StringBuffer(s.substring(0, i));
0263: switch (c) {
0264: case '<':
0265: b.append("<");
0266: break;
0267: case '&':
0268: b.append("&");
0269: break;
0270: case '"':
0271: b.append(""");
0272: break;
0273: }
0274: i++;
0275: int next = i;
0276: while (i < ln) {
0277: c = s.charAt(i);
0278: if (c == '<' || c == '&' || c == '"') {
0279: b.append(s.substring(next, i));
0280: switch (c) {
0281: case '<':
0282: b.append("<");
0283: break;
0284: case '&':
0285: b.append("&");
0286: break;
0287: case '"':
0288: b.append(""");
0289: break;
0290: }
0291: next = i + 1;
0292: }
0293: i++;
0294: }
0295: if (next < ln) {
0296: b.append(s.substring(next));
0297: }
0298: s = b.toString();
0299: break;
0300: } // if c ==
0301: } // for
0302: return s;
0303: }
0304:
0305: /**
0306: * XML encoding without replacing apostrophes and quotation marks and greater-than signs.
0307: * @see #XMLEnc(String)
0308: */
0309: public static String XMLEncNQG(String s) {
0310: int ln = s.length();
0311: for (int i = 0; i < ln; i++) {
0312: char c = s.charAt(i);
0313: if (c == '<' || c == '&') {
0314: StringBuffer b = new StringBuffer(s.substring(0, i));
0315: switch (c) {
0316: case '<':
0317: b.append("<");
0318: break;
0319: case '&':
0320: b.append("&");
0321: break;
0322: }
0323: i++;
0324: int next = i;
0325: while (i < ln) {
0326: c = s.charAt(i);
0327: if (c == '<' || c == '&') {
0328: b.append(s.substring(next, i));
0329: switch (c) {
0330: case '<':
0331: b.append("<");
0332: break;
0333: case '&':
0334: b.append("&");
0335: break;
0336: }
0337: next = i + 1;
0338: }
0339: i++;
0340: }
0341: if (next < ln)
0342: b.append(s.substring(next));
0343: s = b.toString();
0344: break;
0345: } // if c ==
0346: } // for
0347: return s;
0348: }
0349:
0350: /**
0351: * Rich Text Format encoding (does not replace line breaks).
0352: * Escapes all '\' '{' '}' and '"'
0353: */
0354: public static String RTFEnc(String s) {
0355: int ln = s.length();
0356: for (int i = 0; i < ln; i++) {
0357: char c = s.charAt(i);
0358: if (c == '\\' || c == '{' || c == '}') {
0359: StringBuffer b = new StringBuffer(s.substring(0, i));
0360: switch (c) {
0361: case '\\':
0362: b.append("\\\\");
0363: break;
0364: case '{':
0365: b.append("\\{");
0366: break;
0367: case '}':
0368: b.append("\\}");
0369: break;
0370: }
0371: i++;
0372: int next = i;
0373: while (i < ln) {
0374: c = s.charAt(i);
0375: if (c == '\\' || c == '{' || c == '}') {
0376: b.append(s.substring(next, i));
0377: switch (c) {
0378: case '\\':
0379: b.append("\\\\");
0380: break;
0381: case '{':
0382: b.append("\\{");
0383: break;
0384: case '}':
0385: b.append("\\}");
0386: break;
0387: }
0388: next = i + 1;
0389: }
0390: i++;
0391: }
0392: if (next < ln)
0393: b.append(s.substring(next));
0394: s = b.toString();
0395: break;
0396: } // if c ==
0397: } // for
0398: return s;
0399: }
0400:
0401: /**
0402: * URL encoding (like%20this).
0403: */
0404: public static String URLEnc(String s, String charset)
0405: throws UnsupportedEncodingException {
0406: int ln = s.length();
0407: int i;
0408: for (i = 0; i < ln; i++) {
0409: char c = s.charAt(i);
0410: if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
0411: || c >= '0' && c <= '9' || c == '_' || c == '-'
0412: || c == '.' || c == '!' || c == '~' || c >= '\''
0413: && c <= '*')) {
0414: break;
0415: }
0416: }
0417: if (i == ln) {
0418: // Nothing to escape
0419: return s;
0420: }
0421:
0422: StringBuffer b = new StringBuffer(ln + ln / 3 + 2);
0423: b.append(s.substring(0, i));
0424:
0425: int encstart = i;
0426: for (i++; i < ln; i++) {
0427: char c = s.charAt(i);
0428: if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
0429: || c >= '0' && c <= '9' || c == '_' || c == '-'
0430: || c == '.' || c == '!' || c == '~' || c >= '\''
0431: && c <= '*') {
0432: if (encstart != -1) {
0433: byte[] o = s.substring(encstart, i).getBytes(
0434: charset);
0435: for (int j = 0; j < o.length; j++) {
0436: b.append('%');
0437: byte bc = o[j];
0438: int c1 = bc & 0x0F;
0439: int c2 = (bc >> 4) & 0x0F;
0440: b.append((char) (c2 < 10 ? c2 + '0'
0441: : c2 - 10 + 'A'));
0442: b.append((char) (c1 < 10 ? c1 + '0'
0443: : c1 - 10 + 'A'));
0444: }
0445: encstart = -1;
0446: }
0447: b.append(c);
0448: } else {
0449: if (encstart == -1) {
0450: encstart = i;
0451: }
0452: }
0453: }
0454: if (encstart != -1) {
0455: byte[] o = s.substring(encstart, i).getBytes(charset);
0456: for (int j = 0; j < o.length; j++) {
0457: b.append('%');
0458: byte bc = o[j];
0459: int c1 = bc & 0x0F;
0460: int c2 = (bc >> 4) & 0x0F;
0461: b.append((char) (c2 < 10 ? c2 + '0' : c2 - 10 + 'A'));
0462: b.append((char) (c1 < 10 ? c1 + '0' : c1 - 10 + 'A'));
0463: }
0464: }
0465:
0466: return b.toString();
0467: }
0468:
0469: private static char[] createEscapes() {
0470: char[] escapes = new char['\\' + 1];
0471: for (int i = 0; i < 32; ++i) {
0472: escapes[i] = 1;
0473: }
0474: escapes['\\'] = '\\';
0475: escapes['\''] = '\'';
0476: escapes['"'] = '"';
0477: escapes['<'] = 'l';
0478: escapes['>'] = 'g';
0479: escapes['&'] = 'a';
0480: escapes['\b'] = 'b';
0481: escapes['\t'] = 't';
0482: escapes['\n'] = 'n';
0483: escapes['\f'] = 'f';
0484: escapes['\r'] = 'r';
0485: escapes['$'] = '$';
0486: return escapes;
0487: }
0488:
0489: public static String FTLStringLiteralEnc(String s) {
0490: StringBuffer buf = null;
0491: int l = s.length();
0492: int el = ESCAPES.length;
0493: for (int i = 0; i < l; i++) {
0494: char c = s.charAt(i);
0495: if (c < el) {
0496: char escape = ESCAPES[c];
0497: switch (escape) {
0498: case 0: {
0499: if (buf != null) {
0500: buf.append(c);
0501: }
0502: break;
0503: }
0504: case 1: {
0505: if (buf == null) {
0506: buf = new StringBuffer(s.length() + 3);
0507: buf.append(s.substring(0, i));
0508: }
0509: // hex encoding for characters below 0x20
0510: // that have no other escape representation
0511: buf.append("\\x00");
0512: int c2 = (c >> 4) & 0x0F;
0513: c = (char) (c & 0x0F);
0514: buf.append((char) (c2 < 10 ? c2 + '0'
0515: : c2 - 10 + 'A'));
0516: buf
0517: .append((char) (c < 10 ? c + '0'
0518: : c - 10 + 'A'));
0519: break;
0520: }
0521: default: {
0522: if (buf == null) {
0523: buf = new StringBuffer(s.length() + 2);
0524: buf.append(s.substring(0, i));
0525: }
0526: buf.append('\\');
0527: buf.append(escape);
0528: }
0529: }
0530: } else {
0531: if (buf != null) {
0532: buf.append(c);
0533: }
0534: }
0535: }
0536: return buf == null ? s : buf.toString();
0537: }
0538:
0539: /**
0540: * FTL string literal decoding.
0541: *
0542: * \\, \", \', \n, \t, \r, \b and \f will be replaced according to
0543: * Java rules. In additional, it knows \g, \l, \a and \{ which are
0544: * replaced with <, >, & and { respectively.
0545: * \x works as hexadecimal character code escape. The character
0546: * codes are interpreted according to UCS basic plane (Unicode).
0547: * "f\x006Fo", "f\x06Fo" and "f\x6Fo" will be "foo".
0548: * "f\x006F123" will be "foo123" as the maximum number of digits is 4.
0549: *
0550: * All other \X (where X is any character not mentioned above or End-of-string)
0551: * will cause a ParseException.
0552: *
0553: * @param s String literal <em>without</em> the surrounding quotation marks
0554: * @return String with all escape sequences resolved
0555: * @throws ParseException if there string contains illegal escapes
0556: */
0557: public static String FTLStringLiteralDec(String s)
0558: throws ParseException {
0559:
0560: int idx = s.indexOf('\\');
0561: if (idx == -1) {
0562: return s;
0563: }
0564:
0565: int lidx = s.length() - 1;
0566: int bidx = 0;
0567: StringBuffer buf = new StringBuffer(lidx);
0568: do {
0569: buf.append(s.substring(bidx, idx));
0570: if (idx >= lidx) {
0571: throw new ParseException(
0572: "The last character of string literal is backslash",
0573: 0, 0);
0574: }
0575: char c = s.charAt(idx + 1);
0576: switch (c) {
0577: case '"':
0578: buf.append('"');
0579: bidx = idx + 2;
0580: break;
0581: case '\'':
0582: buf.append('\'');
0583: bidx = idx + 2;
0584: break;
0585: case '\\':
0586: buf.append('\\');
0587: bidx = idx + 2;
0588: break;
0589: case 'n':
0590: buf.append('\n');
0591: bidx = idx + 2;
0592: break;
0593: case 'r':
0594: buf.append('\r');
0595: bidx = idx + 2;
0596: break;
0597: case 't':
0598: buf.append('\t');
0599: bidx = idx + 2;
0600: break;
0601: case 'f':
0602: buf.append('\f');
0603: bidx = idx + 2;
0604: break;
0605: case 'b':
0606: buf.append('\b');
0607: bidx = idx + 2;
0608: break;
0609: case 'g':
0610: buf.append('>');
0611: bidx = idx + 2;
0612: break;
0613: case 'l':
0614: buf.append('<');
0615: bidx = idx + 2;
0616: break;
0617: case 'a':
0618: buf.append('&');
0619: bidx = idx + 2;
0620: break;
0621: case '{':
0622: buf.append('{');
0623: bidx = idx + 2;
0624: break;
0625: case 'x': {
0626: idx += 2;
0627: int x = idx;
0628: int y = 0;
0629: int z = lidx > idx + 3 ? idx + 3 : lidx;
0630: while (idx <= z) {
0631: char b = s.charAt(idx);
0632: if (b >= '0' && b <= '9') {
0633: y <<= 4;
0634: y += b - '0';
0635: } else if (b >= 'a' && b <= 'f') {
0636: y <<= 4;
0637: y += b - 'a' + 10;
0638: } else if (b >= 'A' && b <= 'F') {
0639: y <<= 4;
0640: y += b - 'A' + 10;
0641: } else {
0642: break;
0643: }
0644: idx++;
0645: }
0646: if (x < idx) {
0647: buf.append((char) y);
0648: } else {
0649: throw new ParseException(
0650: "Invalid \\x escape in a string literal",
0651: 0, 0);
0652: }
0653: bidx = idx;
0654: break;
0655: }
0656: default:
0657: throw new ParseException("Invalid escape sequence (\\"
0658: + c + ") in a string literal", 0, 0);
0659: }
0660: idx = s.indexOf('\\', bidx);
0661: } while (idx != -1);
0662: buf.append(s.substring(bidx));
0663:
0664: return buf.toString();
0665: }
0666:
0667: public static Locale deduceLocale(String input) {
0668: Locale locale = Locale.getDefault();
0669: if (input.charAt(0) == '"')
0670: input = input.substring(1, input.length() - 1);
0671: StringTokenizer st = new StringTokenizer(input, ",_ ");
0672: String lang = "", country = "";
0673: if (st.hasMoreTokens()) {
0674: lang = st.nextToken();
0675: }
0676: if (st.hasMoreTokens()) {
0677: country = st.nextToken();
0678: }
0679: if (!st.hasMoreTokens()) {
0680: locale = new Locale(lang, country);
0681: } else {
0682: locale = new Locale(lang, country, st.nextToken());
0683: }
0684: return locale;
0685: }
0686:
0687: public static String capitalize(String s) {
0688: StringTokenizer st = new StringTokenizer(s, " \t\r\n", true);
0689: StringBuffer buf = new StringBuffer(s.length());
0690: while (st.hasMoreTokens()) {
0691: String tok = st.nextToken();
0692: buf.append(tok.substring(0, 1).toUpperCase());
0693: buf.append(tok.substring(1).toLowerCase());
0694: }
0695: return buf.toString();
0696: }
0697:
0698: public static boolean getYesNo(String s) {
0699: if (s.startsWith("\"")) {
0700: s = s.substring(1, s.length() - 1);
0701:
0702: }
0703: if (s.equalsIgnoreCase("n") || s.equalsIgnoreCase("no")
0704: || s.equalsIgnoreCase("f")
0705: || s.equalsIgnoreCase("false")) {
0706: return false;
0707: } else if (s.equalsIgnoreCase("y") || s.equalsIgnoreCase("yes")
0708: || s.equalsIgnoreCase("t")
0709: || s.equalsIgnoreCase("true")) {
0710: return true;
0711: }
0712: throw new IllegalArgumentException("Illegal boolean value: "
0713: + s);
0714: }
0715:
0716: /**
0717: * Splits a string at the specified character.
0718: */
0719: public static String[] split(String s, char c) {
0720: int i, b, e;
0721: int cnt;
0722: String res[];
0723: int ln = s.length();
0724:
0725: i = 0;
0726: cnt = 1;
0727: while ((i = s.indexOf(c, i)) != -1) {
0728: cnt++;
0729: i++;
0730: }
0731: res = new String[cnt];
0732:
0733: i = 0;
0734: b = 0;
0735: while (b <= ln) {
0736: e = s.indexOf(c, b);
0737: if (e == -1)
0738: e = ln;
0739: res[i++] = s.substring(b, e);
0740: b = e + 1;
0741: }
0742: return res;
0743: }
0744:
0745: /**
0746: * Splits a string at the specified string.
0747: */
0748: public static String[] split(String s, String sep,
0749: boolean caseInsensitive) {
0750: String splitString = caseInsensitive ? sep.toLowerCase() : sep;
0751: String input = caseInsensitive ? s.toLowerCase() : s;
0752: int i, b, e;
0753: int cnt;
0754: String res[];
0755: int ln = s.length();
0756: int sln = sep.length();
0757:
0758: if (sln == 0)
0759: throw new IllegalArgumentException(
0760: "The separator string has 0 length");
0761:
0762: i = 0;
0763: cnt = 1;
0764: while ((i = input.indexOf(splitString, i)) != -1) {
0765: cnt++;
0766: i += sln;
0767: }
0768: res = new String[cnt];
0769:
0770: i = 0;
0771: b = 0;
0772: while (b <= ln) {
0773: e = input.indexOf(splitString, b);
0774: if (e == -1)
0775: e = ln;
0776: res[i++] = s.substring(b, e);
0777: b = e + sln;
0778: }
0779: return res;
0780: }
0781:
0782: /**
0783: * Replaces all occurrences of a sub-string in a string.
0784: * @param text The string where it will replace <code>oldsub</code> with
0785: * <code>newsub</code>.
0786: * @return String The string after the replacements.
0787: */
0788: public static String replace(String text, String oldsub,
0789: String newsub, boolean caseInsensitive, boolean firstOnly) {
0790: StringBuffer buf;
0791: int tln;
0792: int oln = oldsub.length();
0793:
0794: if (oln == 0) {
0795: int nln = newsub.length();
0796: if (nln == 0) {
0797: return text;
0798: } else {
0799: if (firstOnly) {
0800: return newsub + text;
0801: } else {
0802: tln = text.length();
0803: buf = new StringBuffer(tln + (tln + 1) * nln);
0804: buf.append(newsub);
0805: for (int i = 0; i < tln; i++) {
0806: buf.append(text.charAt(i));
0807: buf.append(newsub);
0808: }
0809: return buf.toString();
0810: }
0811: }
0812: } else {
0813: oldsub = caseInsensitive ? oldsub.toLowerCase() : oldsub;
0814: String input = caseInsensitive ? text.toLowerCase() : text;
0815: int e = input.indexOf(oldsub);
0816: if (e == -1) {
0817: return text;
0818: }
0819: int b = 0;
0820: tln = text.length();
0821: buf = new StringBuffer(tln
0822: + Math.max(newsub.length() - oln, 0) * 3);
0823: do {
0824: buf.append(text.substring(b, e));
0825: buf.append(newsub);
0826: b = e + oln;
0827: e = input.indexOf(oldsub, b);
0828: } while (e != -1 && !firstOnly);
0829: buf.append(text.substring(b));
0830: return buf.toString();
0831: }
0832: }
0833:
0834: /**
0835: * Removes the line-break from the end of the string.
0836: */
0837: public static String chomp(String s) {
0838: if (s.endsWith("\r\n"))
0839: return s.substring(0, s.length() - 2);
0840: if (s.endsWith("\r") || s.endsWith("\n"))
0841: return s.substring(0, s.length() - 1);
0842: return s;
0843: }
0844:
0845: /**
0846: * Quotes string as Java Language string literal.
0847: * Returns string <code>"null"</code> if <code>s</code>
0848: * is <code>null</code>.
0849: */
0850: public static String jQuote(String s) {
0851: if (s == null) {
0852: return "null";
0853: }
0854: int ln = s.length();
0855: StringBuffer b = new StringBuffer(ln + 4);
0856: b.append('"');
0857: for (int i = 0; i < ln; i++) {
0858: char c = s.charAt(i);
0859: if (c == '"') {
0860: b.append("\\\"");
0861: } else if (c == '\\') {
0862: b.append("\\\\");
0863: } else if (c < 0x20) {
0864: if (c == '\n') {
0865: b.append("\\n");
0866: } else if (c == '\r') {
0867: b.append("\\r");
0868: } else if (c == '\f') {
0869: b.append("\\f");
0870: } else if (c == '\b') {
0871: b.append("\\b");
0872: } else if (c == '\t') {
0873: b.append("\\t");
0874: } else {
0875: b.append("\\u00");
0876: int x = c / 0x10;
0877: b
0878: .append((char) (x < 0xA ? x + '0'
0879: : x - 0xA + 'A'));
0880: x = c & 0xF;
0881: b
0882: .append((char) (x < 0xA ? x + '0'
0883: : x - 0xA + 'A'));
0884: }
0885: } else {
0886: b.append(c);
0887: }
0888: } // for each characters
0889: b.append('"');
0890: return b.toString();
0891: }
0892:
0893: /**
0894: * Escapes the <code>String</code> with the escaping rules of Java language
0895: * string literals, so it is safe to insert the value into a string literal.
0896: * The resulting string will not be quoted.
0897: *
0898: * <p>In additional, all characters under UCS code point 0x20, that has no
0899: * dedicated escape sequence in Java language, will be replaced with UNICODE
0900: * escape (<tt>\<!-- -->u<i>XXXX</i></tt>).
0901: *
0902: * @see #jQuote(String)
0903: */
0904: public static String javaStringEnc(String s) {
0905: int ln = s.length();
0906: for (int i = 0; i < ln; i++) {
0907: char c = s.charAt(i);
0908: if (c == '"' || c == '\\' || c < 0x20) {
0909: StringBuffer b = new StringBuffer(ln + 4);
0910: b.append(s.substring(0, i));
0911: while (true) {
0912: if (c == '"') {
0913: b.append("\\\"");
0914: } else if (c == '\\') {
0915: b.append("\\\\");
0916: } else if (c < 0x20) {
0917: if (c == '\n') {
0918: b.append("\\n");
0919: } else if (c == '\r') {
0920: b.append("\\r");
0921: } else if (c == '\f') {
0922: b.append("\\f");
0923: } else if (c == '\b') {
0924: b.append("\\b");
0925: } else if (c == '\t') {
0926: b.append("\\t");
0927: } else {
0928: b.append("\\u00");
0929: int x = c / 0x10;
0930: b.append((char) (x < 0xA ? x + '0'
0931: : x - 0xA + 'a'));
0932: x = c & 0xF;
0933: b.append((char) (x < 0xA ? x + '0'
0934: : x - 0xA + 'a'));
0935: }
0936: } else {
0937: b.append(c);
0938: }
0939: i++;
0940: if (i >= ln) {
0941: return b.toString();
0942: }
0943: c = s.charAt(i);
0944: }
0945: } // if has to be escaped
0946: } // for each characters
0947: return s;
0948: }
0949:
0950: /**
0951: * Escapes a <code>String</code> according the JavaScript string literal
0952: * escaping rules. The resulting string will not be quoted.
0953: *
0954: * <p>It escapes both <tt>'</tt> and <tt>"</tt>.
0955: * In additional it escapes <tt>></tt> as <tt>\></tt> (to avoid
0956: * <tt></script></tt>). Furthermore, all characters under UCS code point
0957: * 0x20, that has no dedicated escape sequence in JavaScript language, will
0958: * be replaced with hexadecimal escape (<tt>\x<i>XX</i></tt>).
0959: */
0960: public static String javaScriptStringEnc(String s) {
0961: int ln = s.length();
0962: for (int i = 0; i < ln; i++) {
0963: char c = s.charAt(i);
0964: if (c == '"' || c == '\'' || c == '\\' || c == '>'
0965: || c < 0x20) {
0966: StringBuffer b = new StringBuffer(ln + 4);
0967: b.append(s.substring(0, i));
0968: while (true) {
0969: if (c == '"') {
0970: b.append("\\\"");
0971: } else if (c == '\'') {
0972: b.append("\\'");
0973: } else if (c == '\\') {
0974: b.append("\\\\");
0975: } else if (c == '>') {
0976: b.append("\\>");
0977: } else if (c < 0x20) {
0978: if (c == '\n') {
0979: b.append("\\n");
0980: } else if (c == '\r') {
0981: b.append("\\r");
0982: } else if (c == '\f') {
0983: b.append("\\f");
0984: } else if (c == '\b') {
0985: b.append("\\b");
0986: } else if (c == '\t') {
0987: b.append("\\t");
0988: } else {
0989: b.append("\\x");
0990: int x = c / 0x10;
0991: b.append((char) (x < 0xA ? x + '0'
0992: : x - 0xA + 'A'));
0993: x = c & 0xF;
0994: b.append((char) (x < 0xA ? x + '0'
0995: : x - 0xA + 'A'));
0996: }
0997: } else {
0998: b.append(c);
0999: }
1000: i++;
1001: if (i >= ln) {
1002: return b.toString();
1003: }
1004: c = s.charAt(i);
1005: }
1006: } // if has to be escaped
1007: } // for each characters
1008: return s;
1009: }
1010:
1011: /**
1012: * Parses a name-value pair list, where the pairs are separated with comma,
1013: * and the name and value is separated with colon.
1014: * The keys and values can contain only letters, digits and <tt>_</tt>. They
1015: * can't be quoted. White-space around the keys and values are ignored. The
1016: * value can be omitted if <code>defaultValue</code> is not null. When a
1017: * value is omitted, then the colon after the key must be omitted as well.
1018: * The same key can't be used for multiple times.
1019: *
1020: * @param s the string to parse.
1021: * For example: <code>"strong:100, soft:900"</code>.
1022: * @param defaultValue the value used when the value is omitted in a
1023: * key-value pair.
1024: *
1025: * @return the map that contains the name-value pairs.
1026: *
1027: * @throws java.text.ParseException if the string is not a valid name-value
1028: * pair list.
1029: */
1030: public static Map parseNameValuePairList(String s,
1031: String defaultValue) throws java.text.ParseException {
1032: Map map = new HashMap();
1033:
1034: char c = ' ';
1035: int ln = s.length();
1036: int p = 0;
1037: int keyStart;
1038: int valueStart;
1039: String key;
1040: String value;
1041:
1042: fetchLoop: while (true) {
1043: // skip ws
1044: while (p < ln) {
1045: c = s.charAt(p);
1046: if (!Character.isWhitespace(c)) {
1047: break;
1048: }
1049: p++;
1050: }
1051: if (p == ln) {
1052: break fetchLoop;
1053: }
1054: keyStart = p;
1055:
1056: // seek key end
1057: while (p < ln) {
1058: c = s.charAt(p);
1059: if (!(Character.isLetterOrDigit(c) || c == '_')) {
1060: break;
1061: }
1062: p++;
1063: }
1064: if (keyStart == p) {
1065: throw new java.text.ParseException(
1066: "Expecting letter, digit or \"_\" "
1067: + "here, (the first character of the key) but found "
1068: + jQuote(String.valueOf(c))
1069: + " at position " + p + ".", p);
1070: }
1071: key = s.substring(keyStart, p);
1072:
1073: // skip ws
1074: while (p < ln) {
1075: c = s.charAt(p);
1076: if (!Character.isWhitespace(c)) {
1077: break;
1078: }
1079: p++;
1080: }
1081: if (p == ln) {
1082: if (defaultValue == null) {
1083: throw new java.text.ParseException(
1084: "Expecting \":\", but reached "
1085: + "the end of the string "
1086: + " at position " + p + ".", p);
1087: }
1088: value = defaultValue;
1089: } else if (c != ':') {
1090: if (defaultValue == null || c != ',') {
1091: throw new java.text.ParseException(
1092: "Expecting \":\" here, but found "
1093: + jQuote(String.valueOf(c))
1094: + " at position " + p + ".", p);
1095: }
1096:
1097: // skip ","
1098: p++;
1099:
1100: value = defaultValue;
1101: } else {
1102: // skip ":"
1103: p++;
1104:
1105: // skip ws
1106: while (p < ln) {
1107: c = s.charAt(p);
1108: if (!Character.isWhitespace(c)) {
1109: break;
1110: }
1111: p++;
1112: }
1113: if (p == ln) {
1114: throw new java.text.ParseException(
1115: "Expecting the value of the key "
1116: + "here, but reached the end of the string "
1117: + " at position " + p + ".", p);
1118: }
1119: valueStart = p;
1120:
1121: // seek value end
1122: while (p < ln) {
1123: c = s.charAt(p);
1124: if (!(Character.isLetterOrDigit(c) || c == '_')) {
1125: break;
1126: }
1127: p++;
1128: }
1129: if (valueStart == p) {
1130: throw new java.text.ParseException(
1131: "Expecting letter, digit or \"_\" "
1132: + "here, (the first character of the value) "
1133: + "but found "
1134: + jQuote(String.valueOf(c))
1135: + " at position " + p + ".", p);
1136: }
1137: value = s.substring(valueStart, p);
1138:
1139: // skip ws
1140: while (p < ln) {
1141: c = s.charAt(p);
1142: if (!Character.isWhitespace(c)) {
1143: break;
1144: }
1145: p++;
1146: }
1147:
1148: // skip ","
1149: if (p < ln) {
1150: if (c != ',') {
1151: throw new java.text.ParseException(
1152: "Excpecting \",\" or the end "
1153: + "of the string here, but found "
1154: + jQuote(String.valueOf(c))
1155: + " at position " + p + ".", p);
1156: } else {
1157: p++;
1158: }
1159: }
1160: }
1161:
1162: // store the key-value pair
1163: if (map.put(key, value) != null) {
1164: throw new java.text.ParseException("Dublicated key: "
1165: + jQuote(key), keyStart);
1166: }
1167: }
1168:
1169: return map;
1170: }
1171:
1172: /**
1173: * @return whether the name is a valid XML tagname.
1174: * (This routine might only be 99% accurate. Should maybe REVISIT)
1175: */
1176: static public boolean isXMLID(String name) {
1177: for (int i = 0; i < name.length(); i++) {
1178: char c = name.charAt(i);
1179: if (i == 0) {
1180: if (c == '-' || c == '.' || Character.isDigit(c))
1181: return false;
1182: }
1183: if (!Character.isLetterOrDigit(c) && c != ':' && c != '_'
1184: && c != '-' && c != '.') {
1185: return false;
1186: }
1187: }
1188: return true;
1189: }
1190:
1191: /**
1192: * @return whether the qname matches the combination of nodeName, nsURI, and environment prefix settings.
1193: */
1194:
1195: static public boolean matchesName(String qname, String nodeName,
1196: String nsURI, Environment env) {
1197: String defaultNS = env.getDefaultNS();
1198: if ((defaultNS != null) && defaultNS.equals(nsURI)) {
1199: return qname.equals(nodeName)
1200: || qname.equals(Template.DEFAULT_NAMESPACE_PREFIX
1201: + ":" + nodeName);
1202: }
1203: if ("".equals(nsURI)) {
1204: if (defaultNS != null) {
1205: return qname.equals(Template.NO_NS_PREFIX + ":"
1206: + nodeName);
1207: } else {
1208: return qname.equals(nodeName)
1209: || qname.equals(Template.NO_NS_PREFIX + ":"
1210: + nodeName);
1211: }
1212: }
1213: String prefix = env.getPrefixForNamespace(nsURI);
1214: if (prefix == null) {
1215: return false; // Is this the right thing here???
1216: }
1217: return qname.equals(prefix + ":" + nodeName);
1218: }
1219:
1220: /**
1221: * Pads the string at the left with spaces until it reaches the desired
1222: * length. If the string is longer than this length, then it returns the
1223: * unchanged string.
1224: *
1225: * @param s the string that will be padded.
1226: * @param minLength the length to reach.
1227: */
1228: public static String leftPad(String s, int minLength) {
1229: return leftPad(s, minLength, ' ');
1230: }
1231:
1232: /**
1233: * Pads the string at the left with the specified character until it reaches
1234: * the desired length. If the string is longer than this length, then it
1235: * returns the unchanged string.
1236: *
1237: * @param s the string that will be padded.
1238: * @param minLength the length to reach.
1239: * @param filling the filling pattern.
1240: */
1241: public static String leftPad(String s, int minLength, char filling) {
1242: int ln = s.length();
1243: if (minLength <= ln) {
1244: return s;
1245: }
1246:
1247: StringBuffer res = new StringBuffer(minLength);
1248:
1249: int dif = minLength - ln;
1250: for (int i = 0; i < dif; i++) {
1251: res.append(filling);
1252: }
1253:
1254: res.append(s);
1255:
1256: return res.toString();
1257: }
1258:
1259: /**
1260: * Pads the string at the left with a filling pattern until it reaches the
1261: * desired length. If the string is longer than this length, then it returns
1262: * the unchanged string. For example: <code>leftPad('ABC', 9, '1234')</code>
1263: * returns <code>"123412ABC"</code>.
1264: *
1265: * @param s the string that will be padded.
1266: * @param minLength the length to reach.
1267: * @param filling the filling pattern. Must be at least 1 characters long.
1268: * Can't be <code>null</code>.
1269: */
1270: public static String leftPad(String s, int minLength, String filling) {
1271: int ln = s.length();
1272: if (minLength <= ln) {
1273: return s;
1274: }
1275:
1276: StringBuffer res = new StringBuffer(minLength);
1277:
1278: int dif = minLength - ln;
1279: int fln = filling.length();
1280: if (fln == 0) {
1281: throw new IllegalArgumentException(
1282: "The \"filling\" argument can't be 0 length string.");
1283: }
1284: int cnt = dif / fln;
1285: for (int i = 0; i < cnt; i++) {
1286: res.append(filling);
1287: }
1288: cnt = dif % fln;
1289: for (int i = 0; i < cnt; i++) {
1290: res.append(filling.charAt(i));
1291: }
1292:
1293: res.append(s);
1294:
1295: return res.toString();
1296: }
1297:
1298: /**
1299: * Pads the string at the right with spaces until it reaches the desired
1300: * length. If the string is longer than this length, then it returns the
1301: * unchanged string.
1302: *
1303: * @param s the string that will be padded.
1304: * @param minLength the length to reach.
1305: */
1306: public static String rightPad(String s, int minLength) {
1307: return rightPad(s, minLength, ' ');
1308: }
1309:
1310: /**
1311: * Pads the string at the right with the specified character until it
1312: * reaches the desired length. If the string is longer than this length,
1313: * then it returns the unchanged string.
1314: *
1315: * @param s the string that will be padded.
1316: * @param minLength the length to reach.
1317: * @param filling the filling pattern.
1318: */
1319: public static String rightPad(String s, int minLength, char filling) {
1320: int ln = s.length();
1321: if (minLength <= ln) {
1322: return s;
1323: }
1324:
1325: StringBuffer res = new StringBuffer(minLength);
1326:
1327: res.append(s);
1328:
1329: int dif = minLength - ln;
1330: for (int i = 0; i < dif; i++) {
1331: res.append(filling);
1332: }
1333:
1334: return res.toString();
1335: }
1336:
1337: /**
1338: * Pads the string at the right with a filling pattern until it reaches the
1339: * desired length. If the string is longer than this length, then it returns
1340: * the unchanged string. For example: <code>rightPad('ABC', 9, '1234')</code>
1341: * returns <code>"ABC412341"</code>. Note that the filling pattern is
1342: * started as if you overlay <code>"123412341"</code> with the left-aligned
1343: * <code>"ABC"</code>, so it starts with <code>"4"</code>.
1344: *
1345: * @param s the string that will be padded.
1346: * @param minLength the length to reach.
1347: * @param filling the filling pattern. Must be at least 1 characters long.
1348: * Can't be <code>null</code>.
1349: */
1350: public static String rightPad(String s, int minLength,
1351: String filling) {
1352: int ln = s.length();
1353: if (minLength <= ln) {
1354: return s;
1355: }
1356:
1357: StringBuffer res = new StringBuffer(minLength);
1358:
1359: res.append(s);
1360:
1361: int dif = minLength - ln;
1362: int fln = filling.length();
1363: if (fln == 0) {
1364: throw new IllegalArgumentException(
1365: "The \"filling\" argument can't be 0 length string.");
1366: }
1367: int start = ln % fln;
1368: int end = fln - start <= dif ? fln : start + dif;
1369: for (int i = start; i < end; i++) {
1370: res.append(filling.charAt(i));
1371: }
1372: dif -= end - start;
1373: int cnt = dif / fln;
1374: for (int i = 0; i < cnt; i++) {
1375: res.append(filling);
1376: }
1377: cnt = dif % fln;
1378: for (int i = 0; i < cnt; i++) {
1379: res.append(filling.charAt(i));
1380: }
1381:
1382: return res.toString();
1383: }
1384:
1385: }
|