/*
* Copyright 2011 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.sommeri.sourcemap;
import java.io.IOException;
import java.nio.charset.CharsetEncoder;
/**
* @author johnlenz@google.com (John Lenz)
*/
public class SourceMapUtil {
private static final char[] HEX_CHARS
= { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* Escapes the given string to a double quoted (") JavaScript/JSON string
*/
public static String escapeString(String s) {
return escapeString(s, '"', "\\\"", "\'", "\\\\", null);
}
/** Helper to escape JavaScript string as well as regular expression */
static String escapeString(String s, char quote,
String doublequoteEscape,
String singlequoteEscape,
String backslashEscape,
CharsetEncoder outputCharsetEncoder) {
StringBuilder sb = new StringBuilder(s.length() + 2);
sb.append(quote);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\\': sb.append(backslashEscape); break;
case '\"': sb.append(doublequoteEscape); break;
case '\'': sb.append(singlequoteEscape); break;
case '>': // Break --> into --\> or ]]> into ]]\>
if (i >= 2 &&
((s.charAt(i - 1) == '-' && s.charAt(i - 2) == '-') ||
(s.charAt(i - 1) == ']' && s.charAt(i - 2) == ']'))) {
sb.append("\\>");
} else {
sb.append(c);
}
break;
case '<':
// Break </script into <\/script
final String END_SCRIPT = "/script";
// Break <!-- into <\!--
final String START_COMMENT = "!--";
if (s.regionMatches(true, i + 1, END_SCRIPT, 0,
END_SCRIPT.length())) {
sb.append("<\\");
} else if (s.regionMatches(false, i + 1, START_COMMENT, 0,
START_COMMENT.length())) {
sb.append("<\\");
} else {
sb.append(c);
}
break;
default:
// If we're given an outputCharsetEncoder, then check if the
// character can be represented in this character set.
if (outputCharsetEncoder != null) {
if (outputCharsetEncoder.canEncode(c)) {
sb.append(c);
} else {
// Unicode-escape the character.
appendCharAsHex(sb, c);
}
} else {
// No charsetEncoder provided - pass straight Latin characters
// through, and escape the rest. Doing the explicit character
// check is measurably faster than using the CharsetEncoder.
if (c > 0x1f && c <= 0x7f) {
sb.append(c);
} else {
// Other characters can be misinterpreted by some JS parsers,
// or perhaps mangled by proxies along the way,
// so we play it safe and Unicode escape them.
appendCharAsHex(sb, c);
}
}
}
}
sb.append(quote);
return sb.toString();
}
/**
* @see #appendHexJavaScriptRepresentation(Appendable, int)
*/
@SuppressWarnings("cast")
private static void appendCharAsHex(
StringBuilder sb, char c) {
try {
appendHexJavaScriptRepresentation(sb, (int)c);
} catch (IOException ex) {
// StringBuilder does not throw IOException.
throw new RuntimeException(ex);
}
}
/**
* Returns a JavaScript representation of the character in a hex escaped
* format.
* @param out The buffer to which the hex representation should be appended.
* @param codePoint The code point to append.
*/
private static void appendHexJavaScriptRepresentation(
Appendable out, int codePoint)
throws IOException {
if (Character.isSupplementaryCodePoint(codePoint)) {
// Handle supplementary Unicode values which are not representable in
// JavaScript. We deal with these by escaping them as two 4B sequences
// so that they will round-trip properly when sent from Java to JavaScript
// and back.
char[] surrogates = Character.toChars(codePoint);
appendHexJavaScriptRepresentation(out, surrogates[0]);
appendHexJavaScriptRepresentation(out, surrogates[1]);
return;
}
out.append("\\u")
.append(HEX_CHARS[(codePoint >>> 12) & 0xf])
.append(HEX_CHARS[(codePoint >>> 8) & 0xf])
.append(HEX_CHARS[(codePoint >>> 4) & 0xf])
.append(HEX_CHARS[codePoint & 0xf]);
}
}