/* * 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]); } }