All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.html.js.JSMarshaller Maven / Gradle / Ivy

/**
 * Copyright (C) 2014-2016 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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.helger.html.js;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import com.helger.commons.collection.ArrayHelper;
import com.helger.commons.collection.ext.CommonsHashSet;
import com.helger.commons.collection.ext.ICommonsSet;
import com.helger.commons.string.StringHelper;

/**
 * JavaScript String helper
 *
 * @author Philip Helger
 */
@Immutable
public final class JSMarshaller
{
  private static final char [] CHARS_TO_MASK = new char [] { '"', '\'', '\\', '/', '\t', '\r', '\n', '\f' };
  private static final char [] CHARS_TO_MASK_REGEX = new char [] { '\\',
                                                                   '^',
                                                                   '$',
                                                                   '*',
                                                                   '+',
                                                                   '?',
                                                                   '|',
                                                                   '.',
                                                                   '-',
                                                                   '[',
                                                                   ']',
                                                                   '(',
                                                                   ')',
                                                                   '{',
                                                                   '}' };
  private static final char MASK_CHAR = '\\';
  private static final char MASK_CHAR_REGEX = '\\';

  /**
   * All reserved keywords of JS. see here
* technically the last few are not reserved words but they cannot be used as * identifiers. */ private static final ICommonsSet RESERVED_KEYWORDS = new CommonsHashSet <> ("break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else", "finally", "for", "function", "if", "in", "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "class", "enum", "export", "extends", "import", "super", // non-reserved "true", "false", "null", "undefined"); private JSMarshaller () {} /** * Turn special characters into escaped characters conforming to JavaScript. * Handles complete character set defined in HTML 4.01 recommendation.
* Reference: Core JavaScript 1.5 Guide * * @param sInput * the input string * @return the escaped string */ @Nullable public static String javaScriptEscape (@Nullable final String sInput) { if (StringHelper.hasNoText (sInput)) return sInput; final char [] aInput = sInput.toCharArray (); if (!StringHelper.containsAny (aInput, CHARS_TO_MASK)) return sInput; final char [] ret = new char [aInput.length * 2]; int nIndex = 0; char cPrevChar = '\u0000'; for (final char cCurrent : aInput) { switch (cCurrent) { case '"': case '\'': case '\\': case '/': ret[nIndex++] = MASK_CHAR; ret[nIndex++] = cCurrent; break; case '\t': ret[nIndex++] = MASK_CHAR; ret[nIndex++] = 't'; break; case '\n': if (cPrevChar != '\r') { ret[nIndex++] = MASK_CHAR; ret[nIndex++] = 'n'; } break; case '\r': ret[nIndex++] = MASK_CHAR; ret[nIndex++] = 'n'; break; case '\f': ret[nIndex++] = MASK_CHAR; ret[nIndex++] = 'f'; break; default: ret[nIndex++] = cCurrent; break; } cPrevChar = cCurrent; } return new String (ret, 0, nIndex); } @Nonnull public static String javaScriptEscapeForRegEx (final char cInput) { if (ArrayHelper.contains (CHARS_TO_MASK_REGEX, cInput)) return new StringBuilder (2).append (MASK_CHAR_REGEX).append (cInput).toString (); return Character.toString (cInput); } /** * Turn special regular expression characters into escaped characters * conforming to JavaScript.
* Reference: MDN Regular Expressions * * @param sInput * the input string * @return the escaped string */ @Nullable public static String javaScriptEscapeForRegEx (@Nullable final String sInput) { if (StringHelper.hasNoText (sInput)) return sInput; final char [] aInput = sInput.toCharArray (); if (!StringHelper.containsAny (aInput, CHARS_TO_MASK_REGEX)) return sInput; // At last each character has one masking character final char [] ret = new char [aInput.length * 2]; int nIndex = 0; for (final char cCurrent : aInput) if (ArrayHelper.contains (CHARS_TO_MASK_REGEX, cCurrent)) { ret[nIndex++] = MASK_CHAR_REGEX; ret[nIndex++] = cCurrent; } else ret[nIndex++] = cCurrent; return new String (ret, 0, nIndex); } /** * Unescape a previously escaped string.
* Important: this is not a 100% reversion of * {@link #javaScriptEscape(String)} since the escaping method drops any '\r' * character and it will therefore not be unescaped! * * @param sInput * The string to be unescaped. May be null. * @return The unescaped string. * @see #javaScriptEscape(String) */ @Nullable public static String javaScriptUnescape (@Nullable final String sInput) { if (StringHelper.hasNoText (sInput)) return sInput; final char [] aInput = sInput.toCharArray (); if (!ArrayHelper.contains (aInput, MASK_CHAR)) return sInput; final char [] ret = new char [aInput.length]; int nIndex = 0; char cPrevChar = '\u0000'; for (int i = 0; i < aInput.length; i++) { final char cCurrent = aInput[i]; if (cPrevChar == MASK_CHAR) { switch (cCurrent) { case '"': case '\'': case '\\': case '/': ret[nIndex++] = cCurrent; break; case 't': ret[nIndex++] = '\t'; break; case 'n': ret[nIndex++] = '\n'; break; case 'f': ret[nIndex++] = '\f'; break; case 'x': final char cHex1 = aInput[++i]; final char cHex2 = aInput[++i]; ret[nIndex++] = (char) StringHelper.getHexByte (cHex1, cHex2); break; default: ret[nIndex++] = MASK_CHAR; ret[nIndex++] = cCurrent; break; } cPrevChar = 0; } else { if (cCurrent != MASK_CHAR) ret[nIndex++] = cCurrent; cPrevChar = cCurrent; } } // Last char is a mask char? append! if (cPrevChar == MASK_CHAR) ret[nIndex++] = MASK_CHAR; return new String (ret, 0, nIndex); } public static boolean isJSIdentifier (@Nullable final String s) { if (StringHelper.hasNoText (s)) return false; // Reserved word? if (RESERVED_KEYWORDS.contains (s)) return false; final char [] aChars = s.toCharArray (); for (int i = 0; i < aChars.length; ++i) { if (i == 0) { if (!Character.isJavaIdentifierStart (aChars[i])) return false; } else { if (!Character.isJavaIdentifierPart (aChars[i])) return false; } } return true; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy