in freemarker-core/src/main/java/freemarker/template/utility/StringUtil.java [1427:1548]
public static String jsStringEnc(String s, JsStringEncCompatibility compatibility, JsStringEncQuotation quotation) {
NullArgumentException.check("s", s);
int ln = s.length();
StringBuilder sb;
if (quotation == null) {
sb = null;
} else {
if (quotation == APOSTROPHE && compatibility.jsonCompatible) {
throw new IllegalArgumentException("JSON compatible mode doesn't allow quotationMode=" + quotation);
}
sb = new StringBuilder(ln + 8);
sb.append(quotation.getSymbol());
}
for (int i = 0; i < ln; i++) {
final char c = s.charAt(i);
final int escapeType; //
if (!(c > '>' && c < 0x7F && c != '\\') && c != ' ' && !(c >= 0xA0 && c < 0x2028)) { // skip common chars
if (c <= 0x1F) { // control chars range 1
if (c == '\n') {
escapeType = 'n';
} else if (c == '\r') {
escapeType = 'r';
} else if (c == '\f') {
escapeType = 'f';
} else if (c == '\b') {
escapeType = 'b';
} else if (c == '\t') {
escapeType = 't';
} else {
escapeType = ESC_HEXA;
}
} else if (c == '"') {
escapeType = quotation == APOSTROPHE ? NO_ESC : ESC_BACKSLASH;
} else if (c == '\'') {
escapeType = !compatibility.javaScriptCompatible || quotation == QUOTATION_MARK ? NO_ESC
: (compatibility.jsonCompatible ? ESC_HEXA : ESC_BACKSLASH);
} else if (c == '\\') {
escapeType = ESC_BACKSLASH;
} else if (c == '/'
&& (i == 0 && quotation == null || i != 0 && s.charAt(i - 1) == '<')) {
// against closing elements with "</"
escapeType = ESC_BACKSLASH;
} else if (c == '>') {
// against "]]> and "-->"
final boolean dangerous;
if (quotation != null && i < 2) {
dangerous = false;
} else if (i == 0) {
dangerous = true;
} else {
final char prevC = s.charAt(i - 1);
if (prevC == ']' || prevC == '-') {
if (i == 1) {
dangerous = true;
} else {
final char prevPrevC = s.charAt(i - 2);
dangerous = prevPrevC == prevC;
}
} else {
dangerous = false;
}
}
escapeType = dangerous ? (compatibility.jsonCompatible ? ESC_HEXA : ESC_BACKSLASH) : NO_ESC;
} else if (c == '<') {
// against "<!"
final boolean dangerous;
if (i == ln - 1) {
dangerous = quotation == null;
} else {
char nextC = s.charAt(i + 1);
dangerous = nextC == '!' || nextC == '?';
}
escapeType = dangerous ? ESC_HEXA : NO_ESC;
} else if ((c >= 0x7F && c <= 0x9F) // control chars range 2
|| (c == 0x2028 || c == 0x2029) // UNICODE line terminators
) {
escapeType = ESC_HEXA;
} else {
escapeType = NO_ESC;
}
if (escapeType != NO_ESC) { // If needs escaping
if (sb == null) {
sb = new StringBuilder(ln + 6);
sb.append(s, 0, i);
}
sb.append('\\');
if (escapeType > 0x20) {
sb.append((char) escapeType);
} else if (escapeType == ESC_HEXA) {
if (!compatibility.jsonCompatible && c < 0x100) {
sb.append('x');
sb.append(toHexDigitUpperCase(c >> 4));
sb.append(toHexDigitUpperCase(c & 0xF));
} else {
sb.append('u');
int cp = c;
sb.append(toHexDigitUpperCase((cp >> 12) & 0xF));
sb.append(toHexDigitUpperCase((cp >> 8) & 0xF));
sb.append(toHexDigitUpperCase((cp >> 4) & 0xF));
sb.append(toHexDigitUpperCase(cp & 0xF));
}
} else { // escapeType == ESC_BACKSLASH
sb.append(c);
}
continue;
}
// Falls through when escapeType == NO_ESC
}
// Needs no escaping
if (sb != null) sb.append(c);
} // for each character
if (quotation != null) {
sb.append(quotation.getSymbol());
}
return sb == null ? s : sb.toString();
}