Java tutorial
/* * Copyright 2014 Google Inc. * * 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.google.template.soy.pysrc.internal; import com.google.common.base.CaseFormat; import com.google.template.soy.shared.internal.AbstractGenerateSoyEscapingDirectiveCode; import com.google.template.soy.shared.internal.DirectiveDigest; import com.google.template.soy.shared.restricted.EscapingConventions; import com.google.template.soy.shared.restricted.EscapingConventions.EscapingLanguage; import com.google.template.soy.shared.restricted.TagWhitelist; import java.io.IOException; import java.util.regex.Pattern; import javax.annotation.ParametersAreNonnullByDefault; /** * Generates Python code in generated_sanitize.py used by the public functions in sanitize.py. * * <p> * This is an ant task and can be invoked as: * <xmp> * <taskdef name="gen.escape.directives" * classname="com.google.template.soy.pysrc.internal.GeneratePySanitizeEscapingDirectiveCode"> * <classpath> * <!-- classpath to Soy classes and dependencies --> * </classpath> * </taskdef> * <gen.escape.directives> * <input path="one or more Python files that use the generated helpers"/> * <output path="the output Python file"/> * </gen.escape.directives> * </xmp> * * <p> * In the above, the first {@code <taskdef>} is an Ant builtin which links the element named * {@code <gen.escape.directives>} to this class. * <p> * That element contains zero or more {@code <input>}s which are Python source files that may * use the helper functions generated by this task. * <p> * There must be exactly one {@code <output>} element which specifies where the output should be * written. That output contains the input sources and the generated helper functions. * */ @ParametersAreNonnullByDefault public final class GeneratePySanitizeEscapingDirectiveCode extends AbstractGenerateSoyEscapingDirectiveCode { @Override protected EscapingLanguage getLanguage() { return EscapingLanguage.PYTHON; } @Override protected String getLineCommentSyntax() { return "#"; } @Override protected String getLineEndSyntax() { return ""; } @Override protected String getRegexStart() { return "re.compile(r'"; } @Override protected String getRegexEnd() { return "', re.U)"; } @Override protected String escapeOutputString(String input) { String escapeCharacters = "\\\'\"\b\f\n\r\t"; // Give the string builder a little bit of extra space to account for new escape characters. StringBuilder result = new StringBuilder((int) (input.length() * 1.2)); for (char c : input.toCharArray()) { if (escapeCharacters.indexOf(c) != -1) { result.append('\\'); } result.append(c); } return result.toString(); } @Override protected String convertFromJavaRegex(Pattern javaPattern) { String body = javaPattern.pattern().replace("\r", "\\r").replace("\n", "\\n").replace("\u2028", "\\u2028") .replace("\u2029", "\\u2029").replace("\\z", "\\Z"); // DOTALL is not allowed to keep the syntax simple (it's also not available in JavaScript). if ((javaPattern.flags() & Pattern.DOTALL) != 0) { throw new IllegalArgumentException("Pattern " + javaPattern + " uses DOTALL."); } StringBuilder buffer = new StringBuilder(body.length() + 40); // Default to using unicode character classes. buffer.append("re.compile(r\"\"\"").append(body).append("\"\"\", re.U"); if ((javaPattern.flags() & Pattern.CASE_INSENSITIVE) != 0) { buffer.append(" | re.I"); } if ((javaPattern.flags() & Pattern.MULTILINE) != 0) { buffer.append(" | re.M"); } buffer.append(")"); return buffer.toString(); } @Override protected void generatePrefix(StringBuilder outputCode) { // Emulate Python 3 style unicode string literals, and import necessary libraries. outputCode.append("from __future__ import unicode_literals\n").append("\n").append("import re\n") .append("import urllib\n").append("\n").append("try:\n").append(" str = unicode\n") .append("except NameError:\n").append(" pass\n\n"); } @Override protected void generateCharacterMapSignature(StringBuilder outputCode, String mapName) { outputCode.append("_ESCAPE_MAP_FOR_").append(mapName); } @Override protected void generateMatcher(StringBuilder outputCode, String name, String matcher) { outputCode.append("\n_MATCHER_FOR_").append(name).append(" = ").append(matcher).append("\n"); } @Override protected void generateFilter(StringBuilder outputCode, String name, String filter) { outputCode.append("\n_FILTER_FOR_").append(name).append(" = ").append(filter).append("\n"); } @Override protected void generateReplacerFunction(StringBuilder outputCode, String mapName) { outputCode.append("\ndef _REPLACER_FOR_").append(mapName).append("(match):\n") .append(" ch = match.group(0)\n").append(" return _ESCAPE_MAP_FOR_").append(mapName) .append("[ch]\n").append("\n"); } @Override protected void useExistingLibraryFunction(StringBuilder outputCode, String identifier, String existingFunction) { String fnName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, identifier); outputCode.append("\ndef ").append(fnName).append("_helper(v):\n").append(" return ") .append(existingFunction).append("(str(v))\n").append("\n"); } @Override protected void generateHelperFunction(StringBuilder outputCode, DirectiveDigest digest) { String name = digest.getDirectiveName(); String fnName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name); outputCode.append("\ndef ").append(fnName).append("_helper(value):\n").append(" value = str(value)\n"); if (digest.getFilterName() != null) { String filterName = digest.getFilterName(); outputCode.append(" if not _FILTER_FOR_").append(filterName).append(".search(value):\n"); // TODO(dcphillips): Raising a debugging assertion error could be useful. outputCode.append(" return '").append(digest.getInnocuousOutput()).append("'\n").append("\n"); } if (digest.getNonAsciiPrefix() != null) { // TODO: We can add a second replace of all non-ascii codepoints below. throw new UnsupportedOperationException("Non ASCII prefix escapers not implemented yet."); } if (digest.getEscapesName() != null) { String escapeMapName = digest.getEscapesName(); String matcherName = digest.getMatcherName(); outputCode.append(" return _MATCHER_FOR_").append(matcherName).append(".sub(\n") .append(" _REPLACER_FOR_").append(escapeMapName).append(", value)\n"); } else { outputCode.append(" return value\n"); } outputCode.append("\n"); } @Override protected void generateCommonConstants(StringBuilder outputCode) { // Emit patterns and constants needed by escaping functions that are not part of any one // escaping convention. outputCode.append("_HTML_TAG_REGEX = ").append(convertFromJavaRegex(EscapingConventions.HTML_TAG_CONTENT)) .append("\n\n").append("_LT_REGEX = re.compile('<')\n").append("\n") .append("_SAFE_TAG_WHITELIST = ").append(toPyStringTuple(TagWhitelist.FORMATTING.asSet())) .append("\n\n"); } /** ["foo", "bar"] -> '("foo", "bar")' */ private String toPyStringTuple(Iterable<String> strings) { StringBuilder sb = new StringBuilder(); boolean isFirst = true; sb.append('('); for (String str : strings) { if (!isFirst) { sb.append(", "); } isFirst = false; writeStringLiteral(str, sb); } sb.append(')'); return sb.toString(); } /** * A non Ant interface for this class. */ public static void main(String[] args) throws IOException { GeneratePySanitizeEscapingDirectiveCode generator = new GeneratePySanitizeEscapingDirectiveCode(); generator.configure(args); generator.execute(); } }