Android.bp0100644 0000000 0000000 00000001344 13662126234 011517 0ustar000000000 0000000 // Copyright (C) 2010 The Android Open Source Project // // 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. java_library_host { name: "jsilver", srcs: ["src/**/*.java"], libs: ["guava"], java_resource_dirs: ["src"], } MODULE_LICENSE_APACHE20100644 0000000 0000000 00000000000 13662126234 012735 0ustar000000000 0000000 NOTICE0100644 0000000 0000000 00000026136 13662126234 010526 0ustar000000000 0000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. OWNERS0100644 0000000 0000000 00000000056 13662126234 010553 0ustar000000000 0000000 include platform/system/core:/janitors/OWNERS README.version0100644 0000000 0000000 00000000067 13662126234 012161 0ustar000000000 0000000 URL: https://code.google.com/p/jsilver/ Version: 1.0.0 build.gradle0100644 0000000 0000000 00000000476 13662126234 012100 0ustar000000000 0000000 apply plugin: 'java' sourceSets { main { java { srcDirs = ['src'] exclude 'com/google/clearsilver/jsilver/benchmark/*.java' } resources { srcDirs = ['src'] include '**/*.dat' } } } dependencies { compile project(':guava') } build.xml0100644 0000000 0000000 00000003323 13662126234 011434 0ustar000000000 0000000 src/0040755 0000000 0000000 00000000000 13662126234 010404 5ustar000000000 0000000 src/com/0040755 0000000 0000000 00000000000 13662126234 011162 5ustar000000000 0000000 src/com/google/0040755 0000000 0000000 00000000000 13662126234 012436 5ustar000000000 0000000 src/com/google/clearsilver/0040755 0000000 0000000 00000000000 13662126234 014751 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/0040755 0000000 0000000 00000000000 13662126234 016427 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/DataLoader.java0100644 0000000 0000000 00000002420 13662126234 021265 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException; import java.io.IOException; /** * Loads data from resources. */ public interface DataLoader { /** * Create new Data instance, ready to be populated. */ Data createData(); /** * Loads data in Hierarchical Data Format (HDF) into an existing Data object. */ void loadData(final String dataFileName, Data output) throws JSilverBadSyntaxException, IOException; /** * Loads data in Hierarchical Data Format (HDF) into a new Data object. */ Data loadData(String dataFileName) throws IOException; } src/com/google/clearsilver/jsilver/JSilver.java0100644 0000000 0000000 00000042360 13662126234 020652 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.compiler.TemplateCompiler; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataFactory; import com.google.clearsilver.jsilver.data.HDFDataFactory; import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException; import com.google.clearsilver.jsilver.exceptions.JSilverException; import com.google.clearsilver.jsilver.functions.Function; import com.google.clearsilver.jsilver.functions.FunctionRegistry; import com.google.clearsilver.jsilver.functions.TextFilter; import com.google.clearsilver.jsilver.functions.bundles.ClearSilverCompatibleFunctions; import com.google.clearsilver.jsilver.functions.bundles.CoreOperators; import com.google.clearsilver.jsilver.interpreter.InterpretedTemplateLoader; import com.google.clearsilver.jsilver.interpreter.LoadingTemplateFactory; import com.google.clearsilver.jsilver.interpreter.OptimizerProvider; import com.google.clearsilver.jsilver.interpreter.OptimizingTemplateFactory; import com.google.clearsilver.jsilver.interpreter.TemplateFactory; import com.google.clearsilver.jsilver.output.InstanceOutputBufferProvider; import com.google.clearsilver.jsilver.output.OutputBufferProvider; import com.google.clearsilver.jsilver.output.ThreadLocalOutputBufferProvider; import com.google.clearsilver.jsilver.precompiler.PrecompiledTemplateLoader; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.DataCommandConsolidator; import com.google.clearsilver.jsilver.syntax.SyntaxTreeOptimizer; import com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper; import com.google.clearsilver.jsilver.syntax.node.Switch; import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader; import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; import java.io.IOException; import java.util.LinkedList; import java.util.List; /** * JSilver templating system. * *

* This is a pure Java version of ClearSilver. *

* *

Example Usage

* *
 * // Load resources (e.g. templates) from directory.
 * JSilver jSilver = new JSilver(new FileResourceLoader("/path/to/templates"));
 *
 * // Set up some data.
 * Data data = new Data();
 * data.setValue("name.first", "Mr");
 * data.setValue("name.last", "Man");
 *
 * // Render template to System.out. Writer output = ...;
 * jSilver.render("say-hello", data, output);
 * 
* * For example usage, see java/com/google/clearsilver/jsilver/examples. * * Additional options can be passed to the constructor using JSilverOptions. * * @see JSilver Docs * @see ClearSilver Docs * @see JSilverOptions * @see Data * @see ResourceLoader */ public final class JSilver implements TemplateRenderer, DataLoader { private final JSilverOptions options; private final TemplateLoader templateLoader; /** * If caching enabled, the cached wrapper (otherwise null). Kept here so we can call clearCache() * later. */ private final FunctionRegistry globalFunctions = new ClearSilverCompatibleFunctions(); private final ResourceLoader defaultResourceLoader; private final DataFactory dataFactory; // Object used to return Appendable output buffers when needed. private final OutputBufferProvider outputBufferProvider; public static final String VAR_ESCAPE_MODE_KEY = "Config.VarEscapeMode"; public static final String AUTO_ESCAPE_KEY = "Config.AutoEscape"; /** * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. * directory, classpath, memory, etc. * @param options Additional options. * @see JSilverOptions */ public JSilver(ResourceLoader defaultResourceLoader, JSilverOptions options) { // To ensure that options cannot be changed externally, we clone them and // use the frozen clone. options = options.clone(); this.defaultResourceLoader = defaultResourceLoader; this.dataFactory = new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy()); this.options = options; // Setup the output buffer provider either with a threadlocal pool // or creating a new instance each time it is asked for. int bufferSize = options.getInitialBufferSize(); if (options.getUseOutputBufferPool()) { // Use a ThreadLocal to reuse StringBuilder objects. outputBufferProvider = new ThreadLocalOutputBufferProvider(bufferSize); } else { // Create a new StringBuilder each time. outputBufferProvider = new InstanceOutputBufferProvider(bufferSize); } // Loads the template from the resource loader, manipulating the AST as // required for correctness. TemplateFactory templateFactory = new LoadingTemplateFactory(); // Applies optimizations to improve performance. // These steps are entirely optional, and are not required for correctness. templateFactory = setupOptimizerFactory(templateFactory); TemplateLoader templateLoader; List delegatingTemplateLoaders = new LinkedList(); AutoEscapeOptions autoEscapeOptions = new AutoEscapeOptions(); autoEscapeOptions.setPropagateEscapeStatus(options.getPropagateEscapeStatus()); autoEscapeOptions.setLogEscapedVariables(options.getLogEscapedVariables()); if (options.getCompileTemplates()) { // Compiled templates. TemplateCompiler compiler = new TemplateCompiler(templateFactory, globalFunctions, autoEscapeOptions); delegatingTemplateLoaders.add(compiler); templateLoader = compiler; } else { // Walk parse tree every time. InterpretedTemplateLoader interpreter = new InterpretedTemplateLoader(templateFactory, globalFunctions, autoEscapeOptions); delegatingTemplateLoaders.add(interpreter); templateLoader = interpreter; } // Do we want to load precompiled Template class objects? if (options.getPrecompiledTemplateMap() != null) { // Load precompiled template classes. PrecompiledTemplateLoader ptl = new PrecompiledTemplateLoader(templateLoader, options.getPrecompiledTemplateMap(), globalFunctions, autoEscapeOptions); delegatingTemplateLoaders.add(ptl); templateLoader = ptl; } for (DelegatingTemplateLoader loader : delegatingTemplateLoaders) { loader.setTemplateLoaderDelegate(templateLoader); } this.templateLoader = templateLoader; } /** * Applies optimizations to improve performance. These steps are entirely optional, and are not * required for correctness. */ private TemplateFactory setupOptimizerFactory(TemplateFactory templateFactory) { // DataCommandConsolidator saves state so we need to create a new one // every time we run it. OptimizerProvider dataCommandConsolidatorProvider = new OptimizerProvider() { public Switch getOptimizer() { return new DataCommandConsolidator(); } }; // SyntaxTreeOptimizer has no state so we can use the same object // concurrently, but it is cheap to make so lets be consistent. OptimizerProvider syntaxTreeOptimizerProvider = new OptimizerProvider() { public Switch getOptimizer() { return new SyntaxTreeOptimizer(); } }; OptimizerProvider stripStructuralWhitespaceProvider = null; if (options.getStripStructuralWhiteSpace()) { // StructuralWhitespaceStripper has state so create a new one each time. stripStructuralWhitespaceProvider = new OptimizerProvider() { public Switch getOptimizer() { return new StructuralWhitespaceStripper(); } }; } return new OptimizingTemplateFactory(templateFactory, dataCommandConsolidatorProvider, syntaxTreeOptimizerProvider, stripStructuralWhitespaceProvider); } /** * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. * directory, classpath, memory, etc. * @param cacheTemplates Whether to cache templates. Cached templates are much faster but do not * check the filesystem for updates. Use true in prod, false in dev. * @deprecated Use {@link #JSilver(ResourceLoader, JSilverOptions)}. */ @Deprecated public JSilver(ResourceLoader defaultResourceLoader, boolean cacheTemplates) { this(defaultResourceLoader, new JSilverOptions().setCacheTemplates(cacheTemplates)); } /** * Creates a JSilver instance with default options. * * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. * directory, classpath, memory, etc. * @see JSilverOptions */ public JSilver(ResourceLoader defaultResourceLoader) { this(defaultResourceLoader, new JSilverOptions()); } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param templateName Name of template to load (e.g. "things/blah.cs"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements Appendable * @param resourceLoader How to find the template data to render and any included files it depends * on. */ @Override public void render(String templateName, Data data, Appendable output, ResourceLoader resourceLoader) throws IOException, JSilverException { EscapeMode escapeMode = getEscapeMode(data); render(templateLoader.load(templateName, resourceLoader, escapeMode), data, output, resourceLoader); } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param templateName Name of template to load (e.g. "things/blah.cs"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements */ @Override public void render(String templateName, Data data, Appendable output) throws IOException, JSilverException { render(templateName, data, output, defaultResourceLoader); } /** * Same as {@link TemplateRenderer#render(String, Data, Appendable)}, except returns rendered * template as a String. */ @Override public String render(String templateName, Data data) throws IOException, JSilverException { Appendable output = createAppendableBuffer(); try { render(templateName, data, output); return output.toString(); } finally { releaseAppendableBuffer(output); } } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param template Template to load. * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable. */ @Override public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader) throws IOException, JSilverException { if (options.getStripHtmlWhiteSpace() && !(output instanceof HtmlWhiteSpaceStripper)) { // Strip out whitespace from rendered HTML content. output = new HtmlWhiteSpaceStripper(output); } template.render(data, output, resourceLoader); } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param template Template to load. * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable. */ @Override public void render(Template template, Data data, Appendable output) throws IOException, JSilverException { render(template, data, output, defaultResourceLoader); } @Override public String render(Template template, Data data) throws IOException, JSilverException { Appendable output = createAppendableBuffer(); try { render(template, data, output); return output.toString(); } finally { releaseAppendableBuffer(output); } } /** * Renders a given template from the content passed in. That is, the first parameter is the actual * template content rather than the filename to load. * * @param content Content of template (e.g. "Hello <cs var:name ?>"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable */ @Override public void renderFromContent(String content, Data data, Appendable output) throws IOException, JSilverException { EscapeMode escapeMode = getEscapeMode(data); render(templateLoader.createTemp("[renderFromContent]", content, escapeMode), data, output); } /** * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template * as a String. */ @Override public String renderFromContent(String content, Data data) throws IOException, JSilverException { Appendable output = createAppendableBuffer(); try { renderFromContent(content, data, output); return output.toString(); } finally { releaseAppendableBuffer(output); } } /** * Determine the escaping to apply based on Config variables in HDF. If there is no escaping * specified in the HDF, check whether JSilverOptions has any escaping configured. * * @param data HDF Data to check * @return EscapeMode */ public EscapeMode getEscapeMode(Data data) { EscapeMode escapeMode = EscapeMode.computeEscapeMode(data.getValue(VAR_ESCAPE_MODE_KEY), data .getBooleanValue(AUTO_ESCAPE_KEY)); if (escapeMode.equals(EscapeMode.ESCAPE_NONE)) { escapeMode = options.getEscapeMode(); } return escapeMode; } /** * Override this to change the type of Appendable buffer used in {@link #render(String, Data)}. */ public Appendable createAppendableBuffer() { return outputBufferProvider.get(); } public void releaseAppendableBuffer(Appendable buffer) { outputBufferProvider.release(buffer); } /** * Registers a global Function that can be used from any template. */ public void registerGlobalFunction(String name, Function function) { globalFunctions.registerFunction(name, function); } /** * Registers a global TextFilter as function that can be used from any template. */ public void registerGlobalFunction(String name, TextFilter textFilter) { globalFunctions.registerFunction(name, textFilter); } /** * Registers a global escaper. This also makes it available as a Function named with "_escape" * suffix (e.g. "html_escape"). */ public void registerGlobalEscaper(String name, TextFilter escaper) { globalFunctions.registerFunction(name + "_escape", escaper, true); globalFunctions.registerEscapeMode(name, escaper); } /** * Create new Data instance, ready to be populated. */ public Data createData() { return dataFactory.createData(); } /** * Loads data in Hierarchical Data Format (HDF) into an existing Data object. */ @Override public void loadData(String dataFileName, Data output) throws JSilverBadSyntaxException, IOException { dataFactory.loadData(dataFileName, defaultResourceLoader, output); } /** * Loads data in Hierarchical Data Format (HDF) into a new Data object. */ @Override public Data loadData(String dataFileName) throws IOException { return dataFactory.loadData(dataFileName, defaultResourceLoader); } /** * Gets underlying ResourceLoader so you can access arbitrary files using the same mechanism as * JSilver. */ public ResourceLoader getResourceLoader() { return defaultResourceLoader; } /** * Force all cached templates to be cleared. */ public void clearCache() { } /** * Returns the TemplateLoader used by this JSilver template renderer. Needed for HDF/CS * compatbility. */ public TemplateLoader getTemplateLoader() { return templateLoader; } /** * Returns a copy of the JSilverOptions used by this JSilver instance. */ public JSilverOptions getOptions() { return options.clone(); } } src/com/google/clearsilver/jsilver/JSilverOptions.java0100644 0000000 0000000 00000033062 13662126234 022225 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.NoOpStringInternStrategy; import com.google.clearsilver.jsilver.data.StringInternStrategy; import java.util.Map; /** * Options for JSilver. * * Note: Setter methods also return reference to this, allowing options to be defined in one * statement. * * e.g. new JSilver(..., new JSilverOptions().setSomething(true).setAnother(false)); * * @see JSilver */ public class JSilverOptions implements Cloneable { private boolean cacheTemplates = true; private boolean compileTemplates = false; private int initialBufferSize = 8192; private boolean ignoreAttributes = false; private Map precompiledTemplateMap = null; private boolean useStrongCacheReferences = false; private EscapeMode escapeMode = EscapeMode.ESCAPE_NONE; private boolean propagateEscapeStatus = false; /** * A pool of strings used to optimize HDF parsing. * * String interning has been shown to improve GC performance, but also to increase CPU usage. To * avoid any possible unexpected changes in behavior it is disabled by default. */ private StringInternStrategy stringInternStrategy = new NoOpStringInternStrategy(); /** * This flag is used to enable logging of all variables whose values are modified by auto escaping * or <cs escape> commands. These will be logged at {@code Level.WARNING}. */ private boolean logEscapedVariables = false; private boolean useOutputBufferPool = false; private boolean stripHtmlWhiteSpace = false; private boolean stripStructuralWhiteSpace = false; private boolean allowGlobalDataModification = false; private boolean keepTemplateCacheFresh = false; private int loadPathCacheSize = 1000; // When adding fields, ensure you: // * add getter. // * add setter (which returns this). // * add to clone() method if necessary. /** * Set the initial size of the load path cache container. Setting this to 0 causes load path cache * to be disabled. */ public JSilverOptions setLoadPathCacheSize(int loadPathCacheSize) { this.loadPathCacheSize = loadPathCacheSize; return this; } /** * @see #setLoadPathCacheSize(int) */ public int getLoadPathCacheSize() { return loadPathCacheSize; } /** * Whether to cache templates. This will only ever load and parse a template from disk once. Best * switched on for production but left off for production (so you can update templates without * restarting). */ public JSilverOptions setCacheTemplates(boolean cacheTemplates) { this.cacheTemplates = cacheTemplates; return this; } /** * @see #setCacheTemplates(boolean) */ public boolean getCacheTemplates() { return cacheTemplates; } /** * Compile templates to Java byte code. This slows down the initial render as it performs a * compilation step, but then subsequent render are faster. * * Compiled templates are always cached. * * WARNING: This functionality is experimental. Use with caution. */ public JSilverOptions setCompileTemplates(boolean compileTemplates) { this.compileTemplates = compileTemplates; return this; } /** * @see #setCompileTemplates(boolean) */ public boolean getCompileTemplates() { return compileTemplates; } /** * If set, then HDF attributes in HDF files will be ignored and not stored in the Data object * filled by the parser. Default is {@code false}. Many applications use HDF attributes only in * template preprocessing (like translation support) and never in production servers where the * templates are rendered. By disabling attribute parsing, these applications can save on memory * for storing HDF structures read from files. */ public JSilverOptions setIgnoreAttributes(boolean ignoreAttributes) { this.ignoreAttributes = ignoreAttributes; return this; } /** * @see #setIgnoreAttributes(boolean) */ public boolean getIgnoreAttributes() { return ignoreAttributes; } /** * Initial buffer size used when rendering directly to a string. */ public JSilverOptions setInitialBufferSize(int initialBufferSize) { this.initialBufferSize = initialBufferSize; return this; } /** * @see #setInitialBufferSize(int) */ public int getInitialBufferSize() { return initialBufferSize; } /** * Optional mapping of TemplateLoader keys to Template instances that will be queried when loading * a template. If the Template is found it is returned. If not, the next template loader is * consulted. * * @param precompiledTemplateMap map of TemplateLoader keys to corresponding class names that * should be valid BaseCompiledTemplate subclasses. Set to {@code null} (default) to not * load precompiled templates. */ public JSilverOptions setPrecompiledTemplateMap(Map precompiledTemplateMap) { this.precompiledTemplateMap = precompiledTemplateMap; return this; } /** * @see #setPrecompiledTemplateMap(java.util.Map) * @return a mapping of TemplateLoader keys to corresponding BaseCompiledTemplate class names, or * {@code null} (default) if no precompiled templates should be preloaded. */ public Map getPrecompiledTemplateMap() { return precompiledTemplateMap; } /** * If {@code true}, then the template cache will use strong persistent references for the values. * If {@code false} (default) it will use soft references. Warning: The cache size is unbounded so * only use this option if you have sufficient memory to load all the Templates into memory. */ public JSilverOptions setUseStrongCacheReferences(boolean value) { this.useStrongCacheReferences = value; return this; } /** * @see #setUseStrongCacheReferences(boolean) */ public boolean getUseStrongCacheReferences() { return useStrongCacheReferences; } /** * @see #setEscapeMode(com.google.clearsilver.jsilver.autoescape.EscapeMode) */ public EscapeMode getEscapeMode() { return escapeMode; } /** * Escape any template being rendered with the given escaping mode. If the mode is ESCAPE_HTML, * ESCAPE_URL or ESCAPE_JS, the corresponding escaping will be all variables in the template. If * the mode is ESCAPE_AUTO, enable auto escaping on * templates. For each variable in the template, this will determine what type of escaping should * be applied to the variable, and automatically apply this escaping. This flag can be overriden * by setting appropriate HDF variables before loading a template. If Config.AutoEscape is 1, auto * escaping is enabled. If Config.VarEscapeMode is set to one of 'html', 'js' or 'url', the * corresponding escaping is applied to all variables. * * @param escapeMode */ public JSilverOptions setEscapeMode(EscapeMode escapeMode) { this.escapeMode = escapeMode; return this; } /** * @see #setPropagateEscapeStatus */ public boolean getPropagateEscapeStatus() { return propagateEscapeStatus; } /** * Only used for templates that are being auto escaped. If * {@code true} and auto escaping is enabled, variables created by <cs set> or <cs * call> commands are not auto escaped if they are assigned constant or escaped values. This is * disabled by default. * * @see #setEscapeMode */ public JSilverOptions setPropagateEscapeStatus(boolean propagateEscapeStatus) { this.propagateEscapeStatus = propagateEscapeStatus; return this; } /** * Sets the {@link StringInternStrategy} object that will be used to optimize HDF parsing. * *

* Set value should not be {@code null} as it can cause {@link NullPointerException}. * * @param stringInternStrategy - {@link StringInternStrategy} object */ public void setStringInternStrategy(StringInternStrategy stringInternStrategy) { if (stringInternStrategy == null) { throw new IllegalArgumentException("StringInternStrategy should not be null."); } this.stringInternStrategy = stringInternStrategy; } /** * Returns {@link StringInternStrategy} object that is used for optimization of HDF parsing. * *

* The returned value should never be {@code null}. * * @return currently used {@link StringInternStrategy} object. */ public StringInternStrategy getStringInternStrategy() { return stringInternStrategy; } /** * If {@code true}, then use a threadlocal buffer pool for holding rendered output when outputting * to String. Assumes that render() is called from a thread and that thread will not reenter * render() while it is running. If {@code false}, a new buffer is allocated for each request * where an Appendable output object was not provided. */ public JSilverOptions setUseOutputBufferPool(boolean value) { this.useOutputBufferPool = value; return this; } /** * @see #setUseOutputBufferPool(boolean) */ public boolean getUseOutputBufferPool() { return useOutputBufferPool; } /** * If {@code true}, then unnecessary whitespace will be stripped from the output. 'Unnecessary' is * meant in terms of HTML output. See * {@link com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper} for more info. */ public JSilverOptions setStripHtmlWhiteSpace(boolean value) { this.stripHtmlWhiteSpace = value; return this; } /** * @see #setStripHtmlWhiteSpace(boolean) */ public boolean getStripHtmlWhiteSpace() { return stripHtmlWhiteSpace; } /** * If {@code true}, then structural whitespace will be stripped from the output. This allows * templates to be written to more closely match normal programming languages. See * {@link com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper} for more info. */ public JSilverOptions setStripStructuralWhiteSpace(boolean value) { this.stripStructuralWhiteSpace = value; return this; } /** * @see #setStripHtmlWhiteSpace(boolean) */ public boolean getStripStructuralWhiteSpace() { return stripStructuralWhiteSpace; } /** * Use this method to disable wrapping the global HDF with an UnmodifiableData object which * prevents modification. This flag is here in case there are corner cases or performance reasons * that someone may want to disable this protection. * * Should not be set to {@code true} unless incompatibilities or performance issues found. Note, * that setting to {@code true} could introduce bugs in templates that acquire local references to * the global data structure and then try to modify them, which is not the intended behavior. * Allowing global data modification during rendering is not compatible with the recently fixed * JNI Clearsilver library. * * TODO: Remove once legacy mode is no longer needed. * * @param allowGlobalDataModification {@code true} if you want to avoid wrapping the global HDF so * that all writes to it during rendering are prevented and throw an exception. * @return this object. */ public JSilverOptions setAllowGlobalDataModification(boolean allowGlobalDataModification) { this.allowGlobalDataModification = allowGlobalDataModification; return this; } /** * @see #setAllowGlobalDataModification(boolean) */ public boolean getAllowGlobalDataModification() { return allowGlobalDataModification; } /** * @param keepTemplateCacheFresh {@code true} to have the template cache call * {@link com.google.clearsilver.jsilver.resourceloader.ResourceLoader#getResourceVersionId(String)} * to check if it should refresh its cache entries (this incurs a small performance penalty * each time the cache is accessed) * @return this object */ public JSilverOptions setKeepTemplateCacheFresh(boolean keepTemplateCacheFresh) { this.keepTemplateCacheFresh = keepTemplateCacheFresh; return this; } /** * @see #setKeepTemplateCacheFresh(boolean) */ public boolean getKeepTemplateCacheFresh() { return keepTemplateCacheFresh; } @Override public JSilverOptions clone() { try { return (JSilverOptions) super.clone(); } catch (CloneNotSupportedException impossible) { throw new AssertionError(impossible); } } /** * @see #setLogEscapedVariables */ public boolean getLogEscapedVariables() { return logEscapedVariables; } /** * Use this method to enable logging of all variables whose values are modified by auto escaping * or <cs escape> commands. These will be logged at {@code Level.WARNING}. This is useful * for detecting variables that should be exempt from auto escaping. * *

* It is recommended to only enable this flag during testing or debugging and not for production * jobs. * * @see #setEscapeMode */ public JSilverOptions setLogEscapedVariables(boolean logEscapedVariables) { this.logEscapedVariables = logEscapedVariables; return this; } } src/com/google/clearsilver/jsilver/TemplateRenderer.java0100644 0000000 0000000 00000007713 13662126234 022541 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.exceptions.JSilverException; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.template.Template; import java.io.IOException; /** * Renders a template. */ public interface TemplateRenderer { /** * Renders a given template and provided data, writing to an arbitrary output. * * @param templateName Name of template to load (e.g. "things/blah.cs"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable * @param resourceLoader ResourceLoader to use when reading in included files. */ void render(String templateName, Data data, Appendable output, ResourceLoader resourceLoader) throws IOException, JSilverException; /** * Same as {@link #render(String, Data, Appendable, ResourceLoader)}, except it uses the default * ResourceLoader passed in to the JSilver constructor. */ void render(String templateName, Data data, Appendable output) throws IOException, JSilverException; /** * Same as {@link #render(String, Data, Appendable)}, except returns rendered template as a * String. */ String render(String templateName, Data data) throws IOException, JSilverException; /** * Renders a given template and provided data, writing to an arbitrary output. * * @param template Template to render. * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable. * @param resourceLoader ResourceLoader to use when reading in included files. * */ void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader) throws IOException, JSilverException; /** * Same as {@link #render(Template,Data,Appendable,ResourceLoader)}, except it uses the * ResourceLoader passed into the JSilver constructor. */ void render(Template template, Data data, Appendable output) throws IOException, JSilverException; /** * Same as {@link #render(Template,Data,Appendable)}, except returns rendered template as a * String. */ String render(Template template, Data data) throws IOException, JSilverException; /** * Renders a given template from the content passed in. That is, the first parameter is the actual * template content rather than the filename to load. * * @param content Content of template (e.g. "Hello <cs var:name ?>"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable */ void renderFromContent(String content, Data data, Appendable output) throws IOException, JSilverException; /** * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template * as a String. */ String renderFromContent(String content, Data data) throws IOException, JSilverException; } src/com/google/clearsilver/jsilver/adaptor/0040755 0000000 0000000 00000000000 13662126234 020061 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/adaptor/JCs.java0100644 0000000 0000000 00000012774 13662126234 021413 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.adaptor; import com.google.clearsilver.jsilver.JSilver; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.LocalAndGlobalData; import com.google.clearsilver.jsilver.exceptions.JSilverIOException; import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper; import com.google.clearsilver.jsilver.template.Template; import org.clearsilver.CS; import org.clearsilver.CSFileLoader; import org.clearsilver.HDF; import java.io.IOException; /** * Adaptor that wraps a JSilver object so it can be used as an CS object. */ class JCs implements CS { private final JHdf localHdf; private JHdf globalHdf; private final JSilver jSilver; private final LoadPathToFileCache loadPathCache; private Template template = null; private CSFileLoader csFileLoader; private ResourceLoaderAdaptor resourceLoaderAdaptor; JCs(JHdf hdf, JSilver jSilver, LoadPathToFileCache loadPathCache) { this.localHdf = hdf; this.jSilver = jSilver; this.loadPathCache = loadPathCache; resourceLoaderAdaptor = localHdf.getResourceLoaderAdaptor(); csFileLoader = resourceLoaderAdaptor.getCSFileLoader(); } /** * Want to delay creating the JSilver object so we can specify necessary parameters. */ private JSilver getJSilver() { return jSilver; } @Override public void setGlobalHDF(HDF global) { globalHdf = JHdf.cast(global); } @Override public HDF getGlobalHDF() { return globalHdf; } @Override public void close() { // Removing unneeded reference, although this is not expected to have the // performance impact seen in JHdf as in production configurations users // should be using cached templates so they are long-lived. template = null; } @Override public void parseFile(String filename) throws IOException { try { if (getEscapeMode().isAutoEscapingMode()) { if (localHdf.getData().getValue("Config.PropagateEscapeStatus") != null) { throw new IllegalArgumentException( "Config.PropagateEscapeStatus does not work with JSilver." + "Use JSilverOptions.setPropagateEscapeStatus instead"); } } template = getJSilver().getTemplateLoader().load(filename, resourceLoaderAdaptor, getEscapeMode()); } catch (RuntimeException e) { Throwable th = e; if (th instanceof JSilverIOException) { // JSilverIOException always has an IOException as its cause. throw ((IOException) th.getCause()); } throw e; } } @Override public void parseStr(String content) { if (getEscapeMode().isAutoEscapingMode()) { if (localHdf.getData().getValue("Config.PropagateEscapeStatus") != null) { throw new IllegalArgumentException( "Config.PropagateEscapeStatus does not work with JSilver." + "Use JSilverOptions.setPropagateEscapeStatus instead"); } } template = getJSilver().getTemplateLoader().createTemp("parseStr", content, getEscapeMode()); } private EscapeMode getEscapeMode() { Data data = localHdf.getData(); return getJSilver().getEscapeMode(data); } @Override public String render() { if (template == null) { throw new IllegalStateException("Call parseFile() or parseStr() before " + "render()"); } Data data; if (globalHdf != null) { // For legacy support we allow users to pass in this option to disable // the new modification protection for global HDF. data = new LocalAndGlobalData(localHdf.getData(), globalHdf.getData(), jSilver.getOptions() .getAllowGlobalDataModification()); } else { data = localHdf.getData(); } Appendable buffer = jSilver.createAppendableBuffer(); try { Appendable output = buffer; // For Clearsilver compatibility we check this HDF variable to see if we // need to turn on whitespace stripping. The preferred approach would be // to turn it on in the JSilverOptions passed to JSilverFactory int wsStripLevel = localHdf.getIntValue("ClearSilver.WhiteSpaceStrip", 0); if (wsStripLevel > 0) { output = new HtmlWhiteSpaceStripper(output, wsStripLevel); } jSilver.render(template, data, output, resourceLoaderAdaptor); return output.toString(); } catch (IOException ioe) { throw new RuntimeException(ioe); } finally { jSilver.releaseAppendableBuffer(buffer); } } @Override public CSFileLoader getFileLoader() { return csFileLoader; } @Override public void setFileLoader(CSFileLoader fileLoader) { if (fileLoader == null && csFileLoader == null) { return; } if (fileLoader != null && fileLoader.equals(csFileLoader)) { return; } csFileLoader = fileLoader; resourceLoaderAdaptor = new ResourceLoaderAdaptor(localHdf, loadPathCache, csFileLoader); } } src/com/google/clearsilver/jsilver/adaptor/JHdf.java0100644 0000000 0000000 00000015667 13662126234 021553 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.adaptor; import com.google.clearsilver.jsilver.JSilverOptions; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataFactory; import com.google.clearsilver.jsilver.data.Parser; import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException; import org.clearsilver.CSFileLoader; import org.clearsilver.HDF; import java.io.FileWriter; import java.io.IOException; import java.io.StringReader; import java.util.Date; import java.util.TimeZone; /** * Adaptor that wraps a JSilver Data object so it can be used as an HDF object. */ public class JHdf implements HDF { // Only changed to null on close() private Data data; private final DataFactory dataFactory; private final JSilverOptions options; private final LoadPathToFileCache loadPathCache; private ResourceLoaderAdaptor resourceLoader; JHdf(Data data, DataFactory dataFactory, LoadPathToFileCache loadPathCache, JSilverOptions options) { this.data = data; this.loadPathCache = loadPathCache; this.dataFactory = dataFactory; this.options = options; this.resourceLoader = new ResourceLoaderAdaptor(this, loadPathCache, null); } static JHdf cast(HDF hdf) { if (!(hdf instanceof JHdf)) { throw new IllegalArgumentException("HDF object not of type JHdf. " + "Make sure you use the same ClearsilverFactory to construct all " + "related HDF and CS objects."); } return (JHdf) hdf; } Data getData() { return data; } ResourceLoaderAdaptor getResourceLoaderAdaptor() { return resourceLoader; } @Override public void close() { // This looks pointless but it actually reduces the lifetime of the large // Data object as far as the garbage collector is concerned and // dramatically improves performance. data = null; } @Override public boolean readFile(String filename) throws IOException { dataFactory.loadData(filename, resourceLoader, data); return false; } @Override public CSFileLoader getFileLoader() { return resourceLoader.getCSFileLoader(); } @Override public void setFileLoader(CSFileLoader fileLoader) { this.resourceLoader = new ResourceLoaderAdaptor(this, loadPathCache, fileLoader); } @Override public boolean writeFile(String filename) throws IOException { FileWriter writer = new FileWriter(filename); try { data.write(writer, 2); } finally { writer.close(); } return true; } @Override public boolean readString(String content) { Parser hdfParser = dataFactory.getParser(); try { hdfParser.parse(new StringReader(content), data, new Parser.ErrorHandler() { public void error(int line, String lineContent, String fileName, String errorMessage) { throw new JSilverBadSyntaxException("HDF parsing error : '" + errorMessage + "'", lineContent, fileName, line, JSilverBadSyntaxException.UNKNOWN_POSITION, null); } }, resourceLoader, null, options.getIgnoreAttributes()); return true; } catch (IOException e) { return false; } } @Override public int getIntValue(String hdfName, int defaultValue) { return data.getIntValue(hdfName, defaultValue); } @Override public String getValue(String hdfName, String defaultValue) { return data.getValue(hdfName, defaultValue); } @Override public void setValue(String hdfName, String value) { data.setValue(hdfName, value); } @Override public void removeTree(String hdfName) { data.removeTree(hdfName); } @Override public void setSymLink(String hdfNameSrc, String hdfNameDest) { data.setSymlink(hdfNameSrc, hdfNameDest); } @Override public void exportDate(String hdfName, TimeZone timeZone, Date date) { throw new UnsupportedOperationException("TBD"); } @Override public void exportDate(String hdfName, String tz, int tt) { throw new UnsupportedOperationException("TBD"); } @Override public HDF getObj(String hdfpath) { Data d = data.getChild(hdfpath); return d == null ? null : new JHdf(d, dataFactory, loadPathCache, options); } @Override public HDF getChild(String hdfpath) { Data d = data.getChild(hdfpath); if (d == null) { return null; } for (Data child : d.getChildren()) { if (child.isFirstSibling()) { return new JHdf(child, dataFactory, loadPathCache, options); } else { // The first child returned should be the first sibling. Throw an error // if not. throw new IllegalStateException("First child was not first sibling."); } } return null; } @Override public HDF getRootObj() { Data root = data.getRoot(); if (root == data) { return this; } else { return new JHdf(root, dataFactory, loadPathCache, options); } } @Override public boolean belongsToSameRoot(HDF hdf) { JHdf jHdf = cast(hdf); return this.data.getRoot() == jHdf.data.getRoot(); } @Override public HDF getOrCreateObj(String hdfpath) { return new JHdf(data.createChild(hdfpath), dataFactory, loadPathCache, options); } @Override public String objName() { return data.getName(); } @Override public String objValue() { return data.getValue(); } @Override public HDF objChild() { for (Data child : data.getChildren()) { if (child.isFirstSibling()) { return new JHdf(child, dataFactory, loadPathCache, options); } } return null; } @Override public HDF objNext() { Data next = data.getNextSibling(); return next == null ? null : new JHdf(next, dataFactory, loadPathCache, options); } @Override public void copy(String hdfpath, HDF src) { JHdf srcJHdf = cast(src); if (hdfpath.equals("")) { data.copy(srcJHdf.data); } else { data.copy(hdfpath, srcJHdf.data); } } @Override public String dump() { StringBuilder sb = new StringBuilder(); try { data.write(sb, 0); return sb.toString(); } catch (IOException e) { return null; } } @Override public String writeString() { return dump(); } @Override public String toString() { return dump(); } /** * JSilver-specific method that optimizes the underlying data object. Should only be used on * long-lived HDF objects (e.g. global HDF). */ public void optimize() { data.optimize(); } } src/com/google/clearsilver/jsilver/adaptor/JSilverFactory.java0100644 0000000 0000000 00000006477 13662126234 023645 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.adaptor; import com.google.clearsilver.jsilver.JSilver; import com.google.clearsilver.jsilver.JSilverOptions; import com.google.clearsilver.jsilver.data.DataFactory; import com.google.clearsilver.jsilver.data.DefaultData; import com.google.clearsilver.jsilver.data.HDFDataFactory; import org.clearsilver.ClearsilverFactory; import org.clearsilver.DelegatedHdf; import org.clearsilver.HDF; /** * ClearsilverFactory that adapts JSilver for use as any HDF/CS rendering engine. */ public class JSilverFactory implements ClearsilverFactory { private static final JSilverOptions DEFAULT_OPTIONS = new JSilverOptions(); private final boolean unwrapDelegatedHdfs; private final JSilver jSilver; private final JSilverOptions options; private final DataFactory dataFactory; private final LoadPathToFileCache loadPathCache; /** * Default constructor. enables unwrapping of DelegatedHdfs. */ public JSilverFactory() { this(DEFAULT_OPTIONS); } public JSilverFactory(JSilverOptions options) { this(options, true); } public JSilverFactory(JSilverOptions options, boolean unwrapDelegatedHdfs) { this(new JSilver(null, options), unwrapDelegatedHdfs); } /** * This constructor is available for those who already use JSilver and want to use the same * attributes and caches for their Java Clearsilver Framework code. Users who use only JCF should * use a different constructor. * * @param jSilver existing instance of JSilver to use for parsing and rendering. * @param unwrapDelegatedHdfs whether to unwrap DelegetedHdfs or not before casting. */ public JSilverFactory(JSilver jSilver, boolean unwrapDelegatedHdfs) { this.unwrapDelegatedHdfs = unwrapDelegatedHdfs; this.jSilver = jSilver; this.options = jSilver.getOptions(); if (this.options.getLoadPathCacheSize() == 0) { this.loadPathCache = null; } else { this.loadPathCache = new LoadPathToFileCache(this.options.getLoadPathCacheSize()); } this.dataFactory = new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy()); } @Override public JCs newCs(HDF hdf) { if (unwrapDelegatedHdfs) { hdf = DelegatedHdf.getFullyUnwrappedHdf(hdf); } return new JCs(JHdf.cast(hdf), jSilver, loadPathCache); } @Override public JCs newCs(HDF hdf, HDF globalHdf) { if (unwrapDelegatedHdfs) { hdf = DelegatedHdf.getFullyUnwrappedHdf(hdf); globalHdf = DelegatedHdf.getFullyUnwrappedHdf(globalHdf); } JCs cs = new JCs(JHdf.cast(hdf), jSilver, loadPathCache); cs.setGlobalHDF(globalHdf); return cs; } @Override public JHdf newHdf() { return new JHdf(new DefaultData(), dataFactory, loadPathCache, options); } } src/com/google/clearsilver/jsilver/adaptor/LoadPathToFileCache.java0100644 0000000 0000000 00000012146 13662126234 024450 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.adaptor; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * This class implements a cache of a list of loadpaths and a file name to the absolute file name * where the file is located on the filesystem. The purpose is to avoid filesystem calls for common * operations, like in which of these directories does this file exist? This class is threadsafe. * * Some of this code is copied from {@link com.google.clearsilver.base.CSFileCache}. */ public class LoadPathToFileCache { private final LRUCache cache; private final ReadWriteLock cacheLock = new ReentrantReadWriteLock(); public LoadPathToFileCache(int capacity) { cache = new LRUCache(capacity); } /** * Lookup in the cache to see if we have a mapping from the given loadpaths and filename to an * absolute file path. * * @param loadPaths the ordered list of directories to search for the file. * @param filename the name of the file. * @return the absolute filepath location of the file, or {@code null} if not in the cache. */ public String lookup(List loadPaths, String filename) { String filePathMapKey = makeCacheKey(loadPaths, filename); cacheLock.readLock().lock(); try { return cache.get(filePathMapKey); } finally { cacheLock.readLock().unlock(); } } /** * Add a new mapping to the cache. * * @param loadPaths the ordered list of directories to search for the file. * @param filename the name of the file. * @param filePath the absolute filepath location of the file */ public void add(List loadPaths, String filename, String filePath) { String filePathMapKey = makeCacheKey(loadPaths, filename); cacheLock.writeLock().lock(); try { cache.put(filePathMapKey, filePath); } finally { cacheLock.writeLock().unlock(); } } public static String makeCacheKey(List loadPaths, String filename) { if (loadPaths == null) { throw new NullPointerException("Loadpaths cannot be null"); } if (filename == null) { throw new NullPointerException("Filename cannot be null"); } String loadPathsHash = Long.toHexString(hashLoadPath(loadPaths)); StringBuilder sb = new StringBuilder(loadPathsHash); sb.append('|').append(filename); return sb.toString(); } /** * Generate a hashCode to represent the ordered list of loadpaths. Used as a key into the fileMap * since a concatenation of the loadpaths is likely to be huge (greater than 1K) but very * repetitive. Algorithm comes from Effective Java by Joshua Bloch. *

* We don't use the builtin hashCode because it is only 32-bits, and while the expect different * values of loadpaths is very small, we want to minimize any chance of collision since we use the * hash as the key and throw away the loadpath list * * @param list an ordered list of loadpaths. * @return a long representing a hash of the loadpaths. */ static long hashLoadPath(List list) { long hash = 17; for (String path : list) { hash = 37 * hash + path.hashCode(); } return hash; } /** * This code is copied from {@link com.google.common.cache.LRUCache} but is distilled to basics in * order to not require importing Google code. Hopefully there is an open-source implementation, * although given the design of LinkHashMap, this is trivial. */ static class LRUCache extends LinkedHashMap { private final int capacity; LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity = capacity; } /** * {@inheritDoc} *

* Necessary to override because HashMap increases the capacity of the hashtable before * inserting the elements. However, here we have set the max capacity already and will instead * remove eldest elements instead of increasing capacity. */ @Override public void putAll(Map m) { for (Map.Entry e : m.entrySet()) { put(e.getKey(), e.getValue()); } } /** * This method is called by LinkedHashMap to check whether the eldest entry should be removed. * * @param eldest * @return true if element should be removed. */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > capacity; } } } src/com/google/clearsilver/jsilver/adaptor/ResourceLoaderAdaptor.java0100644 0000000 0000000 00000014755 13662126234 025166 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.adaptor; import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import org.clearsilver.CSFileLoader; import org.clearsilver.CSUtil; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.util.List; /** * Wrap a CSFileLoader with a ResourceLoader */ public class ResourceLoaderAdaptor implements ResourceLoader { private final JHdf hdf; private final LoadPathToFileCache loadPathCache; private final CSFileLoader csFileLoader; private List loadPaths; ResourceLoaderAdaptor(JHdf hdf, LoadPathToFileCache loadPathCache, CSFileLoader csFileLoader) { this.hdf = hdf; this.loadPathCache = loadPathCache; this.csFileLoader = csFileLoader; } @Override public Reader open(String name) throws IOException { if (csFileLoader != null) { if (hdf.getData() == null) { throw new IllegalStateException("HDF is already closed"); } return new StringReader(csFileLoader.load(hdf, name)); } else { File file = locateFile(name); if (file == null) { throw new FileNotFoundException("Could not locate file " + name); } return new InputStreamReader(new FileInputStream(file), "UTF-8"); } } @Override public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException { Reader reader = open(name); if (reader == null) { final StringBuffer text = new StringBuffer(); text.append("No file '"); text.append(name); text.append("' "); if (loadPaths == null || loadPaths.isEmpty()) { text.append("with no load paths"); } else if (loadPaths.size() == 1) { text.append("inside directory '"); text.append(loadPaths.get(0)); text.append("'"); } else { text.append("inside directories ( "); for (String path : getLoadPaths()) { text.append("'"); text.append(path); text.append("' "); } text.append(")"); } throw new JSilverTemplateNotFoundException(text.toString()); } else { return reader; } } /** * * @param name name of the file to locate. * @return a File object corresponding to the existing file or {@code null} if it does not exist. */ File locateFile(String name) { if (name.startsWith(File.separator)) { // Full path to file was given. File file = newFile(name); return file.exists() ? file : null; } File file = null; // loadPathCache is null when load path caching is disabled at the // JSilverFactory level. This is implied by setting cache size // to 0 using JSilverOptions.setLoadPathCacheSize(0). if (loadPathCache != null) { String filePath = loadPathCache.lookup(getLoadPaths(), name); if (filePath != null) { file = newFile(filePath); return file.exists() ? file : null; } } file = locateFile(getLoadPaths(), name); if (file != null && loadPathCache != null) { loadPathCache.add(getLoadPaths(), name, file.getAbsolutePath()); } return file; } /** * Given an ordered list of directories to look in, locate the specified file. Returns * null if file not found. *

* This is copied from {@link org.clearsilver.CSUtil#locateFile(java.util.List, String)} but has * one important difference. It calls our subclassable newFile method. * * @param loadPaths the ordered list of paths to search. * @param filename the name of the file. * @return a File object corresponding to the file. null if file not found. */ File locateFile(List loadPaths, String filename) { if (filename == null) { throw new NullPointerException("No filename provided"); } if (loadPaths == null) { throw new NullPointerException("No loadpaths provided."); } for (String path : loadPaths) { File file = newFile(path, filename); if (file.exists()) { return file; } } return null; } /** * Separate methods to allow tests to subclass and override File creation and return mocks or * fakes. */ File newFile(String filename) { return new File(filename); } File newFile(String path, String filename) { return new File(path, filename); } @Override public void close(Reader reader) throws IOException { reader.close(); } @Override public Object getKey(String filename) { if (filename.startsWith(File.separator)) { return filename; } else { File file = locateFile(filename); if (file == null) { // The file does not exist, use the full loadpath and file name as the // key. return LoadPathToFileCache.makeCacheKey(getLoadPaths(), filename); } else { return file.getAbsolutePath(); } } } /** * Some applications, e.g. online help, need to know when a file has changed due to a symlink * modification hence the use of {@link File#getCanonicalFile()}, if possible. */ @Override public Object getResourceVersionId(String filename) { File file = locateFile(filename); if (file == null) { return null; } String fullPath; try { fullPath = file.getCanonicalPath(); } catch (IOException e) { fullPath = file.getAbsolutePath(); } return String.format("%s@%s", fullPath, file.lastModified()); } final CSFileLoader getCSFileLoader() { return csFileLoader; } private synchronized List getLoadPaths() { if (loadPaths == null) { if (hdf.getData() == null) { throw new IllegalStateException("HDF is already closed"); } loadPaths = CSUtil.getLoadPaths(hdf, true); } return loadPaths; } } src/com/google/clearsilver/jsilver/autoescape/0040755 0000000 0000000 00000000000 13662126234 020560 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/autoescape/AutoEscapeContext.java0100644 0000000 0000000 00000046376 13662126234 025036 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.autoescape; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_CSS; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_JS; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_UNQUOTED_JS; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_URI; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_URI_START; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_HTML; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_JS; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_JS_UNQUOTED; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_STYLE; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_CSS; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_JS; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_UNQUOTED_JS; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_URI; import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_URI_START; import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException; import com.google.streamhtmlparser.ExternalState; import com.google.streamhtmlparser.HtmlParser; import com.google.streamhtmlparser.HtmlParserFactory; import com.google.streamhtmlparser.ParseException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; /** * Encapsulates auto escaping logic. */ public class AutoEscapeContext { /** * Map of content-type to corresponding {@code HtmlParser.Mode}, used by {@code setContentType} to * specify the content type of provided input. Valid values and the corresponding mode are:
* * * * * * * * * * * * * * * * * * * * * * * * * *
text/htmlHtmlParser.Mode.HTML
text/plainHtmlParser.Mode.HTML
application/javascriptHtmlParser.Mode.JS
application/jsonHtmlParser.Mode.JS
text/javascriptHtmlParser.Mode.JS
text/cssHtmlParser.Mode.CSS
* * @see #setContentType */ public static final Map CONTENT_TYPE_LIST; // These options are used to provide extra information to HtmlParserFactory.createParserInMode or // HtmlParserFactory.createParserInAttribute, which is required for certain modes. private static final HashSet quotedJsAttributeOption; private static final HashSet partialUrlAttributeOption; private static final HashSet jsModeOption; private HtmlParser htmlParser; static { quotedJsAttributeOption = new HashSet(); quotedJsAttributeOption.add(HtmlParserFactory.AttributeOptions.JS_QUOTED); partialUrlAttributeOption = new HashSet(); partialUrlAttributeOption.add(HtmlParserFactory.AttributeOptions.URL_PARTIAL); jsModeOption = new HashSet(); jsModeOption.add(HtmlParserFactory.ModeOptions.JS_QUOTED); CONTENT_TYPE_LIST = new HashMap(); CONTENT_TYPE_LIST.put("text/html", HtmlParser.Mode.HTML); CONTENT_TYPE_LIST.put("text/plain", HtmlParser.Mode.HTML); CONTENT_TYPE_LIST.put("application/javascript", HtmlParser.Mode.JS); CONTENT_TYPE_LIST.put("application/json", HtmlParser.Mode.JS); CONTENT_TYPE_LIST.put("text/javascript", HtmlParser.Mode.JS); CONTENT_TYPE_LIST.put("text/css", HtmlParser.Mode.CSS); } /** * Name of resource being auto escaped. Will be used in error and display messages. */ private String resourceName; public AutoEscapeContext() { this(EscapeMode.ESCAPE_AUTO, null); } /** * Create a new context in the state represented by mode. * * @param mode EscapeMode object. */ public AutoEscapeContext(EscapeMode mode) { this(mode, null); } /** * Create a new context in the state represented by mode. If a non-null resourceName is provided, * it will be used in displaying error messages. * * @param mode The initial EscapeMode for this context * @param resourceName Name of the resource being auto escaped. */ public AutoEscapeContext(EscapeMode mode, String resourceName) { this.resourceName = resourceName; htmlParser = createHtmlParser(mode); } /** * Create a new context that is a copy of the current state of this context. * * @return New {@code AutoEscapeContext} that is a snapshot of the current state of this context. */ public AutoEscapeContext cloneCurrentEscapeContext() { AutoEscapeContext autoEscapeContext = new AutoEscapeContext(); autoEscapeContext.resourceName = resourceName; autoEscapeContext.htmlParser = HtmlParserFactory.createParser(htmlParser); return autoEscapeContext; } /** * Sets the current position in the resource being auto escaped. Useful for generating detailed * error messages. * * @param line line number. * @param column column number within line. */ public void setCurrentPosition(int line, int column) { htmlParser.setLineNumber(line); htmlParser.setColumnNumber(column); } /** * Returns the name of the resource currently being auto escaped. */ public String getResourceName() { return resourceName; } /** * Returns the current line number within the resource being auto escaped. */ public int getLineNumber() { return htmlParser.getLineNumber(); } /** * Returns the current column number within the resource being auto escaped. */ public int getColumnNumber() { return htmlParser.getColumnNumber(); } private HtmlParser createHtmlParser(EscapeMode mode) { switch (mode) { case ESCAPE_AUTO: case ESCAPE_AUTO_HTML: return HtmlParserFactory.createParser(); case ESCAPE_AUTO_JS_UNQUOTED: // return AutoEscapeState.JS; } else { // // No quotes around the variable, hence it can inject arbitrary javascript. // So severely restrict the values it may contain. return AutoEscapeState.JS_UNQUOTED; } } // Inside an HTML tag or attribute name if (state.equals(HtmlParser.STATE_ATTR) || state.equals(HtmlParser.STATE_TAG)) { return AutoEscapeState.ATTR; // TODO: Need a strict validation function for tag and attribute names. } else if (state.equals(HtmlParser.STATE_VALUE)) { // Inside an HTML attribute value return getCurrentAttributeState(); } else if (state.equals(HtmlParser.STATE_COMMENT) || state.equals(HtmlParser.STATE_TEXT)) { // Default is assumed to be HTML body // Hello : return AutoEscapeState.HTML; } throw new JSilverAutoEscapingException("Invalid state received from HtmlParser: " + state.toString(), resourceName, htmlParser.getLineNumber(), htmlParser.getColumnNumber()); } private AutoEscapeState getCurrentAttributeState() { HtmlParser.ATTR_TYPE type = htmlParser.getAttributeType(); boolean attrQuoted = htmlParser.isAttributeQuoted(); switch (type) { case REGULAR: // : if (attrQuoted) { return AutoEscapeState.ATTR; } else { return AutoEscapeState.UNQUOTED_ATTR; } case URI: if (htmlParser.isUrlStart()) { // if (attrQuoted) { return AutoEscapeState.ATTR_URI_START; } else { return AutoEscapeState.UNQUOTED_ATTR_URI_START; } } else { // if (attrQuoted) { // TODO: Html escaping because that is what Clearsilver does right now. // May change this to url escaping soon. return AutoEscapeState.ATTR_URI; } else { return AutoEscapeState.UNQUOTED_ATTR_URI; } } case JS: if (htmlParser.isJavascriptQuoted()) { /* * Note: js_escape() hex encodes all html metacharacters. Therefore it is safe to not do * an HTML escape around this. */ if (attrQuoted) { // return AutoEscapeState.ATTR_JS; } else { // ');> return AutoEscapeState.UNQUOTED_ATTR_JS; } } else { if (attrQuoted) { /* */ return AutoEscapeState.ATTR_UNQUOTED_JS; } else { /* );> */ return AutoEscapeState.UNQUOTED_ATTR_UNQUOTED_JS; } } case STYLE: // : if (attrQuoted) { return AutoEscapeState.ATTR_CSS; } else { return AutoEscapeState.UNQUOTED_ATTR_CSS; } default: throw new JSilverAutoEscapingException("Invalid attribute type in HtmlParser: " + type, resourceName, htmlParser.getLineNumber(), htmlParser.getColumnNumber()); } } /** * Resets the state of the underlying html parser to a state consistent with the {@code * contentType} provided. This method should be used when the starting auto escaping context of a * resource cannot be determined from its contents - for example, a CSS stylesheet or a javascript * source file. * * @param contentType MIME type header representing the content being parsed. * @see #CONTENT_TYPE_LIST */ public void setContentType(String contentType) { HtmlParser.Mode mode = CONTENT_TYPE_LIST.get(contentType); if (mode == null) { throw new JSilverAutoEscapingException("Invalid content type specified: " + contentType, resourceName, htmlParser.getLineNumber(), htmlParser.getColumnNumber()); } htmlParser.resetMode(mode); } /** * Enum representing states of the data being parsed. * * This enumeration lists all the states in which autoescaping would have some effect. * */ public static enum AutoEscapeState { HTML("html", ESCAPE_AUTO_HTML), JS("js", ESCAPE_AUTO_JS), STYLE("css", ESCAPE_AUTO_STYLE), JS_UNQUOTED( "js_check_number", ESCAPE_AUTO_JS_UNQUOTED), ATTR("html", ESCAPE_AUTO_ATTR), UNQUOTED_ATTR( "html_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR), ATTR_URI("html", ESCAPE_AUTO_ATTR_URI), UNQUOTED_ATTR_URI( "html_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR_URI), ATTR_URI_START("url_validate", ESCAPE_AUTO_ATTR_URI_START), UNQUOTED_ATTR_URI_START("url_validate_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR_URI_START), ATTR_JS("js", ESCAPE_AUTO_ATTR_JS), ATTR_UNQUOTED_JS( "js_check_number", ESCAPE_AUTO_ATTR_UNQUOTED_JS), UNQUOTED_ATTR_JS("js_attr_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR_JS), UNQUOTED_ATTR_UNQUOTED_JS("js_check_number", ESCAPE_AUTO_UNQUOTED_ATTR_UNQUOTED_JS), ATTR_CSS("css", ESCAPE_AUTO_ATTR_CSS), UNQUOTED_ATTR_CSS( "css_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR_CSS); private final String functionName; private final EscapeMode escapeMode; private AutoEscapeState(String functionName, EscapeMode mode) { this.functionName = functionName; this.escapeMode = mode; } public String getFunctionName() { return functionName; } public EscapeMode getEscapeMode() { return escapeMode; } } } src/com/google/clearsilver/jsilver/autoescape/AutoEscapeOptions.java0100644 0000000 0000000 00000002435 13662126234 025031 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.autoescape; /** * Global configuration options specific to auto escaping. */ public class AutoEscapeOptions { private boolean propagateEscapeStatus = false; private boolean logEscapedVariables = false; public boolean getLogEscapedVariables() { return logEscapedVariables; } public void setLogEscapedVariables(boolean logEscapedVariables) { this.logEscapedVariables = logEscapedVariables; } public boolean getPropagateEscapeStatus() { return propagateEscapeStatus; } public void setPropagateEscapeStatus(boolean propagateEscapeStatus) { this.propagateEscapeStatus = propagateEscapeStatus; } } src/com/google/clearsilver/jsilver/autoescape/EscapeMode.java0100644 0000000 0000000 00000011704 13662126234 023430 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.autoescape; import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException; public enum EscapeMode { ESCAPE_NONE("none", false), ESCAPE_HTML("html", false), ESCAPE_JS("js", false), ESCAPE_URL("url", false), ESCAPE_IS_CONSTANT("constant", false), // These modes are used as starting modes, and a parser parses the // subsequent template contents to determine the right escaping command to use. ESCAPE_AUTO("auto", true), // Identical to ESCAPE_AUTO_HTML ESCAPE_AUTO_HTML("auto_html", true), ESCAPE_AUTO_JS("auto_js", true), ESCAPE_AUTO_JS_UNQUOTED( "auto_js_unquoted", true), ESCAPE_AUTO_STYLE("auto_style", true), ESCAPE_AUTO_ATTR( "auto_attr", true), ESCAPE_AUTO_UNQUOTED_ATTR("auto_attr_unquoted", true), ESCAPE_AUTO_ATTR_URI( "auto_attr_uri", true), ESCAPE_AUTO_UNQUOTED_ATTR_URI("auto_attr_uri_unquoted", true), ESCAPE_AUTO_ATTR_URI_START( "auto_attr_uri_start", true), ESCAPE_AUTO_UNQUOTED_ATTR_URI_START( "auto_attr_uri_start_unquoted", true), ESCAPE_AUTO_ATTR_JS("auto_attr_js", true), ESCAPE_AUTO_ATTR_UNQUOTED_JS( "auto_attr_unquoted_js", true), ESCAPE_AUTO_UNQUOTED_ATTR_JS("auto_attr_js_unquoted", true), ESCAPE_AUTO_UNQUOTED_ATTR_UNQUOTED_JS( "auto_attr_js_unquoted_js", true), ESCAPE_AUTO_ATTR_CSS("auto_attr_style", true), ESCAPE_AUTO_UNQUOTED_ATTR_CSS( "auto_attr_style_unquoted", true); private String escapeCmd; private boolean autoEscaper; private EscapeMode(String escapeCmd, boolean autoEscaper) { this.escapeCmd = escapeCmd; this.autoEscaper = autoEscaper; } /** * This function maps the type of escaping requested (escapeCmd) to the appropriate EscapeMode. If * no explicit escaping is requested, but doAutoEscape is true, the function chooses auto escaping * (EscapeMode.ESCAPE_AUTO). This mirrors the behaviour of ClearSilver. * * @param escapeCmd A string indicating type of escaping requested. * @param doAutoEscape Whether auto escaping should be applied if escapeCmd is null. Corresponds * to the Config.AutoEscape HDF variable. * @return */ public static EscapeMode computeEscapeMode(String escapeCmd, boolean doAutoEscape) { EscapeMode escapeMode; // If defined, the explicit escaping mode (configured using "Config.VarEscapeMode") // takes preference over auto escaping if (escapeCmd != null) { for (EscapeMode e : EscapeMode.values()) { if (e.escapeCmd.equals(escapeCmd)) { return e; } } throw new JSilverAutoEscapingException("Invalid escaping mode specified: " + escapeCmd); } else { if (doAutoEscape) { escapeMode = ESCAPE_AUTO; } else { escapeMode = ESCAPE_NONE; } return escapeMode; } } /** * Calls {@link #computeEscapeMode(String, boolean)} with {@code doAutoEscape = false}. * * @param escapeCmd A string indicating type of escaping requested. * @return EscapeMode * @throws JSilverAutoEscapingException if {@code escapeCmd} is not recognized. */ public static EscapeMode computeEscapeMode(String escapeCmd) { return computeEscapeMode(escapeCmd, false); } /** * Computes the EscapeMode of the result of concatenating two values. The EscapeModes of the two * values are provided by {@code left} and {@code right} respectively. For now, if either of the * values was escaped or a constant, we return {@code ESCAPE_IS_CONSTANT}. This is how ClearSilver * behaves. * * @return {@code ESCAPE_NONE} if either of the values was not escaped or constant. {@code * ESCAPE_IS_CONSTANT} otherwise. */ public static EscapeMode combineModes(EscapeMode left, EscapeMode right) { if (left.equals(ESCAPE_NONE) || right.equals(ESCAPE_NONE)) { // If either of the values has not been escaped, // do not trust the result. return ESCAPE_NONE; } else { // For now, indicate that this result is always safe in all contexts. // This is what ClearSilver does. We may introduce a stricter autoescape // rule later on which also requires that the escaping be the same as the // context its used in. return ESCAPE_IS_CONSTANT; } } public boolean isAutoEscapingMode() { return autoEscaper; } // TODO: Simplify enum names, and just use toString() instead. public String getEscapeCommand() { return escapeCmd; } } src/com/google/clearsilver/jsilver/compatibility/0040755 0000000 0000000 00000000000 13662126234 021300 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/compatibility/ClearsilverRenderer.java0100644 0000000 0000000 00000011335 13662126234 026105 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compatibility; import com.google.clearsilver.jsilver.TemplateRenderer; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.exceptions.JSilverException; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import org.clearsilver.CS; import org.clearsilver.CSFileLoader; import org.clearsilver.ClearsilverFactory; import org.clearsilver.HDF; import org.clearsilver.jni.JniClearsilverFactory; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; /** * A {@link TemplateRenderer} implemented using ClearSilver itself. */ public class ClearsilverRenderer implements TemplateRenderer { private final ClearsilverFactory factory; private final ResourceLoader defaultResourceLoader; /** * Creates an implementation using the provided ClearSilver factory and JSilver resource loader. */ public ClearsilverRenderer(ClearsilverFactory factory, ResourceLoader resourceLoader) { this.factory = factory; this.defaultResourceLoader = resourceLoader; } /** * Creates a JSilver implementation using the JNI ClearSilver factory and provided JSilver * resource loader. */ public ClearsilverRenderer(ResourceLoader resourceLoader) { this(new JniClearsilverFactory(), resourceLoader); } @Override public void render(String templateName, Data data, Appendable output, final ResourceLoader resourceLoader) throws IOException, JSilverException { CSFileLoader fileLoader = new CSFileLoader() { @Override public String load(HDF hdf, String filename) throws IOException { return loadResource(filename, resourceLoader); } }; HDF hdf = factory.newHdf(); try { // Copy the Data into the HDF. hdf.readString(data.toString()); CS cs = factory.newCs(hdf); try { cs.setFileLoader(fileLoader); cs.parseFile(templateName); output.append(cs.render()); } finally { cs.close(); } } finally { hdf.close(); } } @Override public void render(String templateName, Data data, Appendable output) throws IOException, JSilverException { render(templateName, data, output, defaultResourceLoader); } @Override public String render(String templateName, Data data) throws IOException, JSilverException { Appendable output = new StringBuilder(8192); render(templateName, data, output); return output.toString(); } @Override public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader) throws IOException, JSilverException { throw new UnsupportedOperationException("ClearsilverRenderer only expects " + "template names, not Templates"); } @Override public void render(Template template, Data data, Appendable output) throws IOException, JSilverException { render(template, data, output, defaultResourceLoader); } @Override public String render(Template template, Data data) throws IOException, JSilverException { Appendable output = new StringBuilder(8192); render(template, data, output); return output.toString(); } @Override public void renderFromContent(String content, Data data, Appendable output) throws IOException, JSilverException { throw new UnsupportedOperationException(); } @Override public String renderFromContent(String content, Data data) throws IOException, JSilverException { Appendable output = new StringBuilder(8192); renderFromContent(content, data, output); return output.toString(); } /** * @return the contents of a resource, or null if the resource was not found. */ private String loadResource(String filename, ResourceLoader resourceLoader) throws IOException { Reader reader = resourceLoader.open(filename); if (reader == null) { throw new FileNotFoundException(filename); } StringBuilder sb = new StringBuilder(); char buf[] = new char[4096]; int count; while ((count = reader.read(buf)) != -1) { sb.append(buf, 0, count); } return sb.toString(); } } src/com/google/clearsilver/jsilver/compiler/0040755 0000000 0000000 00000000000 13662126234 020241 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/compiler/BaseCompiledTemplate.java0100644 0000000 0000000 00000025646 13662126234 025141 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataContext; import com.google.clearsilver.jsilver.data.DefaultDataContext; import com.google.clearsilver.jsilver.data.TypeConverter; import com.google.clearsilver.jsilver.exceptions.ExceptionUtil; import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.template.DefaultRenderingContext; import com.google.clearsilver.jsilver.template.Macro; import com.google.clearsilver.jsilver.template.RenderingContext; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; import com.google.clearsilver.jsilver.values.Value; import java.io.IOException; import java.util.Collections; /** * Base class providing help to generated templates. * * Note, many of the methods are public as they are also used by macros. */ public abstract class BaseCompiledTemplate implements Template { private FunctionExecutor functionExecutor; private String templateName; private TemplateLoader templateLoader; private EscapeMode escapeMode = EscapeMode.ESCAPE_NONE; private AutoEscapeOptions autoEscapeOptions; public void setFunctionExecutor(FunctionExecutor functionExecutor) { this.functionExecutor = functionExecutor; } public void setTemplateName(String templateName) { this.templateName = templateName; } public void setTemplateLoader(TemplateLoader templateLoader) { this.templateLoader = templateLoader; } /** * Set auto escaping options so they can be passed to the rendering context. * * @see AutoEscapeOptions */ public void setAutoEscapeOptions(AutoEscapeOptions autoEscapeOptions) { this.autoEscapeOptions = autoEscapeOptions; } @Override public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException { render(createRenderingContext(data, out, resourceLoader)); } @Override public RenderingContext createRenderingContext(Data data, Appendable out, ResourceLoader resourceLoader) { DataContext dataContext = new DefaultDataContext(data); return new DefaultRenderingContext(dataContext, resourceLoader, out, functionExecutor, autoEscapeOptions); } @Override public String getTemplateName() { return templateName; } /** * Sets the EscapeMode in which this template was generated. * * @param mode EscapeMode */ public void setEscapeMode(EscapeMode mode) { this.escapeMode = mode; } @Override public EscapeMode getEscapeMode() { return escapeMode; } @Override public String getDisplayName() { return templateName; } /** * Verify that the loop arguments are valid. If not, we will skip the loop. */ public static boolean validateLoopArgs(int start, int end, int increment) { if (increment == 0) { return false; // No increment. Avoid infinite loop. } if (increment > 0 && start > end) { return false; // Incrementing the wrong way. Avoid infinite loop. } if (increment < 0 && start < end) { return false; // Incrementing the wrong way. Avoid infinite loop. } return true; } public static boolean exists(Data data) { return TypeConverter.exists(data); } public static int asInt(String value) { return TypeConverter.asNumber(value); } public static int asInt(int value) { return value; } public static int asInt(boolean value) { return value ? 1 : 0; } public static int asInt(Value value) { return value.asNumber(); } public static int asInt(Data data) { return TypeConverter.asNumber(data); } public static String asString(String value) { return value; } public static String asString(int value) { return Integer.toString(value); } public static String asString(boolean value) { return value ? "1" : "0"; } public static String asString(Value value) { return value.asString(); } public static String asString(Data data) { return TypeConverter.asString(data); } public static Value asValue(String value) { // Compiler mode does not use the Value's escapeMode or partiallyEscaped // variables. TemplateTranslator uses other means to determine the proper // escaping to apply. So just set the default escaping flags here. return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false); } public static Value asValue(int value) { // Compiler mode does not use the Value's escapeMode or partiallyEscaped // variables. TemplateTranslator uses other means to determine the proper // escaping to apply. So just set the default escaping flags here. return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false); } public static Value asValue(boolean value) { // Compiler mode does not use the Value's escapeMode or partiallyEscaped // variables. TemplateTranslator uses other means to determine the proper // escaping to apply. So just set the default escaping flags here. return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false); } public static Value asValue(Value value) { return value; } public static Value asVariableValue(String variableName, DataContext context) { return Value.variableValue(variableName, context); } public static boolean asBoolean(boolean value) { return value; } public static boolean asBoolean(String value) { return TypeConverter.asBoolean(value); } public static boolean asBoolean(int value) { return value != 0; } public static boolean asBoolean(Value value) { return value.asBoolean(); } public static boolean asBoolean(Data data) { return TypeConverter.asBoolean(data); } /** * Gets the name of the node for writing. Used by cs name command. Returns empty string if not * found. */ public static String getNodeName(Data data) { return data == null ? "" : data.getSymlink().getName(); } /** * Returns child nodes of parent. Parent may be null, in which case an empty iterable is returned. */ public Iterable getChildren(Data parent) { if (parent == null) { return Collections.emptySet(); } else { return parent.getChildren(); } } protected TemplateLoader getTemplateLoader() { return templateLoader; } public abstract class CompiledMacro implements Macro { private final String macroName; private final String[] argumentsNames; protected CompiledMacro(String macroName, String... argumentsNames) { this.macroName = macroName; this.argumentsNames = argumentsNames; } @Override public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException { render(createRenderingContext(data, out, resourceLoader)); } @Override public RenderingContext createRenderingContext(Data data, Appendable out, ResourceLoader resourceLoader) { return BaseCompiledTemplate.this.createRenderingContext(data, out, resourceLoader); } @Override public String getTemplateName() { return BaseCompiledTemplate.this.getTemplateName(); } @Override public String getMacroName() { return macroName; } @Override public String getArgumentName(int index) { if (index >= argumentsNames.length) { // TODO: Make sure this behavior of failing if too many // arguments are passed to a macro is consistent with JNI / interpreter. throw new JSilverInterpreterException("Too many arguments supplied to macro " + macroName); } return argumentsNames[index]; } public int getArgumentCount() { return argumentsNames.length; } protected TemplateLoader getTemplateLoader() { return templateLoader; } @Override public EscapeMode getEscapeMode() { return BaseCompiledTemplate.this.getEscapeMode(); } @Override public String getDisplayName() { return BaseCompiledTemplate.this.getDisplayName() + ":" + macroName; } } /** * Code common to all three include commands. * * @param templateName String representing name of file to include. * @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template * loader should be ignored, {@code false} otherwise. * @param context Rendering context to use for the included template. */ protected void include(String templateName, boolean ignoreMissingFile, RenderingContext context) { if (!context.pushIncludeStackEntry(templateName)) { throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context .getIncludedTemplateNames())); } loadAndRenderIncludedTemplate(templateName, ignoreMissingFile, context); if (!context.popIncludeStackEntry(templateName)) { // Include stack trace is corrupted throw new IllegalStateException("Unable to find on include stack: " + templateName); } } // This method should ONLY be called from include() private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile, RenderingContext context) { Template template = null; try { template = templateLoader.load(templateName, context.getResourceLoader(), context .getAutoEscapeMode()); } catch (RuntimeException e) { if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) { return; } else { throw e; } } // Intepret loaded template. try { template.render(context); } catch (IOException e) { throw new JSilverInterpreterException(e.getMessage()); } } private String createIncludeLoopErrorMessage(String templateName, Iterable includeStack) { StringBuilder message = new StringBuilder(); message.append("File included twice: "); message.append(templateName); message.append(" Include stack:"); for (String fileName : includeStack) { message.append("\n -> "); message.append(fileName); } message.append("\n -> "); message.append(templateName); return message.toString(); } } src/com/google/clearsilver/jsilver/compiler/CompilingClassLoader.java0100644 0000000 0000000 00000016607 13662126234 025151 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import java.net.URISyntaxException; import java.net.URI; import java.io.IOException; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import static java.util.Collections.singleton; import java.util.Map; import java.util.HashMap; import java.util.List; import java.util.LinkedList; import javax.tools.JavaCompiler; import javax.tools.ToolProvider; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.JavaFileManager; import javax.tools.ForwardingJavaFileManager; import javax.tools.FileObject; import javax.tools.DiagnosticListener; /** * This is a Java ClassLoader that will attempt to load a class from a string of source code. * *

Example

* *
 * String className = "com.foo.MyClass";
 * String classSource =
 *   "package com.foo;\n" +
 *   "public class MyClass implements Runnable {\n" +
 *   "  @Override public void run() {\n" +
 *   "    System.out.println(\"Hello world\");\n" +
 *   "  }\n" +
 *   "}";
 *
 * // Load class from source.
 * ClassLoader classLoader = new CompilingClassLoader(
 *     parentClassLoader, className, classSource);
 * Class myClass = classLoader.loadClass(className);
 *
 * // Use it.
 * Runnable instance = (Runnable)myClass.newInstance();
 * instance.run();
 * 
* * Only one chunk of source can be compiled per instance of CompilingClassLoader. If you need to * compile more, create multiple CompilingClassLoader instances. * * Uses Java 1.6's in built compiler API. * * If the class cannot be compiled, loadClass() will throw a ClassNotFoundException and log the * compile errors to System.err. If you don't want the messages logged, or want to explicitly handle * the messages you can provide your own {@link javax.tools.DiagnosticListener} through * {#setDiagnosticListener()}. * * @see java.lang.ClassLoader * @see javax.tools.JavaCompiler */ public class CompilingClassLoader extends ClassLoader { /** * Thrown when code cannot be compiled. */ public static class CompilerException extends Exception { public CompilerException(String message) { super(message); } } private Map byteCodeForClasses = new HashMap(); private static final URI EMPTY_URI; static { try { // Needed to keep SimpleFileObject constructor happy. EMPTY_URI = new URI(""); } catch (URISyntaxException e) { throw new Error(e); } } /** * @param parent Parent classloader to resolve dependencies from. * @param className Name of class to compile. eg. "com.foo.MyClass". * @param sourceCode Java source for class. e.g. "package com.foo; class MyClass { ... }". * @param diagnosticListener Notified of compiler errors (may be null). */ public CompilingClassLoader(ClassLoader parent, String className, CharSequence sourceCode, DiagnosticListener diagnosticListener) throws CompilerException { super(parent); if (!compileSourceCodeToByteCode(className, sourceCode, diagnosticListener)) { throw new CompilerException("Could not compile " + className); } } /** * Override ClassLoader's class resolving method. Don't call this directly, instead use * {@link ClassLoader#loadClass(String)}. */ @Override public Class findClass(String name) throws ClassNotFoundException { ByteArrayOutputStream byteCode = byteCodeForClasses.get(name); if (byteCode == null) { throw new ClassNotFoundException(name); } return defineClass(name, byteCode.toByteArray(), 0, byteCode.size()); } /** * @return Whether compilation was successful. */ private boolean compileSourceCodeToByteCode(String className, CharSequence sourceCode, DiagnosticListener diagnosticListener) { JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); // Set up the in-memory filesystem. InMemoryFileManager fileManager = new InMemoryFileManager(javaCompiler.getStandardFileManager(null, null, null)); JavaFileObject javaFile = new InMemoryJavaFile(className, sourceCode); // Javac option: remove these when the javac zip impl is fixed // (http://b/issue?id=1822932) System.setProperty("useJavaUtilZip", "true"); // setting value to any non-null string List options = new LinkedList(); // this is ignored by javac currently but useJavaUtilZip should be // a valid javac XD option, which is another bug options.add("-XDuseJavaUtilZip"); // Now compile! JavaCompiler.CompilationTask compilationTask = javaCompiler.getTask(null, // Null: log any // unhandled errors to // stderr. fileManager, diagnosticListener, options, null, singleton(javaFile)); return compilationTask.call(); } /** * Provides an in-memory representation of JavaFileManager abstraction, so we do not need to write * any files to disk. * * When files are written to, rather than putting the bytes on disk, they are appended to buffers * in byteCodeForClasses. * * @see javax.tools.JavaFileManager */ private class InMemoryFileManager extends ForwardingJavaFileManager { public InMemoryFileManager(JavaFileManager fileManager) { super(fileManager); } @Override public JavaFileObject getJavaFileForOutput(Location location, final String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { return new SimpleJavaFileObject(EMPTY_URI, kind) { public OutputStream openOutputStream() throws IOException { ByteArrayOutputStream outputStream = byteCodeForClasses.get(className); if (outputStream != null) { throw new IllegalStateException("Cannot write more than once"); } // Reasonable size for a simple .class. outputStream = new ByteArrayOutputStream(256); byteCodeForClasses.put(className, outputStream); return outputStream; } }; } } private static class InMemoryJavaFile extends SimpleJavaFileObject { private final CharSequence sourceCode; public InMemoryJavaFile(String className, CharSequence sourceCode) { super(makeUri(className), Kind.SOURCE); this.sourceCode = sourceCode; } private static URI makeUri(String className) { try { return new URI(className.replaceAll("\\.", "/") + Kind.SOURCE.extension); } catch (URISyntaxException e) { throw new RuntimeException(e); // Not sure what could cause this. } } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return sourceCode; } } } src/com/google/clearsilver/jsilver/compiler/EscapingEvaluator.java0100644 0000000 0000000 00000031062 13662126234 024517 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression; import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn; import static com.google.clearsilver.jsilver.compiler.JavaExpression.string; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAddExpression; import com.google.clearsilver.jsilver.syntax.node.AAndExpression; import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression; import com.google.clearsilver.jsilver.syntax.node.ADescendVariable; import com.google.clearsilver.jsilver.syntax.node.ADivideExpression; import com.google.clearsilver.jsilver.syntax.node.AEqExpression; import com.google.clearsilver.jsilver.syntax.node.AExistsExpression; import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression; import com.google.clearsilver.jsilver.syntax.node.AGtExpression; import com.google.clearsilver.jsilver.syntax.node.AGteExpression; import com.google.clearsilver.jsilver.syntax.node.AHexExpression; import com.google.clearsilver.jsilver.syntax.node.ALtExpression; import com.google.clearsilver.jsilver.syntax.node.ALteExpression; import com.google.clearsilver.jsilver.syntax.node.AModuloExpression; import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression; import com.google.clearsilver.jsilver.syntax.node.ANameVariable; import com.google.clearsilver.jsilver.syntax.node.ANeExpression; import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression; import com.google.clearsilver.jsilver.syntax.node.ANotExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression; import com.google.clearsilver.jsilver.syntax.node.AOrExpression; import com.google.clearsilver.jsilver.syntax.node.AStringExpression; import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression; import com.google.clearsilver.jsilver.syntax.node.AVariableExpression; import com.google.clearsilver.jsilver.syntax.node.PExpression; import java.util.LinkedList; /** * Generates a JavaExpression to determine whether a given CS expression should be escaped before * displaying. If propagateEscapeStatus is enabled, string and numeric literals are not escaped, nor * is the output of an escaping function. If not, any expression that contains an escaping function * is not escaped. This maintains compatibility with the way ClearSilver works. */ public class EscapingEvaluator extends DepthFirstAdapter { private JavaExpression currentEscapingExpression; private boolean propagateEscapeStatus; private final VariableTranslator variableTranslator; public EscapingEvaluator(VariableTranslator variableTranslator) { super(); this.variableTranslator = variableTranslator; } /** * Returns a JavaExpression that can be used to decide whether a given variable should be escaped. * * @param expression variable expression to be evaluated. * @param propagateEscapeStatus Whether to propagate the variable's escape status. * * @return Returns a {@code JavaExpression} representing a boolean expression that evaluates to * {@code true} if {@code expression} should be exempted from escaping and {@code false} * otherwise. */ public JavaExpression computeIfExemptFromEscaping(PExpression expression, boolean propagateEscapeStatus) { if (propagateEscapeStatus) { return computeForPropagateStatus(expression); } return computeEscaping(expression, propagateEscapeStatus); } private JavaExpression computeForPropagateStatus(PExpression expression) { // This function generates a boolean expression that evaluates to true // if the input should be exempt from escaping. As this should only be // called when PropagateStatus is enabled we must check EscapeMode as // well as isPartiallyEscaped. // The interpreter mode equivalent of this boolean expression would be : // ((value.getEscapeMode() != EscapeMode.ESCAPE_NONE) || value.isPartiallyEscaped()) JavaExpression escapeMode = computeEscaping(expression, true); JavaExpression partiallyEscaped = computeEscaping(expression, false); JavaExpression escapeModeCheck = JavaExpression.infix(JavaExpression.Type.BOOLEAN, "!=", escapeMode, JavaExpression .symbol("EscapeMode.ESCAPE_NONE")); return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", escapeModeCheck, partiallyEscaped); } /** * Compute the escaping applied to the given expression. Uses {@code propagateEscapeStatus} to * determine how to treat constants, and whether escaping is required on a part of the expression * or the whole expression. */ public JavaExpression computeEscaping(PExpression expression, boolean propagateEscapeStatus) { try { assert currentEscapingExpression == null : "Not reentrant"; this.propagateEscapeStatus = propagateEscapeStatus; expression.apply(this); assert currentEscapingExpression != null : "No escaping calculated"; return currentEscapingExpression; } finally { currentEscapingExpression = null; } } private void setEscaping(JavaExpression escaping) { currentEscapingExpression = escaping; } /** * String concatenation. Do not escape the combined string, if either of the halves has been * escaped. */ @Override public void caseAAddExpression(AAddExpression node) { node.getLeft().apply(this); JavaExpression left = currentEscapingExpression; node.getRight().apply(this); JavaExpression right = currentEscapingExpression; setEscaping(or(left, right)); } /** * Process AST node for a function (e.g. dosomething(...)). */ @Override public void caseAFunctionExpression(AFunctionExpression node) { LinkedList argsList = node.getArgs(); PExpression[] args = argsList.toArray(new PExpression[argsList.size()]); // Because the function name may have dots in, the parser would have broken // it into a little node tree which we need to walk to reconstruct the // full name. final StringBuilder fullFunctionName = new StringBuilder(); node.getName().apply(new DepthFirstAdapter() { @Override public void caseANameVariable(ANameVariable node11) { fullFunctionName.append(node11.getWord().getText()); } @Override public void caseADescendVariable(ADescendVariable node12) { node12.getParent().apply(this); fullFunctionName.append('.'); node12.getChild().apply(this); } }); setEscaping(function(fullFunctionName.toString(), args)); } /** * Do not escape the output of a function if either the function is an escaping function, or any * of its parameters have been escaped. */ private JavaExpression function(String name, PExpression... csExpressions) { if (propagateEscapeStatus) { // context.isEscapingFunction("name") ? EscapeMode.ESCAPE_IS_CONSTANT : EscapeMode.ESCAPE_NONE return JavaExpression.inlineIf(JavaExpression.Type.UNKNOWN, callOn( JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction", string(name)), JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"), JavaExpression .symbol("EscapeMode.ESCAPE_NONE")); } JavaExpression finalExpression = BooleanLiteralExpression.FALSE; for (int i = 0; i < csExpressions.length; i++) { // Will put result in currentEscapingExpression. csExpressions[i].apply(this); finalExpression = or(finalExpression, currentEscapingExpression); } JavaExpression funcExpr = callOn(JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction", string(name)); return or(finalExpression, funcExpr); } /* * This function tries to optimize the output expression where possible: instead of * "(false || context.isEscapingFunction())" it returns "context.isEscapingFunction()". */ private JavaExpression or(JavaExpression first, JavaExpression second) { if (propagateEscapeStatus) { return JavaExpression.callOn(JavaExpression.symbol("EscapeMode"), "combineModes", first, second); } if (first instanceof BooleanLiteralExpression) { BooleanLiteralExpression expr = (BooleanLiteralExpression) first; if (expr.getValue()) { return expr; } else { return second; } } if (second instanceof BooleanLiteralExpression) { BooleanLiteralExpression expr = (BooleanLiteralExpression) second; if (expr.getValue()) { return expr; } else { return first; } } return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", first, second); } /* * All the following operators have no effect on escaping, so just default to 'false'. */ /** * Process AST node for a variable (e.g. a.b.c). */ @Override public void caseAVariableExpression(AVariableExpression node) { if (propagateEscapeStatus) { JavaExpression varName = variableTranslator.translate(node.getVariable()); setEscaping(callOn(TemplateTranslator.DATA_CONTEXT, "findVariableEscapeMode", varName)); } else { setDefaultEscaping(); } } private void setDefaultEscaping() { if (propagateEscapeStatus) { setEscaping(JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT")); } else { setEscaping(BooleanLiteralExpression.FALSE); } } /** * Process AST node for a string (e.g. "hello"). */ @Override public void caseAStringExpression(AStringExpression node) { setDefaultEscaping(); } /** * Process AST node for a decimal integer (e.g. 123). */ @Override public void caseADecimalExpression(ADecimalExpression node) { setDefaultEscaping(); } /** * Process AST node for a hex integer (e.g. 0x1AB). */ @Override public void caseAHexExpression(AHexExpression node) { setDefaultEscaping(); } @Override public void caseANumericExpression(ANumericExpression node) { setDefaultEscaping(); } @Override public void caseANotExpression(ANotExpression node) { setDefaultEscaping(); } @Override public void caseAExistsExpression(AExistsExpression node) { setDefaultEscaping(); } @Override public void caseAEqExpression(AEqExpression node) { setDefaultEscaping(); } @Override public void caseANumericEqExpression(ANumericEqExpression node) { setDefaultEscaping(); } @Override public void caseANeExpression(ANeExpression node) { setDefaultEscaping(); } @Override public void caseANumericNeExpression(ANumericNeExpression node) { setDefaultEscaping(); } @Override public void caseALtExpression(ALtExpression node) { setDefaultEscaping(); } @Override public void caseAGtExpression(AGtExpression node) { setDefaultEscaping(); } @Override public void caseALteExpression(ALteExpression node) { setDefaultEscaping(); } @Override public void caseAGteExpression(AGteExpression node) { setDefaultEscaping(); } @Override public void caseAAndExpression(AAndExpression node) { setDefaultEscaping(); } @Override public void caseAOrExpression(AOrExpression node) { setDefaultEscaping(); } @Override public void caseANumericAddExpression(ANumericAddExpression node) { setDefaultEscaping(); } @Override public void caseASubtractExpression(ASubtractExpression node) { setDefaultEscaping(); } @Override public void caseAMultiplyExpression(AMultiplyExpression node) { setDefaultEscaping(); } @Override public void caseADivideExpression(ADivideExpression node) { setDefaultEscaping(); } @Override public void caseAModuloExpression(AModuloExpression node) { setDefaultEscaping(); } @Override public void caseANegativeExpression(ANegativeExpression node) { setDefaultEscaping(); } } src/com/google/clearsilver/jsilver/compiler/ExpressionTranslator.java0100644 0000000 0000000 00000033040 13662126234 025312 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.compiler.JavaExpression.Type; import static com.google.clearsilver.jsilver.compiler.JavaExpression.bool; import static com.google.clearsilver.jsilver.compiler.JavaExpression.call; import static com.google.clearsilver.jsilver.compiler.JavaExpression.callFindVariable; import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn; import static com.google.clearsilver.jsilver.compiler.JavaExpression.declare; import static com.google.clearsilver.jsilver.compiler.JavaExpression.integer; import static com.google.clearsilver.jsilver.compiler.JavaExpression.string; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAddExpression; import com.google.clearsilver.jsilver.syntax.node.AAndExpression; import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression; import com.google.clearsilver.jsilver.syntax.node.ADescendVariable; import com.google.clearsilver.jsilver.syntax.node.ADivideExpression; import com.google.clearsilver.jsilver.syntax.node.AEqExpression; import com.google.clearsilver.jsilver.syntax.node.AExistsExpression; import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression; import com.google.clearsilver.jsilver.syntax.node.AGtExpression; import com.google.clearsilver.jsilver.syntax.node.AGteExpression; import com.google.clearsilver.jsilver.syntax.node.AHexExpression; import com.google.clearsilver.jsilver.syntax.node.ALtExpression; import com.google.clearsilver.jsilver.syntax.node.ALteExpression; import com.google.clearsilver.jsilver.syntax.node.AModuloExpression; import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression; import com.google.clearsilver.jsilver.syntax.node.ANameVariable; import com.google.clearsilver.jsilver.syntax.node.ANeExpression; import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression; import com.google.clearsilver.jsilver.syntax.node.ANotExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression; import com.google.clearsilver.jsilver.syntax.node.AOrExpression; import com.google.clearsilver.jsilver.syntax.node.AStringExpression; import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression; import com.google.clearsilver.jsilver.syntax.node.AVariableExpression; import com.google.clearsilver.jsilver.syntax.node.PExpression; import java.util.LinkedList; /** * Translates a CS expression (from the AST) into an equivalent Java expression. * * In order to optimize the expressions nicely this class emits code using a series of wrapper * functions for casting to/from various types. Rather than the old style of saying: * *
ValueX.asFoo()
* * we now write: * *
asFoo(ValueX)
* * This is actually very important because it means that as we optimize the expressions to return * fundamental types, we just have different versions of the {@code asFoo()} methods that take the * appropriate types. The user of the expression is responsible for casting it and the producer of * the expression is now free to produce optimized expressions. */ public class ExpressionTranslator extends DepthFirstAdapter { private JavaExpression currentJavaExpression; /** * Translate a template AST expression into a Java String expression. */ public JavaExpression translateToString(PExpression csExpression) { return translateUntyped(csExpression).cast(Type.STRING); } /** * Translate a template AST expression into a Java boolean expression. */ public JavaExpression translateToBoolean(PExpression csExpression) { return translateUntyped(csExpression).cast(Type.BOOLEAN); } /** * Translate a template AST expression into a Java integer expression. */ public JavaExpression translateToNumber(PExpression csExpression) { return translateUntyped(csExpression).cast(Type.INT); } /** * Translate a template AST expression into a Java Data expression. */ public JavaExpression translateToData(PExpression csExpression) { return translateUntyped(csExpression).cast(Type.DATA); } /** * Translate a template AST expression into a Java Data expression. */ public JavaExpression translateToVarName(PExpression csExpression) { return translateUntyped(csExpression).cast(Type.VAR_NAME); } /** * Translate a template AST expression into a Java Value expression. */ public JavaExpression translateToValue(PExpression csExpression) { return translateUntyped(csExpression).cast(Type.VALUE); } /** * Declares the (typed) expression as a variable with the given name. (e.g. "int foo = 5" or * "Data foo = Data.getChild("a.b")" */ public JavaExpression declareAsVariable(String name, PExpression csExpression) { JavaExpression expression = translateUntyped(csExpression); Type type = expression.getType(); assert type != null : "all subexpressions should be typed"; return declare(type, name, expression); } /** * Translate a template AST expression into an untyped expression. */ public JavaExpression translateUntyped(PExpression csExpression) { try { assert currentJavaExpression == null : "Not reentrant"; csExpression.apply(this); assert currentJavaExpression != null : "No expression created"; return currentJavaExpression; } finally { currentJavaExpression = null; } } private void setResult(JavaExpression javaExpression) { this.currentJavaExpression = javaExpression; } /** * Process AST node for a variable (e.g. a.b.c). */ @Override public void caseAVariableExpression(AVariableExpression node) { JavaExpression varName = new VariableTranslator(this).translate(node.getVariable()); setResult(varName); } /** * Process AST node for a string (e.g. "hello"). */ @Override public void caseAStringExpression(AStringExpression node) { String value = node.getValue().getText(); value = value.substring(1, value.length() - 1); // Remove enclosing quotes. setResult(string(value)); } /** * Process AST node for a decimal integer (e.g. 123). */ @Override public void caseADecimalExpression(ADecimalExpression node) { String value = node.getValue().getText(); setResult(integer(value)); } /** * Process AST node for a hex integer (e.g. 0x1AB). */ @Override public void caseAHexExpression(AHexExpression node) { String value = node.getValue().getText(); // Luckily ClearSilver hex representation is a subset of the Java hex // representation so we can just use the literal directly. // TODO: add well-formedness checks whenever literals are used setResult(integer(value)); } /* * The next block of functions all convert CS operators into dynamically looked up functions. */ @Override public void caseANumericExpression(ANumericExpression node) { setResult(cast(Type.INT, node.getExpression())); } @Override public void caseANotExpression(ANotExpression node) { setResult(prefix(Type.BOOLEAN, Type.BOOLEAN, "!", node.getExpression())); } @Override public void caseAExistsExpression(AExistsExpression node) { // Special case. Exists is only ever an issue for variables, all // other expressions unconditionally exist. PExpression expression = node.getExpression(); if (expression instanceof AVariableExpression) { expression.apply(this); if (currentJavaExpression.getType() == Type.VAR_NAME) { currentJavaExpression = callFindVariable(currentJavaExpression, false); } setResult(call(Type.BOOLEAN, "exists", currentJavaExpression)); } else { // If it's not a variable, it always exists // NOTE: It's not clear if we must evaluate the sub-expression // here (is there anything that can have side effects??) setResult(bool(true)); } } @Override public void caseAEqExpression(AEqExpression node) { JavaExpression left = cast(Type.STRING, node.getLeft()); JavaExpression right = cast(Type.STRING, node.getRight()); setResult(callOn(Type.BOOLEAN, left, "equals", right)); } @Override public void caseANumericEqExpression(ANumericEqExpression node) { setResult(infix(Type.BOOLEAN, Type.INT, "==", node.getLeft(), node.getRight())); } @Override public void caseANeExpression(ANeExpression node) { JavaExpression left = cast(Type.STRING, node.getLeft()); JavaExpression right = cast(Type.STRING, node.getRight()); setResult(JavaExpression.prefix(Type.BOOLEAN, "!", callOn(Type.BOOLEAN, left, "equals", right))); } @Override public void caseANumericNeExpression(ANumericNeExpression node) { setResult(infix(Type.BOOLEAN, Type.INT, "!=", node.getLeft(), node.getRight())); } @Override public void caseALtExpression(ALtExpression node) { setResult(infix(Type.BOOLEAN, Type.INT, "<", node.getLeft(), node.getRight())); } @Override public void caseAGtExpression(AGtExpression node) { setResult(infix(Type.BOOLEAN, Type.INT, ">", node.getLeft(), node.getRight())); } @Override public void caseALteExpression(ALteExpression node) { setResult(infix(Type.BOOLEAN, Type.INT, "<=", node.getLeft(), node.getRight())); } @Override public void caseAGteExpression(AGteExpression node) { setResult(infix(Type.BOOLEAN, Type.INT, ">=", node.getLeft(), node.getRight())); } @Override public void caseAAndExpression(AAndExpression node) { setResult(infix(Type.BOOLEAN, Type.BOOLEAN, "&&", node.getLeft(), node.getRight())); } @Override public void caseAOrExpression(AOrExpression node) { setResult(infix(Type.BOOLEAN, Type.BOOLEAN, "||", node.getLeft(), node.getRight())); } @Override public void caseAAddExpression(AAddExpression node) { setResult(infix(Type.STRING, Type.STRING, "+", node.getLeft(), node.getRight())); } @Override public void caseANumericAddExpression(ANumericAddExpression node) { setResult(infix(Type.INT, Type.INT, "+", node.getLeft(), node.getRight())); } @Override public void caseASubtractExpression(ASubtractExpression node) { setResult(infix(Type.INT, Type.INT, "-", node.getLeft(), node.getRight())); } @Override public void caseAMultiplyExpression(AMultiplyExpression node) { setResult(infix(Type.INT, Type.INT, "*", node.getLeft(), node.getRight())); } @Override public void caseADivideExpression(ADivideExpression node) { setResult(infix(Type.INT, Type.INT, "/", node.getLeft(), node.getRight())); } @Override public void caseAModuloExpression(AModuloExpression node) { setResult(infix(Type.INT, Type.INT, "%", node.getLeft(), node.getRight())); } @Override public void caseANegativeExpression(ANegativeExpression node) { setResult(prefix(Type.INT, Type.INT, "-", node.getExpression())); } /** * Process AST node for a function (e.g. dosomething(...)). */ @Override public void caseAFunctionExpression(AFunctionExpression node) { LinkedList argsList = node.getArgs(); PExpression[] args = argsList.toArray(new PExpression[argsList.size()]); // Because the function name may have dots in, the parser would have broken // it into a little node tree which we need to walk to reconstruct the // full name. final StringBuilder fullFunctionName = new StringBuilder(); node.getName().apply(new DepthFirstAdapter() { @Override public void caseANameVariable(ANameVariable node11) { fullFunctionName.append(node11.getWord().getText()); } @Override public void caseADescendVariable(ADescendVariable node12) { node12.getParent().apply(this); fullFunctionName.append('.'); node12.getChild().apply(this); } }); setResult(function(fullFunctionName.toString(), args)); } /** * Generate a JavaExpression for calling a function. */ private JavaExpression function(String name, PExpression... csExpressions) { // Outputs: context.executeFunction("myfunc", args...); JavaExpression[] args = new JavaExpression[1 + csExpressions.length]; args[0] = string(name); for (int i = 0; i < csExpressions.length; i++) { args[i + 1] = cast(Type.VALUE, csExpressions[i]); } return callOn(Type.VALUE, TemplateTranslator.CONTEXT, "executeFunction", args); } private JavaExpression infix(Type destType, Type srcType, String infix, PExpression leftNode, PExpression rightNode) { JavaExpression left = cast(srcType, leftNode); JavaExpression right = cast(srcType, rightNode); return JavaExpression.infix(destType, infix, left, right); } private JavaExpression prefix(Type destType, Type srcType, String prefix, PExpression node) { return JavaExpression.prefix(destType, prefix, cast(srcType, node)); } private JavaExpression cast(Type type, PExpression node) { node.apply(this); return currentJavaExpression.cast(type); } } src/com/google/clearsilver/jsilver/compiler/JSilverCompilationException.java0100644 0000000 0000000 00000001775 13662126234 026547 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.exceptions.JSilverException; /** * Thrown when a template cannot be compiled. */ public class JSilverCompilationException extends JSilverException { public JSilverCompilationException(String message, Throwable cause) { super(message, cause); } public JSilverCompilationException(String message) { super(message); } } src/com/google/clearsilver/jsilver/compiler/JavaExpression.java0100644 0000000 0000000 00000034026 13662126234 024047 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.data.TypeConverter; import java.io.PrintWriter; import java.io.StringWriter; /** * Represents a node of a Java expression. * * This class contains static helper methods for common types of expressions, or you can just create * your own subclass. */ public abstract class JavaExpression { /** * Simple type enumeration to allow us to compare the return types of expressions easily and cast * expressions nicely. */ public enum Type { STRING("String") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { expression = expression.cast(DATA); } return call(Type.STRING, "asString", expression); } }, INT("int") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { expression = expression.cast(DATA); } return call(Type.INT, "asInt", expression); } }, BOOLEAN("boolean") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { expression = expression.cast(DATA); } return call(Type.BOOLEAN, "asBoolean", expression); } }, VALUE("Value") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { return call(Type.VALUE, "asVariableValue", expression, TemplateTranslator.DATA_CONTEXT); } else { return call(Type.VALUE, "asValue", expression); } } }, DATA("Data") { @Override protected JavaExpression cast(JavaExpression expression) { if (expression.getType() == VAR_NAME) { return callFindVariable(expression, false); } else { throw new JSilverCompilationException("Cannot cast to 'Data' for expression:\n" + expression.toString()); } } }, // This is a string that represents the name of a Data path. VAR_NAME("String") { @Override protected JavaExpression cast(JavaExpression expression) { final JavaExpression stringExpr = expression.cast(Type.STRING); return new JavaExpression(Type.VAR_NAME) { public void write(PrintWriter out) { stringExpr.write(out); } }; } }, // This is a special type because we only cast from DataContext, never to it. DATA_CONTEXT("DataContext") { @Override protected JavaExpression cast(JavaExpression expression) { throw new JSilverCompilationException("Cannot cast to 'DataContext' for expression:\n" + expression.toString()); } }, // This is a special type because we only cast from Data, never to it. MACRO("Macro") { @Override protected JavaExpression cast(JavaExpression expression) { throw new JSilverCompilationException("Cannot cast to 'Macro' for expression:\n" + expression.toString()); } }, // Use this type for JavaExpressions that have no type (such as method // calls with no return value). Wraps the input expression with a // JavaExpression of Type VOID. VOID("Void") { @Override protected JavaExpression cast(final JavaExpression expression) { return new JavaExpression(Type.VOID) { @Override public void write(PrintWriter out) { expression.write(out); } }; } }; /** Useful constant for unknown types */ public static final Type UNKNOWN = null; /** * The Java literal representing the type (e.g. "int", "boolean", "Value") */ public final String symbol; /** * Unconditionally casts the given expression to the type. This should only be called after it * has been determined that the destination type is not the same as the expression type. */ protected abstract JavaExpression cast(JavaExpression expression); private Type(String symbol) { this.symbol = symbol; } } private final Type type; /** * Creates a typed expression. Typed expressions allow for greater optimization by avoiding * unnecessary casting operations. * * @param type the Type of the expression. Must be from the enum above and represent a primitive * or a Class name or void. */ public JavaExpression(Type type) { this.type = type; } /** * Cast this expression to the destination type (possibly a no-op) */ public JavaExpression cast(Type destType) { return (type != destType) ? destType.cast(this) : this; } /** * Gets the type of this expression (or {@code null} if unknown). */ public Type getType() { return type; } /** * Implementations use this to output the expression as Java code. */ public abstract void write(PrintWriter out); @Override public String toString() { StringWriter out = new StringWriter(); write(new PrintWriter(out)); return out.toString(); } /** * An untyped method call (e.g. doStuff(x, "y")). */ public static JavaExpression call(final String method, final JavaExpression... params) { return call(null, method, params); } /** * A typed method call (e.g. doStuff(x, "y")). */ public static JavaExpression call(Type type, final String method, final JavaExpression... params) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, method); out.append('('); boolean seenAnyParams = false; for (JavaExpression param : params) { if (seenAnyParams) { out.append(", "); } else { seenAnyParams = true; } param.write(out); } out.append(')'); } }; } /** * An untyped method call on an instance (e.g. thingy.doStuff(x, "y")). We assume it returns VOID * and thus there is no return value. */ public static JavaExpression callOn(final JavaExpression instance, final String method, final JavaExpression... params) { return callOn(Type.VOID, instance, method, params); } /** * A typed method call on an instance (e.g. thingy.doStuff(x, "y")). */ public static JavaExpression callOn(Type type, final JavaExpression instance, final String method, final JavaExpression... params) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { instance.write(out); out.append('.'); call(method, params).write(out); } }; } /** * A Java string (e.g. "hello\nworld"). */ public static JavaExpression string(String value) { return new StringExpression(value); } public static class StringExpression extends JavaExpression { private final String value; public StringExpression(String value) { super(Type.STRING); this.value = value; } public String getValue() { return value; } @Override public void write(PrintWriter out) { // TODO: This is not production ready yet - needs more // thorough escaping mechanism. out.append('"'); char[] chars = value.toCharArray(); for (char c : chars) { switch (c) { // Single quote (') does not need to be escaped as it's in a // double-quoted (") string. case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\t': out.append("\\t"); break; case '\\': out.append("\\\\"); break; case '"': out.append("\\\""); break; case '\b': out.append("\\b"); break; case '\f': out.append("\\f"); break; default: out.append(c); } } out.append('"'); } } /** * A JavaExpression to represent boolean literal values ('true' or 'false'). */ public static class BooleanLiteralExpression extends JavaExpression { private final boolean value; public static final BooleanLiteralExpression FALSE = new BooleanLiteralExpression(false); public static final BooleanLiteralExpression TRUE = new BooleanLiteralExpression(true); private BooleanLiteralExpression(boolean value) { super(Type.BOOLEAN); this.value = value; } public boolean getValue() { return value; } @Override public void write(PrintWriter out) { out.append(String.valueOf(value)); } } /** * An integer. */ public static JavaExpression integer(String value) { // Just parse it to to check that it is valid TypeConverter.parseNumber(value); return literal(Type.INT, value); } /** * An integer. */ public static JavaExpression integer(int value) { return literal(Type.INT, String.valueOf(value)); } /** * A boolean */ public static JavaExpression bool(boolean value) { return literal(Type.BOOLEAN, value ? "true" : "false"); } /** * An untyped symbol (e.g. myVariable). */ public static JavaExpression symbol(final String value) { return new JavaExpression(Type.UNKNOWN) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, value); } }; } /** * A typed symbol (e.g. myVariable). */ public static JavaExpression symbol(Type type, final String value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, value); } }; } public static JavaExpression macro(final String value) { return symbol(Type.MACRO, value); } /** * A typed assignment (e.g. stuff = doSomething()). */ public static JavaExpression assign(Type type, final String name, final JavaExpression value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, name); out.append(" = "); value.write(out); } }; } /** * A typed assignment with declaration (e.g. String stuff = doSomething()). Use this in preference * when declaring variables from typed expressions. */ public static JavaExpression declare(final Type type, final String name, final JavaExpression value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { JavaSourceWriter.writeJavaSymbol(out, type.symbol); out.append(' '); assign(type, name, value).write(out); } }; } /** * An infix expression (e.g. (a + b) ). */ public static JavaExpression infix(Type type, final String operator, final JavaExpression left, final JavaExpression right) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append("("); left.write(out); out.append(" ").append(operator).append(" "); right.write(out); out.append(")"); } }; } /** * An prefix expression (e.g. (-a) ). */ public static JavaExpression prefix(Type type, final String operator, final JavaExpression expression) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append("(").append(operator); expression.write(out); out.append(")"); } }; } /** * A three term inline if expression (e.g. (a ? b : c) ). */ public static JavaExpression inlineIf(Type type, final JavaExpression query, final JavaExpression trueExp, final JavaExpression falseExp) { if (query.getType() != Type.BOOLEAN) { throw new IllegalArgumentException("Expect BOOLEAN expression"); } return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append("("); query.write(out); out.append(" ? "); trueExp.write(out); out.append(" : "); falseExp.write(out); out.append(")"); } }; } /** * An increment statement (e.g. a += b). The difference with infix is that this does not wrap the * expression in parentheses as that is not a valid statement. */ public static JavaExpression increment(Type type, final JavaExpression accumulator, final JavaExpression incr) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { accumulator.write(out); out.append(" += "); incr.write(out); } }; } /** * A literal expression (e.g. anything!). This method injects whatever string it is given into the * Java code - use only in cases where there can be no ambiguity about how the string could be * interpreted by the compiler. */ public static JavaExpression literal(Type type, final String value) { return new JavaExpression(type) { @Override public void write(PrintWriter out) { out.append(value); } }; } public static JavaExpression callFindVariable(JavaExpression expression, boolean create) { if (expression.getType() != Type.VAR_NAME) { throw new IllegalArgumentException("Expect VAR_NAME expression"); } return callOn(Type.DATA, TemplateTranslator.DATA_CONTEXT, "findVariable", expression, JavaExpression.bool(create)); } } src/com/google/clearsilver/jsilver/compiler/JavaSourceWriter.java0100644 0000000 0000000 00000017455 13662126234 024354 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import java.io.Closeable; import java.io.Flushable; import java.io.PrintWriter; import java.io.Writer; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * Simple API for generating Java source code. Easier than lots of string manipulation. * *

Example

* *
 * java = new JavaSourceWriter(out);
 *
 * java.writeComment("// Auto generated file");
 * java.writePackage("com.something.mypackage");
 * java.writeImports(SomeClassToImport.class, Another.class);
 *
 * java.startClass("SomeClass", "InterfaceA");
 * java.startMethod(Object.class.getMethod("toString"));
 * java.writeStatement(call("System.out.println", string("hello")));
 * java.endClass();
 * 
* * Note: For writing statements/expressions, staticly import the methods on {@link JavaExpression}. */ public class JavaSourceWriter implements Closeable, Flushable { private final PrintWriter out; private int indent; public JavaSourceWriter(Writer out) { this.out = new PrintWriter(out); } public void writePackage(String packageName) { // TODO: Verify packageName is valid. if (packageName != null) { startLine(); out.append("package ").append(packageName).append(';'); endLine(); emptyLine(); } } public void writeImports(Class... javaClasses) { for (Class javaClass : javaClasses) { startLine(); out.append("import ").append(javaClass.getName()).append(';'); endLine(); } if (javaClasses.length > 0) { emptyLine(); } } public void writeComment(String comment) { // TODO: Handle line breaks in comments. startLine(); out.append("// ").append(comment); endLine(); } public void startClass(String className, String baseClassName, String... interfaceNames) { startLine(); out.append("public class "); writeJavaSymbol(out, className); if (baseClassName != null) { out.append(" extends "); writeJavaSymbol(out, baseClassName); } boolean seenAnyInterfaces = false; for (String interfaceName : interfaceNames) { if (!seenAnyInterfaces) { seenAnyInterfaces = true; out.append(" implements "); } else { out.append(", "); } writeJavaSymbol(out, interfaceName); } out.append(' '); startBlock(); emptyLine(); } public void startAnonymousClass(String baseClass, JavaExpression... constructorArgs) { out.append("new "); writeJavaSymbol(out, baseClass); out.append('('); boolean seenAnyArgs = false; for (JavaExpression constructorArg : constructorArgs) { if (seenAnyArgs) { out.append(", "); } seenAnyArgs = true; constructorArg.write(out); } out.append(") "); startBlock(); emptyLine(); } public void endAnonymousClass() { endBlock(); } /** * Start a method. The signature is based on that of an existing method. */ public void startMethod(Method method, String... paramNames) { // This currently does not support generics, varargs or arrays. // If you need it - add the support. Don't want to overcomplicate it // until necessary. if (paramNames.length != method.getParameterTypes().length) { throw new IllegalArgumentException("Did not specifiy correct " + "number of parameter names for method signature " + method); } startLine(); // @Override abstract methods. int modifiers = method.getModifiers(); if (Modifier.isAbstract(modifiers)) { out.append("@Override"); endLine(); startLine(); } // Modifiers: (public, protected, static) if (modifiers != 0) { // Modifiers we care about. Ditch the rest. Specifically NOT ABSTRACT. modifiers &= Modifier.PUBLIC | Modifier.PROTECTED | Modifier.STATIC; out.append(Modifier.toString(modifiers)).append(' '); } // Return type and name: (e.g. "void doStuff(") out.append(method.getReturnType().getSimpleName()).append(' ').append(method.getName()).append( '('); // Parameters. int paramIndex = 0; for (Class paramType : method.getParameterTypes()) { if (paramIndex > 0) { out.append(", "); } writeJavaSymbol(out, paramType.getSimpleName()); out.append(' '); writeJavaSymbol(out, paramNames[paramIndex]); paramIndex++; } out.append(')'); // Exceptions thrown. boolean seenAnyExceptions = false; for (Class exception : method.getExceptionTypes()) { if (!seenAnyExceptions) { seenAnyExceptions = true; endLine(); startLine(); out.append(" throws "); } else { out.append(", "); } writeJavaSymbol(out, exception.getSimpleName()); } out.append(' '); startBlock(); } public void startIfBlock(JavaExpression expression) { startLine(); out.append("if ("); writeExpression(expression); out.append(") "); startBlock(); } public void endIfStartElseBlock() { endBlock(); out.append(" else "); startBlock(); } public void endIfBlock() { endBlock(); endLine(); } public void startScopedBlock() { startLine(); startBlock(); } public void endScopedBlock() { endBlock(); endLine(); } public void startIterableForLoop(String type, String name, JavaExpression expression) { startLine(); out.append("for ("); writeJavaSymbol(out, type); out.append(' '); writeJavaSymbol(out, name); out.append(" : "); writeExpression(expression); out.append(") "); startBlock(); } public void startForLoop(JavaExpression start, JavaExpression end, JavaExpression increment) { startLine(); out.append("for ("); writeExpression(start); out.append("; "); writeExpression(end); out.append("; "); writeExpression(increment); out.append(") "); startBlock(); } public void endLoop() { endBlock(); endLine(); } public void writeStatement(JavaExpression expression) { startLine(); writeExpression(expression); out.append(';'); endLine(); } public void writeExpression(JavaExpression expression) { expression.write(out); } public void endMethod() { endBlock(); endLine(); emptyLine(); } public void endClass() { endBlock(); endLine(); emptyLine(); } @Override public void flush() { out.flush(); } @Override public void close() { out.close(); } private void startBlock() { out.append('{'); endLine(); indent++; } private void endBlock() { indent--; startLine(); out.append('}'); } private void startLine() { for (int i = 0; i < indent; i++) { out.append(" "); } } private void endLine() { out.append('\n'); } private void emptyLine() { out.append('\n'); } public static void writeJavaSymbol(PrintWriter out, String symbol) { out.append(symbol); // TODO Make safe and validate. } public void startField(String type, JavaExpression name) { startLine(); out.append("private final "); writeJavaSymbol(out, type); out.append(' '); name.write(out); out.append(" = "); } public void endField() { out.append(';'); endLine(); emptyLine(); } } src/com/google/clearsilver/jsilver/compiler/TemplateCompiler.java0100644 0000000 0000000 00000015315 13662126234 024354 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.interpreter.TemplateFactory; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree; import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; import java.io.StringWriter; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaFileObject; /** * Takes a template AST and compiles it into a Java class, which executes much faster than the * intepreter. */ public class TemplateCompiler implements DelegatingTemplateLoader { private static final Logger logger = Logger.getLogger(TemplateCompiler.class.getName()); private static final String PACKAGE_NAME = "com.google.clearsilver.jsilver.compiler"; // Because each template is isolated in its own ClassLoader, it doesn't // matter if there are naming clashes between templates. private static final String CLASS_NAME = "$CompiledTemplate"; private final TemplateFactory templateFactory; private final FunctionExecutor globalFunctionExecutor; private final AutoEscapeOptions autoEscapeOptions; private TemplateLoader templateLoaderDelegate = this; public TemplateCompiler(TemplateFactory templateFactory, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) { this.templateFactory = templateFactory; this.globalFunctionExecutor = globalFunctionExecutor; this.autoEscapeOptions = autoEscapeOptions; } @Override public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) { this.templateLoaderDelegate = templateLoaderDelegate; } @Override public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) { return compile(templateFactory.find(templateName, resourceLoader, escapeMode), templateName, escapeMode); } @Override public Template createTemp(String name, String content, EscapeMode escapeMode) { return compile(templateFactory.createTemp(content, escapeMode), name, escapeMode); } /** * Compile AST into Java class. * * @param ast A template AST. * @param templateName Name of template (e.g. "foo.cs"). Used for error reporting. May be null, * @return Template that can be executed (again and again). */ private Template compile(TemplateSyntaxTree ast, String templateName, EscapeMode mode) { CharSequence javaSource = translateAstToJavaSource(ast, mode); String errorMessage = "Could not compile template: " + templateName; Class templateClass = compileAndLoad(javaSource, errorMessage); try { BaseCompiledTemplate compiledTemplate = (BaseCompiledTemplate) templateClass.newInstance(); compiledTemplate.setFunctionExecutor(globalFunctionExecutor); compiledTemplate.setTemplateName(templateName); compiledTemplate.setTemplateLoader(templateLoaderDelegate); compiledTemplate.setEscapeMode(mode); compiledTemplate.setAutoEscapeOptions(autoEscapeOptions); return compiledTemplate; } catch (InstantiationException e) { throw new Error(e); // Should not be possible. Throw Error if it does. } catch (IllegalAccessException e) { throw new Error(e); // Should not be possible. Throw Error if it does. } } private CharSequence translateAstToJavaSource(TemplateSyntaxTree ast, EscapeMode mode) { StringWriter sourceBuffer = new StringWriter(256); boolean propagateStatus = autoEscapeOptions.getPropagateEscapeStatus() && mode.isAutoEscapingMode(); ast.apply(new TemplateTranslator(PACKAGE_NAME, CLASS_NAME, sourceBuffer, propagateStatus)); StringBuffer javaSource = sourceBuffer.getBuffer(); logger.log(Level.FINEST, "Compiled template:\n{0}", javaSource); return javaSource; } private Class compileAndLoad(CharSequence javaSource, String errorMessage) throws JSilverCompilationException { // Need a parent class loader to load dependencies from. // This does not use any libraries outside of JSilver (e.g. custom user // libraries), so using this class's ClassLoader should be fine. ClassLoader parentClassLoader = getClass().getClassLoader(); // Collect any compiler errors/warnings. DiagnosticCollector diagnosticCollector = new DiagnosticCollector(); try { // Magical ClassLoader that compiles source code on the fly. CompilingClassLoader templateClassLoader = new CompilingClassLoader(parentClassLoader, CLASS_NAME, javaSource, diagnosticCollector); return templateClassLoader.loadClass(PACKAGE_NAME + "." + CLASS_NAME); } catch (Exception e) { // Ordinarily, this shouldn't happen as the code is generated. However, // in case there's a bug in JSilver, it will be helpful to have as much // info as possible in the exception to diagnose the problem. throwExceptionWithLotsOfDiagnosticInfo(javaSource, errorMessage, diagnosticCollector .getDiagnostics(), e); return null; // Keep compiler happy. } } private void throwExceptionWithLotsOfDiagnosticInfo(CharSequence javaSource, String errorMessage, List> diagnostics, Exception cause) throws JSilverCompilationException { // Create exception with lots of info in it. StringBuilder message = new StringBuilder(errorMessage).append('\n'); message.append("------ Source code ------\n").append(javaSource); message.append("------ Compiler messages ------\n"); for (Diagnostic diagnostic : diagnostics) { message.append(diagnostic).append('\n'); } message.append("------ ------\n"); throw new JSilverCompilationException(message.toString(), cause); } } src/com/google/clearsilver/jsilver/compiler/TemplateTranslator.java0100644 0000000 0000000 00000100276 13662126234 024734 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression; import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type; import static com.google.clearsilver.jsilver.compiler.JavaExpression.call; import static com.google.clearsilver.jsilver.compiler.JavaExpression.callFindVariable; import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn; import static com.google.clearsilver.jsilver.compiler.JavaExpression.declare; import static com.google.clearsilver.jsilver.compiler.JavaExpression.increment; import static com.google.clearsilver.jsilver.compiler.JavaExpression.infix; import static com.google.clearsilver.jsilver.compiler.JavaExpression.inlineIf; import static com.google.clearsilver.jsilver.compiler.JavaExpression.integer; import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal; import static com.google.clearsilver.jsilver.compiler.JavaExpression.macro; import static com.google.clearsilver.jsilver.compiler.JavaExpression.string; import static com.google.clearsilver.jsilver.compiler.JavaExpression.symbol; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataContext; import com.google.clearsilver.jsilver.functions.Function; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAltCommand; import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand; import com.google.clearsilver.jsilver.syntax.node.ACallCommand; import com.google.clearsilver.jsilver.syntax.node.ADataCommand; import com.google.clearsilver.jsilver.syntax.node.ADefCommand; import com.google.clearsilver.jsilver.syntax.node.AEachCommand; import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand; import com.google.clearsilver.jsilver.syntax.node.AEvarCommand; import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand; import com.google.clearsilver.jsilver.syntax.node.AIfCommand; import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand; import com.google.clearsilver.jsilver.syntax.node.ALvarCommand; import com.google.clearsilver.jsilver.syntax.node.ANameCommand; import com.google.clearsilver.jsilver.syntax.node.ANoopCommand; import com.google.clearsilver.jsilver.syntax.node.ASetCommand; import com.google.clearsilver.jsilver.syntax.node.AUvarCommand; import com.google.clearsilver.jsilver.syntax.node.AVarCommand; import com.google.clearsilver.jsilver.syntax.node.AWithCommand; import com.google.clearsilver.jsilver.syntax.node.PCommand; import com.google.clearsilver.jsilver.syntax.node.PExpression; import com.google.clearsilver.jsilver.syntax.node.PPosition; import com.google.clearsilver.jsilver.syntax.node.PVariable; import com.google.clearsilver.jsilver.syntax.node.Start; import com.google.clearsilver.jsilver.syntax.node.TCsOpen; import com.google.clearsilver.jsilver.syntax.node.TWord; import com.google.clearsilver.jsilver.template.Macro; import com.google.clearsilver.jsilver.template.RenderingContext; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.values.Value; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; /** * Translates a JSilver AST into compilable Java code. This executes much faster than the * interpreter. * * @see TemplateCompiler */ public class TemplateTranslator extends DepthFirstAdapter { // Root data public static final JavaExpression DATA = symbol(Type.DATA, "data"); // RenderingContext public static final JavaExpression CONTEXT = symbol("context"); // DataContext public static final JavaExpression DATA_CONTEXT = symbol(Type.DATA_CONTEXT, "dataContext"); public static final JavaExpression NULL = symbol("null"); // Accessed from macros as well. public static final JavaExpression RESOURCE_LOADER = callOn(CONTEXT, "getResourceLoader"); public static final JavaExpression TEMPLATE_LOADER = symbol("getTemplateLoader()"); public static final JavaExpression THIS_TEMPLATE = symbol("this"); private final JavaSourceWriter java; private final String packageName; private final String className; private final ExpressionTranslator expressionTranslator = new ExpressionTranslator(); private final VariableTranslator variableTranslator = new VariableTranslator(expressionTranslator); private final EscapingEvaluator escapingEvaluator = new EscapingEvaluator(variableTranslator); private static final Method RENDER_METHOD; private int tempVariable = 0; /** * Used to determine the escaping to apply before displaying a variable. If propagateEscapeStatus * is enabled, string and numeric literals are not escaped, nor is the output of an escaping * function. If not, any expression that contains an escaping function is not escaped. This * maintains compatibility with the way ClearSilver works. */ private boolean propagateEscapeStatus; /** * Holds Macro information used while generating code. */ private static class MacroInfo { /** * JavaExpression used for outputting the static Macro variable name. */ JavaExpression symbol; /** * Parser node for the definition. Stored for evaluation after main render method is output. */ ADefCommand defNode; } /** * Map of macro names to definition nodes and java expressions used to refer to them. */ private final Map macroMap = new HashMap(); /** * Used to iterate through list of macros. We can't rely on Map's iterator because we may be * adding to the map as we iterate through the values() list and that would throw a * ConcurrentModificationException. */ private final Queue macroQueue = new LinkedList(); /** * Creates a MacroInfo object and adds it to the data structures. Also outputs statement to * register the macro. * * @param name name of the macro as defined in the template. * @param symbol static variable name of the macro definition. * @param defNode parser node holding the macro definition to be evaluated later. */ private void addMacro(String name, JavaExpression symbol, ADefCommand defNode) { if (macroMap.get(name) != null) { // TODO: This macro is already defined. Should throw an error. } MacroInfo info = new MacroInfo(); info.symbol = symbol; info.defNode = defNode; macroMap.put(name, info); macroQueue.add(info); // Register the macro. java.writeStatement(callOn(CONTEXT, "registerMacro", string(name), symbol)); } static { try { RENDER_METHOD = Template.class.getMethod("render", RenderingContext.class); } catch (NoSuchMethodException e) { throw new Error("Cannot find CompiledTemplate.render() method! " + "Has signature changed?", e); } } public TemplateTranslator(String packageName, String className, Writer output, boolean propagateEscapeStatus) { this.packageName = packageName; this.className = className; java = new JavaSourceWriter(output); this.propagateEscapeStatus = propagateEscapeStatus; } @Override public void caseStart(Start node) { java.writeComment("This class is autogenerated by JSilver. Do not edit."); java.writePackage(packageName); java.writeImports(BaseCompiledTemplate.class, Template.class, Macro.class, RenderingContext.class, Data.class, DataContext.class, Function.class, FunctionExecutor.class, Value.class, EscapeMode.class, IOException.class); java.startClass(className, BaseCompiledTemplate.class.getSimpleName()); // Implement render() method. java.startMethod(RENDER_METHOD, "context"); java .writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT, "getDataContext"))); java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE)); super.caseStart(node); // Walk template AST. java.writeStatement(callOn(CONTEXT, "popExecutionContext")); java.endMethod(); // The macros have to be defined outside of the render method. // (Well actually they *could* be defined inline as anon classes, but it // would make the generated code quite hard to understand). MacroTransformer macroTransformer = new MacroTransformer(); while (!macroQueue.isEmpty()) { MacroInfo curr = macroQueue.remove(); macroTransformer.parseDefNode(curr.symbol, curr.defNode); } java.endClass(); } /** * Chunk of data (i.e. not a CS command). */ @Override public void caseADataCommand(ADataCommand node) { String content = node.getData().getText(); java.writeStatement(callOn(CONTEXT, "writeUnescaped", string(content))); } /** * <?cs var:blah > expression. Evaluate as string and write output, using default escaping. */ @Override public void caseAVarCommand(AVarCommand node) { capturePosition(node.getPosition()); String tempVariableName = generateTempVariable("result"); JavaExpression result = symbol(Type.STRING, tempVariableName); java.writeStatement(declare(Type.STRING, tempVariableName, expressionTranslator .translateToString(node.getExpression()))); JavaExpression escaping = escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus); writeVariable(result, escaping); } /** * <?cs uvar:blah > expression. Evaluate as string and write output, but don't escape. */ @Override public void caseAUvarCommand(AUvarCommand node) { capturePosition(node.getPosition()); java.writeStatement(callOn(CONTEXT, "writeUnescaped", expressionTranslator .translateToString(node.getExpression()))); } /** * <?cs set:x='y' > command. */ @Override public void caseASetCommand(ASetCommand node) { capturePosition(node.getPosition()); String tempVariableName = generateTempVariable("setNode"); // Data setNode1 = dataContext.findVariable("x", true); JavaExpression setNode = symbol(Type.DATA, tempVariableName); java.writeStatement(declare(Type.DATA, tempVariableName, callFindVariable(variableTranslator .translate(node.getVariable()), true))); // setNode1.setValue("hello"); java.writeStatement(callOn(setNode, "setValue", expressionTranslator.translateToString(node .getExpression()))); if (propagateEscapeStatus) { // setNode1.setEscapeMode(EscapeMode.ESCAPE_IS_CONSTANT); java.writeStatement(callOn(setNode, "setEscapeMode", escapingEvaluator.computeEscaping(node .getExpression(), propagateEscapeStatus))); } } /** * <?cs name:blah > command. Writes out the name of the original variable referred to by a * given node. */ @Override public void caseANameCommand(ANameCommand node) { capturePosition(node.getPosition()); JavaExpression readNode = callFindVariable(variableTranslator.translate(node.getVariable()), false); java.writeStatement(callOn(CONTEXT, "writeEscaped", call("getNodeName", readNode))); } /** * <?cs if:blah > ... <?cs else > ... <?cs /if > command. */ @Override public void caseAIfCommand(AIfCommand node) { capturePosition(node.getPosition()); java.startIfBlock(expressionTranslator.translateToBoolean(node.getExpression())); node.getBlock().apply(this); if (!(node.getOtherwise() instanceof ANoopCommand)) { java.endIfStartElseBlock(); node.getOtherwise().apply(this); } java.endIfBlock(); } /** * <?cs each:x=Stuff > ... <?cs /each > command. Loops over child items of a data * node. */ @Override public void caseAEachCommand(AEachCommand node) { capturePosition(node.getPosition()); JavaExpression parent = expressionTranslator.translateToData(node.getExpression()); writeEach(node.getVariable(), parent, node.getCommand()); } /** * <?cs with:x=Something > ... <?cs /with > command. Aliases a value within a specific * scope. */ @Override public void caseAWithCommand(AWithCommand node) { capturePosition(node.getPosition()); java.startScopedBlock(); java.writeComment("with:"); // Extract the value first in case the temp variable has the same name. JavaExpression value = expressionTranslator.translateUntyped(node.getExpression()); String methodName = null; if (value.getType() == Type.VAR_NAME) { String withValueName = generateTempVariable("withValue"); java.writeStatement(declare(Type.STRING, withValueName, value)); value = symbol(Type.VAR_NAME, withValueName); methodName = "createLocalVariableByPath"; // We need to check if the variable exists. If not, we skip the with // call. java.startIfBlock(JavaExpression.infix(Type.BOOLEAN, "!=", value.cast(Type.DATA), literal( Type.DATA, "null"))); } else { // Cast to string so we support numeric or boolean values as well. value = value.cast(Type.STRING); methodName = "createLocalVariableByValue"; } JavaExpression itemKey = variableTranslator.translate(node.getVariable()); // Push a new local variable scope for the with local variable java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); java.writeStatement(callOn(DATA_CONTEXT, methodName, itemKey, value)); node.getCommand().apply(this); // Release the variable scope used by the with statement java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); if (value.getType() == Type.VAR_NAME) { // End of if block that checks that the Data node exists. java.endIfBlock(); } java.endScopedBlock(); } /** * <?cs loop:10 > ... <?cs /loop > command. Loops over a range of numbers, starting at * zero. */ @Override public void caseALoopToCommand(ALoopToCommand node) { capturePosition(node.getPosition()); JavaExpression start = integer(0); JavaExpression end = expressionTranslator.translateToNumber(node.getExpression()); JavaExpression incr = integer(1); writeLoop(node.getVariable(), start, end, incr, node.getCommand()); } /** * <?cs loop:0,10 > ... <?cs /loop > command. Loops over a range of numbers. */ @Override public void caseALoopCommand(ALoopCommand node) { capturePosition(node.getPosition()); JavaExpression start = expressionTranslator.translateToNumber(node.getStart()); JavaExpression end = expressionTranslator.translateToNumber(node.getEnd()); JavaExpression incr = integer(1); writeLoop(node.getVariable(), start, end, incr, node.getCommand()); } /** * <?cs loop:0,10,2 > ... <?cs /loop > command. Loops over a range of numbers, with a * specific increment. */ @Override public void caseALoopIncCommand(ALoopIncCommand node) { capturePosition(node.getPosition()); JavaExpression start = expressionTranslator.translateToNumber(node.getStart()); JavaExpression end = expressionTranslator.translateToNumber(node.getEnd()); JavaExpression incr = expressionTranslator.translateToNumber(node.getIncrement()); writeLoop(node.getVariable(), start, end, incr, node.getCommand()); } private void writeLoop(PVariable itemVariable, JavaExpression start, JavaExpression end, JavaExpression incr, PCommand command) { java.startScopedBlock(); String startVarName = generateTempVariable("start"); java.writeStatement(declare(Type.INT, startVarName, start)); JavaExpression startVar = symbol(Type.INT, startVarName); String endVarName = generateTempVariable("end"); java.writeStatement(declare(Type.INT, endVarName, end)); JavaExpression endVar = symbol(Type.INT, endVarName); String incrVarName = generateTempVariable("incr"); java.writeStatement(declare(Type.INT, incrVarName, incr)); JavaExpression incrVar = symbol(Type.INT, incrVarName); // TODO: Test correctness of values. java.startIfBlock(call(Type.BOOLEAN, "validateLoopArgs", startVar, endVar, incrVar)); JavaExpression itemKey = variableTranslator.translate(itemVariable); // Push a new local variable scope for the loop local variable java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); String loopVariable = generateTempVariable("loop"); JavaExpression loopVar = symbol(Type.INT, loopVariable); JavaExpression ifStart = declare(Type.INT, loopVariable, startVar); JavaExpression ifEnd = inlineIf(Type.BOOLEAN, infix(Type.BOOLEAN, ">=", incrVar, integer(0)), infix(Type.BOOLEAN, "<=", loopVar, endVar), infix(Type.BOOLEAN, ">=", loopVar, endVar)); java.startForLoop(ifStart, ifEnd, increment(Type.INT, loopVar, incrVar)); java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByValue", itemKey, symbol( loopVariable).cast(Type.STRING), infix(Type.BOOLEAN, "==", symbol(loopVariable), startVar), infix(Type.BOOLEAN, "==", symbol(loopVariable), endVar))); command.apply(this); java.endLoop(); // Release the variable scope used by the loop statement java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); java.endIfBlock(); java.endScopedBlock(); } private void writeEach(PVariable itemVariable, JavaExpression parentData, PCommand command) { JavaExpression itemKey = variableTranslator.translate(itemVariable); // Push a new local variable scope for the each local variable java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); String childDataVariable = generateTempVariable("child"); java.startIterableForLoop("Data", childDataVariable, call("getChildren", parentData)); java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByPath", itemKey, callOn( Type.STRING, symbol(childDataVariable), "getFullPath"))); command.apply(this); java.endLoop(); // Release the variable scope used by the each statement java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); } /** * <?cs alt:someValue > ... <?cs /alt > command. If value exists, write it, otherwise * write the body of the command. */ @Override public void caseAAltCommand(AAltCommand node) { capturePosition(node.getPosition()); String tempVariableName = generateTempVariable("altVar"); JavaExpression declaration = expressionTranslator.declareAsVariable(tempVariableName, node.getExpression()); JavaExpression reference = symbol(declaration.getType(), tempVariableName); java.writeStatement(declaration); java.startIfBlock(reference.cast(Type.BOOLEAN)); JavaExpression escaping = escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus); writeVariable(reference, escaping); java.endIfStartElseBlock(); node.getCommand().apply(this); java.endIfBlock(); } /* * Generates a statement that will write out a variable expression, after determining whether the * variable expression should be exempted from any global escaping that may currently be in * effect. We try to make this determination during translation if possible, and if we cannot, we * output an if/else statement to check the escaping status of the expression at run time. * * Currently, unless the expression contains a function call, we know at translation tmie that it * does not need to be exempted. */ private void writeVariable(JavaExpression result, JavaExpression escapingExpression) { if (escapingExpression instanceof BooleanLiteralExpression) { BooleanLiteralExpression expr = (BooleanLiteralExpression) escapingExpression; if (expr.getValue()) { java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING))); } else { java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING))); } } else { java.startIfBlock(escapingExpression); java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING))); java.endIfStartElseBlock(); java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING))); java.endIfBlock(); } } /** * <?cs escape:'html' > command. Changes default escaping function. */ @Override public void caseAEscapeCommand(AEscapeCommand node) { capturePosition(node.getPosition()); java.writeStatement(callOn(CONTEXT, "pushEscapingFunction", expressionTranslator .translateToString(node.getExpression()))); node.getCommand().apply(this); java.writeStatement(callOn(CONTEXT, "popEscapingFunction")); } /** * A fake command injected by AutoEscaper. * * AutoEscaper determines the html context in which an include or lvar or evar command is called * and stores this context in the AAutoescapeCommand node. This function loads the include or lvar * template in this stored context. */ @Override public void caseAAutoescapeCommand(AAutoescapeCommand node) { capturePosition(node.getPosition()); java.writeStatement(callOn(CONTEXT, "pushAutoEscapeMode", callOn(symbol("EscapeMode"), "computeEscapeMode", expressionTranslator.translateToString(node.getExpression())))); node.getCommand().apply(this); java.writeStatement(callOn(CONTEXT, "popAutoEscapeMode")); } /** * <?cs linclude:'somefile.cs' > command. Lazily includes another template (at render time). * Throw an error if file does not exist. */ @Override public void caseAHardLincludeCommand(AHardLincludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT)); } /** * <?cs linclude:'somefile.cs' > command. Lazily includes another template (at render time). * Silently ignore if the included file does not exist. */ @Override public void caseALincludeCommand(ALincludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT)); } /** * <?cs include!'somefile.cs' > command. Throw an error if file does not exist. */ @Override public void caseAHardIncludeCommand(AHardIncludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT)); } /** * <?cs include:'somefile.cs' > command. Silently ignore if the included file does not * exist. */ @Override public void caseAIncludeCommand(AIncludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT)); } /** * <?cs lvar:blah > command. Evaluate expression and execute commands within. */ @Override public void caseALvarCommand(ALvarCommand node) { capturePosition(node.getPosition()); evaluateVariable(node.getExpression(), "[lvar expression]"); } /** * <?cs evar:blah > command. Evaluate expression and execute commands within. */ @Override public void caseAEvarCommand(AEvarCommand node) { capturePosition(node.getPosition()); evaluateVariable(node.getExpression(), "[evar expression]"); } private void evaluateVariable(PExpression expression, String stackTraceDescription) { java.writeStatement(callOn(callOn(TEMPLATE_LOADER, "createTemp", string(stackTraceDescription), expressionTranslator.translateToString(expression), callOn(CONTEXT, "getAutoEscapeMode")), "render", CONTEXT)); } /** * <?cs def:someMacro(x,y) > ... <?cs /def > command. Define a macro (available for * the remainder of the context). */ @Override public void caseADefCommand(ADefCommand node) { capturePosition(node.getPosition()); // This doesn't actually define the macro body yet, it just calls: // registerMacro("someMacroName", someReference); // where someReference is defined as a field later on (with the body). String name = makeWord(node.getMacro()); if (macroMap.containsKey(name)) { // this is a duplicated definition. // TODO: Handle duplicates correctly. } // Keep track of the macro so we can generate the body later. // See MacroTransformer. addMacro(name, macro("macro" + macroMap.size()), node); } /** * This is a special tree walker that's called after the render() method has been generated to * create the macro definitions and their bodies. * * It basically generates fields that look like this: * * private final Macro macro1 = new CompiledMacro("myMacro", "arg1", "arg2"...) { public void * render(Data data, RenderingContext context) { // macro body. } }; */ private class MacroTransformer { public void parseDefNode(JavaExpression macroName, ADefCommand node) { java.startField("Macro", macroName); // Parameters passed to constructor. First is name of macro, the rest // are the name of the arguments. // e.g. cs def:doStuff(person, cheese) // -> new CompiledMacro("doStuff", "person", "cheese") { .. }. int i = 0; JavaExpression[] args = new JavaExpression[1 + node.getArguments().size()]; args[i++] = string(makeWord(node.getMacro())); for (PVariable argName : node.getArguments()) { args[i++] = variableTranslator.translate(argName); } java.startAnonymousClass("CompiledMacro", args); java.startMethod(RENDER_METHOD, "context"); java.writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT, "getDataContext"))); java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE)); // If !context.isRuntimeAutoEscaping(), enable runtime autoescaping for macro call. String tempVariableName = generateTempVariable("doRuntimeAutoEscaping"); JavaExpression value = JavaExpression.prefix(Type.BOOLEAN, "!", callOn(CONTEXT, "isRuntimeAutoEscaping")); JavaExpression stmt = declare(Type.BOOLEAN, tempVariableName, value); java.writeStatement(stmt); JavaExpression doRuntimeAutoEscaping = symbol(Type.BOOLEAN, tempVariableName); java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN)); java.writeStatement(callOn(CONTEXT, "startRuntimeAutoEscaping")); java.endIfBlock(); node.getCommand().apply(TemplateTranslator.this); java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN)); java.writeStatement(callOn(CONTEXT, "stopRuntimeAutoEscaping")); java.endIfBlock(); java.writeStatement(callOn(CONTEXT, "popExecutionContext")); java.endMethod(); java.endAnonymousClass(); java.endField(); } } private String makeWord(LinkedList words) { if (words.size() == 1) { return words.getFirst().getText(); } StringBuilder result = new StringBuilder(); for (TWord word : words) { if (result.length() > 0) { result.append('.'); } result.append(word.getText()); } return result.toString(); } /** * <?cs call:someMacro(x,y) command. Call a macro. */ @Override public void caseACallCommand(ACallCommand node) { capturePosition(node.getPosition()); String name = makeWord(node.getMacro()); java.startScopedBlock(); java.writeComment("call:" + name); // Lookup macro. // The expression used for the macro will either be the name of the // static Macro object representing the macro (if we can statically // determine it), or will be a temporary Macro variable (named // 'macroCall###') that gets the result of findMacro at evaluation time. JavaExpression macroCalled; MacroInfo macroInfo = macroMap.get(name); if (macroInfo == null) { // We never saw the definition of the macro. Assume it might come in an // included file and look it up at render time. String macroCall = generateTempVariable("macroCall"); java .writeStatement(declare(Type.MACRO, macroCall, callOn(CONTEXT, "findMacro", string(name)))); macroCalled = macro(macroCall); } else { macroCalled = macroInfo.symbol; } int numArgs = node.getArguments().size(); if (numArgs > 0) { // TODO: Add check that number of arguments passed in equals the // number expected by the macro. This should be done at translation // time in a future CL. JavaExpression[] argValues = new JavaExpression[numArgs]; JavaExpression[] argStatus = new JavaExpression[numArgs]; // Read the values first in case the new local variables shares the same // name as a variable (or variable expansion) being passed in to the macro. int i = 0; for (PExpression argNode : node.getArguments()) { JavaExpression value = expressionTranslator.translateUntyped(argNode); if (value.getType() != Type.VAR_NAME) { value = value.cast(Type.STRING); } String valueName = generateTempVariable("argValue"); java.writeStatement(declare(Type.STRING, valueName, value)); argValues[i] = JavaExpression.symbol(value.getType(), valueName); if (propagateEscapeStatus) { argStatus[i] = escapingEvaluator.computeEscaping(argNode, propagateEscapeStatus); } else { argStatus[i] = JavaExpression.symbol("EscapeMode.ESCAPE_NONE"); } i++; } // Push a new local variable scope for this macro execution. java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); // Create the local variables for each argument. for (i = 0; i < argValues.length; i++) { JavaExpression value = argValues[i]; JavaExpression tempVar = callOn(macroCalled, "getArgumentName", integer(i)); String methodName; if (value.getType() == Type.VAR_NAME) { methodName = "createLocalVariableByPath"; java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value)); } else { // Must be String as we cast it above. methodName = "createLocalVariableByValue"; java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value, argStatus[i])); } } } // Render macro. java.writeStatement(callOn(macroCalled, "render", CONTEXT)); if (numArgs > 0) { // Release the variable scope used by the macro call java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); } java.endScopedBlock(); } /** * Walks the PPosition tree, which calls {@link #caseTCsOpen(TCsOpen)} below. This is simply to * capture the position of the node in the original template file, to help developers diagnose * errors. */ private void capturePosition(PPosition position) { position.apply(this); } /** * Every time a <cs token is found, grab the line and column and call * context.setCurrentPosition() so this is captured for stack traces. */ @Override public void caseTCsOpen(TCsOpen node) { int line = node.getLine(); int column = node.getPos(); java.writeStatement(callOn(CONTEXT, "setCurrentPosition", JavaExpression.integer(line), JavaExpression.integer(column))); } private String generateTempVariable(String prefix) { return prefix + tempVariable++; } } src/com/google/clearsilver/jsilver/compiler/VariableTranslator.java0100644 0000000 0000000 00000012564 13662126234 024710 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.compiler; import static com.google.clearsilver.jsilver.compiler.JavaExpression.StringExpression; import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type; import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.ADecNumberVariable; import com.google.clearsilver.jsilver.syntax.node.ADescendVariable; import com.google.clearsilver.jsilver.syntax.node.AExpandVariable; import com.google.clearsilver.jsilver.syntax.node.AHexNumberVariable; import com.google.clearsilver.jsilver.syntax.node.ANameVariable; import com.google.clearsilver.jsilver.syntax.node.PVariable; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; /** * Translates a variable name (e.g. search.results.3.title) into the Java code for use as a key in * looking up a variable (e.g. "search.results.3.title"). * * While it is possible to reuse an instance of this class repeatedly, it is not thread safe or * reentrant. Evaluating an expression such as: a.b[c.d] would require two instances. */ public class VariableTranslator extends DepthFirstAdapter { private List components; private final ExpressionTranslator expressionTranslator; public VariableTranslator(ExpressionTranslator expressionTranslator) { this.expressionTranslator = expressionTranslator; } /** * See class description. * * @param csVariable Variable node in template's AST. * @return Appropriate code (as JavaExpression). */ public JavaExpression translate(PVariable csVariable) { try { assert components == null; components = new ArrayList(); csVariable.apply(this); components = joinComponentsWithDots(components); components = combineAdjacentStrings(components); return concatenate(components); } finally { components = null; } } @Override public void caseANameVariable(ANameVariable node) { components.add(new StringExpression(node.getWord().getText())); } @Override public void caseADecNumberVariable(ADecNumberVariable node) { components.add(new StringExpression(node.getDecNumber().getText())); } @Override public void caseAHexNumberVariable(AHexNumberVariable node) { components.add(new StringExpression(node.getHexNumber().getText())); } @Override public void caseADescendVariable(ADescendVariable node) { node.getParent().apply(this); node.getChild().apply(this); } @Override public void caseAExpandVariable(AExpandVariable node) { node.getParent().apply(this); components.add(expressionTranslator.translateToString(node.getChild())); } /** * Inserts dots between each component in the path. * * e.g. from: "a", "b", something, "c" to: "a", ".", "b", ".", something, ".", "c" */ private List joinComponentsWithDots(List in) { List out = new ArrayList(in.size() * 2); for (JavaExpression component : in) { if (!out.isEmpty()) { out.add(DOT); } out.add(component); } return out; } private static final JavaExpression DOT = new StringExpression("."); /** * Combines adjacent strings. * * e.g. from: "a", ".", "b", ".", something, ".", "c" to : "a.b.", something, ".c" */ private List combineAdjacentStrings(List in) { assert !in.isEmpty(); List out = new ArrayList(in.size()); JavaExpression last = null; for (JavaExpression current : in) { if (last == null) { last = current; continue; } if (current instanceof StringExpression && last instanceof StringExpression) { // Last and current are both strings - combine them. StringExpression currentString = (StringExpression) current; StringExpression lastString = (StringExpression) last; last = new StringExpression(lastString.getValue() + currentString.getValue()); } else { out.add(last); last = current; } } out.add(last); return out; } /** * Concatenate a list of JavaExpressions into a single string. * * e.g. from: "a", "b", stuff to : "a" + "b" + stuff */ private JavaExpression concatenate(List expressions) { StringWriter buffer = new StringWriter(); PrintWriter out = new PrintWriter(buffer); boolean seenFirst = false; for (JavaExpression expression : expressions) { if (seenFirst) { out.print(" + "); } seenFirst = true; expression.write(out); } return literal(Type.VAR_NAME, buffer.toString()); } } src/com/google/clearsilver/jsilver/data/0040755 0000000 0000000 00000000000 13662126234 017340 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/data/AbstractData.java0100644 0000000 0000000 00000010153 13662126234 022535 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.io.IOException; /** * This class is meant to hold implementation common to different instances of Data interface. */ public abstract class AbstractData implements Data { protected EscapeMode escapeMode = EscapeMode.ESCAPE_NONE; public int getIntValue() { // If we ever use the integer value of a node to create the string // representation we must ensure that an empty node is not mistaken // for a node with the integer value '0'. return TypeConverter.asNumber(getValue()); } public boolean getBooleanValue() { // If we ever use the boolean value of a node to create the string // representation we must ensure that an empty node is not mistaken // for a node with the boolean value 'false'. return TypeConverter.asBoolean(getValue()); } // ******************* Convenience methods ******************* /** * Retrieves the value at the specified path in this HDF node's subtree. * * Use {@link #getValue(String)} in preference to ensure ClearSilver compatibility. */ public String getValue(String path, String defaultValue) { Data child = getChild(path); if (child == null) { return defaultValue; } else { String result = child.getValue(); return result == null ? defaultValue : result; } } /** * Retrieves the integer value at the specified path in this HDF node's subtree. If the value does * not exist, or cannot be converted to an integer, default_value will be returned. * * Use {@link #getValue(String)} in preference to ensure ClearSilver compatibility. */ public int getIntValue(String path, int defaultValue) { Data child = getChild(path); if (child == null) { return defaultValue; } else { String result = child.getValue(); try { return result == null ? defaultValue : TypeConverter.parseNumber(result); } catch (NumberFormatException e) { return defaultValue; } } } /** * Retrieves the value at the specified path in this HDF node's subtree. If not found, returns * null. */ public String getValue(String path) { return getValue(path, null); } /** * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid, * returns 0. */ public int getIntValue(String path) { return TypeConverter.asNumber(getChild(path)); } /** * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid, * returns false. */ public boolean getBooleanValue(String path) { return TypeConverter.asBoolean(getChild(path)); } /** * Sets the value at the specified path in this HDF node's subtree. */ public void setValue(String path, String value) { Data child = createChild(path); child.setValue(value); } // ******************* String representation ******************* @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); toString(stringBuilder, 0); return stringBuilder.toString(); } public void toString(StringBuilder out, int indent) { try { write(out, indent); } catch (IOException ioe) { throw new RuntimeException(ioe); // COV_NF_LINE } } @Override public void optimize() { // Do nothing. } @Override public void setEscapeMode(EscapeMode mode) { this.escapeMode = mode; } @Override public EscapeMode getEscapeMode() { return escapeMode; } } src/com/google/clearsilver/jsilver/data/ChainedData.java0100644 0000000 0000000 00000014010 13662126234 022321 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; /** * Implementation of Data that allows for multiple underlying Data objects and checks each one in * order for a value before giving up. Behaves like local HDF and global HDF in the JNI * implementation of Clearsilver. This is only meant to be a root Data object and hardcodes that * fact. *

* Note: If you have elements foo.1, foo.2, foo.3 in first Data object and foo.4, foo.5, foo.6 in * second Data object, then fetching children of foo will return only foo.1 foo.2 foo.3 from first * Data object. */ public class ChainedData extends DelegatedData { public static final Logger logger = Logger.getLogger(ChainedData.class.getName()); // This mode allows developers to locate occurrences where they set the same HDF // variable in multiple Data objects in the chain, which usually indicates // bad planning or misuse. public static final boolean DEBUG_MULTIPLE_ASSIGNMENTS = false; Data[] dataList; /** * Optmization for case of single item. * * @param data a single data object to wrap. */ public ChainedData(Data data) { super(data); this.dataList = new Data[] {data}; } public ChainedData(Data... dataList) { super(getFirstData(dataList)); this.dataList = dataList; } public ChainedData(List dataList) { super(getFirstData(dataList)); this.dataList = dataList.toArray(new Data[dataList.size()]); } @Override protected DelegatedData newInstance(Data newDelegate) { return newDelegate == null ? null : new ChainedData(newDelegate); } private static Data getFirstData(Data[] dataList) { if (dataList.length == 0) { throw new IllegalArgumentException("Must pass in at least one Data object to ChainedData."); } Data first = dataList[0]; if (first == null) { throw new IllegalArgumentException("ChainedData does not accept null Data objects."); } return first; } private static Data getFirstData(List dataList) { if (dataList.size() == 0) { throw new IllegalArgumentException("Must pass in at least one Data object to ChainedData."); } Data first = dataList.get(0); if (first == null) { throw new IllegalArgumentException("ChainedData does not accept null Data objects."); } return first; } @Override public Data getChild(String path) { ArrayList children = null; Data first = null; for (Data d : dataList) { Data child = d.getChild(path); if (child != null) { if (!DEBUG_MULTIPLE_ASSIGNMENTS) { // If not in debug mode just return the first match. This assumes we are using the new // style of VariableLocator that does not iteratively ask for each HDF path element // separately. return child; } if (first == null) { // First match found first = child; } else if (children == null) { // Second match found children = new ArrayList(dataList.length); children.add(first); children.add(child); } else { // Third or more match found children.add(child); } } } if (children == null) { // 0 or 1 matches. Return first which is null or Data. return first; } else { // Multiple matches. Pass back the first item found. This is only hit when // DEBUG_MULTIPLE_ASSIGNMENTS is true. logger.info("Found " + children.size() + " matches for path " + path); return first; } } @Override public Data createChild(String path) { Data child = getChild(path); if (child != null) { return child; } else { // We don't call super because we don't want to wrap the result in DelegatedData. return dataList[0].createChild(path); } } @Override public String getValue(String path, String defaultValue) { Data child = getChild(path); if (child != null && child.getValue() != null) { return child.getValue(); } else { return defaultValue; } } @Override public int getIntValue(String path, int defaultValue) { Data child = getChild(path); if (child != null) { String value = child.getValue(); try { return value == null ? defaultValue : TypeConverter.parseNumber(value); } catch (NumberFormatException e) { return defaultValue; } } else { return defaultValue; } } @Override public String getValue(String path) { Data child = getChild(path); if (child != null) { return child.getValue(); } else { return null; } } @Override public int getIntValue(String path) { Data child = getChild(path); if (child != null) { return child.getIntValue(); } else { return 0; } } @Override public boolean getBooleanValue(String path) { Data child = getChild(path); if (child != null) { return child.getBooleanValue(); } else { return false; } } @Override public void toString(StringBuilder out, int indent) { for (Data d : dataList) { d.toString(out, indent); } } @Override public void write(Appendable out, int indent) throws IOException { for (Data d : dataList) { d.write(out, indent); } } @Override public void optimize() { for (Data d : dataList) { d.optimize(); } } } src/com/google/clearsilver/jsilver/data/Data.java0100644 0000000 0000000 00000020117 13662126234 021052 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.io.IOException; import java.util.Map; /** * Represents a hierarchical data set of primitives. * * This is the JSilver equivalent to ClearSilver's HDF object. */ public interface Data { // ******************* Node data ******************* /** * Returns the name of this HDF node. The root node has no name, so calling this on the root node * will return null. */ String getName(); /** * Returns the value of this HDF node, or null if this node has no value. Every node in the tree * can have a value, a child, and a next peer. */ String getValue(); /** * Returns the integer value of this HDF node, or 0 if this node has no value. * * Note: The fact that this method returns a primitive type, rather than an Integer means that its * value cannot be used to determine whether the data node exists or not. Note also that, when * implementing a Data object that caches these values, care must be taken to ensure that a node * with an integer value of '0' is not mistaken for a non-existent node. */ int getIntValue(); /** * Returns the boolean value of this HDF node, or false if this node has no value. * * Note: The fact that this method returns a primitive type, rather than a Boolean means that its * value cannot be used to determine whether the data node exists or not. Note also that, when * implementing a Data object that caches these values, care must be taken to ensure that a node * with a boolean value of 'false' is not mistaken for a non-existent node. */ boolean getBooleanValue(); /** * Set the value of this node. Any symlink that may have been set for this node will be replaced. */ void setValue(String value); /** * Returns the full path to this node via its parent links. */ String getFullPath(); // ******************* Attributes ******************* /** * Sets an attribute key and value on the current node, replacing any existing value. * * @param key the name of the attribute to add/modify. * @param value the value to assign it. Value of {@code null} will clear the attribute. */ void setAttribute(String key, String value); /** * Returns the value of the node attribute with the given name, or {@code null} if there is no * value. */ String getAttribute(String key); /** * Returns {@code true} if the node contains an attribute with the given name, {@code false} * otherwise. */ boolean hasAttribute(String key); /** * Returns the number of attributes on this node. */ int getAttributeCount(); /** * Returns an iterable collection of attribute name/value pairs. * * @return an object that can be iterated over to get all the attribute name/value pairs. Should * return empty iterator if there are no attributes. */ Iterable> getAttributes(); // ******************* Children ******************* /** * Return the root of the tree where the current node lies. If the current node is the root, * return this. */ Data getRoot(); /** * Get the parent node. */ Data getParent(); /** * Is this the first of its siblings? */ boolean isFirstSibling(); /** * Is this the last of its siblings? */ boolean isLastSibling(); /** * Retrieves the node representing the next sibling of this Data node, if any. * * @return the next sibling Data object or {@code null} if this is the last sibling. */ Data getNextSibling(); /** * Returns number of child nodes. */ int getChildCount(); /** * Returns children of this node. */ Iterable getChildren(); /** * Retrieves the object that is the root of the subtree at hdfpath, returning null if the subtree * doesn't exist */ Data getChild(String path); /** * Retrieves the HDF object that is the root of the subtree at hdfpath, create the subtree if it * doesn't exist */ Data createChild(String path); /** * Remove the specified subtree. */ void removeTree(String path); // ******************* Symbolic links ******************* /** * Set the source node to be a symbolic link to the destination. */ void setSymlink(String sourcePath, String destinationPath); /** * Set the source node to be a symbolic link to the destination. */ void setSymlink(String sourcePath, Data destination); /** * Set this node to be a symbolic link to another node. */ void setSymlink(Data symLink); /** * Retrieve the symbolic link this node points to. Will return reference to self if not a symlink. */ Data getSymlink(); // **************************** Copy ************************** /** * Does a deep copy of the attributes and values from one node to another. * * @param toPath destination path for the deep copy. * @param from Data object that should be copied over. */ void copy(String toPath, Data from); /** * Does a deep copy the attributes and values from one node to another * * @param from Data object whose value should be copied over. */ void copy(Data from); // ******************* Convenience methods ******************* /** * Retrieves the value at the specified path in this HDF node's subtree. */ String getValue(String path, String defaultValue); /** * Retrieves the integer value at the specified path in this HDF node's subtree. If the value does * not exist, or cannot be converted to an integer, default_value will be returned. */ int getIntValue(String path, int defaultValue); /** * Retrieves the value at the specified path in this HDF node's subtree. If not found, returns * null. */ String getValue(String path); /** * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid, * returns 0. */ int getIntValue(String path); /** * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid, * returns false. */ boolean getBooleanValue(String path); /** * Sets the value at the specified path in this HDF node's subtree. */ void setValue(String path, String value); // ******************* String representation ******************* String toString(); void toString(StringBuilder out, int indent); /** * Write out the String representation of this HDF node. */ void write(Appendable out, int indent) throws IOException; /** * Optimizes the Data structure for performance. This is a somewhat expensive operation that * should improve CPU and/or memory usage for long-lived Data objects. For example, it may * internalize all Strings to reduce redundant copies. */ void optimize(); /** * Indicates the escaping, if any that was applied to this HDF node. * * @return EscapeMode that was applied to this node's value. {@code EscapeMode.ESCAPE_NONE} if the * value is not escaped. {@code EscapeMode.ESCAPE_IS_CONSTANT} if value is a string or * numeric literal. * * @see #setEscapeMode * @see EscapeMode */ EscapeMode getEscapeMode(); /** * Set the escaping that was applied to this HDF node. This method may be called by the template * renderer, for instance, when a "set" command sets the node to a constant string. It may also be * explicitly called if populating the HDF with pre-escaped or trusted values. * * @see #getEscapeMode */ void setEscapeMode(EscapeMode mode); } src/com/google/clearsilver/jsilver/data/DataContext.java0100644 0000000 0000000 00000011602 13662126234 022416 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; /** * Manages the global Data object and local variable mappings during rendering. */ public interface DataContext { /** * Returns the main Data object this RenderingContext was defined with. */ Data getRootData(); /** * Creates a new Data object to hold local references, pushes it onto the variable map stack. This * is used to hold macro parameters after a call command, and local variables defined for 'each' * and 'with'. */ void pushVariableScope(); /** * Removes the most recent Data object added to the local variable map stack. */ void popVariableScope(); /** * Creates and sets a local variable in the current scope equal to the given value. * * @param name the name of the local variable to fetch or create. * @param value The String value to store at the local variable. */ void createLocalVariableByValue(String name, String value); /** * Creates and sets a local variable in the current scope equal to the given value. Also set the * EscapeMode that was applied to its value. This may be used by the template renderer to decide * whether to autoescape the variable. * * @param name the name of the local variable to fetch or create. * @param value The String value to store at the local variable. * @param mode EscapeMode that describes the escaping this variable has. {@code * EscapeMode.ESCAPE_NONE} if the variable was not escaped. {@code * EscapeMode.ESCAPE_IS_CONSTANT} if the variable was populated with a string or numeric * literal. */ void createLocalVariableByValue(String name, String value, EscapeMode mode); /** * Creates and sets a local variable in the current scope equal to the given value. *

* This method is a helper method for supporting the first() and last() functions on loops without * requiring loops create a full Data tree. * * @param name the name of the local variable to fetch or create. * @param value The String value to store at the local variable. * @param isFirst What the local variable should return for * {@link com.google.clearsilver.jsilver.data.Data#isFirstSibling} * @param isLast What the local variable should return for * {@link com.google.clearsilver.jsilver.data.Data#isLastSibling} */ void createLocalVariableByValue(String name, String value, boolean isFirst, boolean isLast); /** * Creates a local variable that references a (possibly non-existent) Data node. When the Data * object for this variable is requested, it will return the Data object at the path location or * {@code null}, if it does not exist. If {@link #findVariable} is called with {@code create == * true}, then if no Data object exists at the path location, it will be created. * * @param name the name of the local variable to fetch or create. * @param path The path to the Data object */ void createLocalVariableByPath(String name, String path); /** * Searches the variable map stack for the specified variable name. If not found, it then searches * the root Data object. If not found then and create is {@code true}, then a new node is created * with the given name in the root Data object and that node is returned. *

* Note: This only creates nodes in the root Data object, not in any local variable map. To do * that, use the Data node returned by {@link #pushVariableScope()}. * * @param name the name of the variable to find and/or create. * @param create if {@link true} then a new node will be created if an existing Data node with the * given name does not exist. * @return The first Data node in the variable map stack that matches the given name, or a Data * node in the root Data object matching the given name, or {@code null} if no such node * exists and {@code create} is {@code false}. */ Data findVariable(String name, boolean create); /** * Searches the variable map stack for the specified variable name, and returns its * {@link EscapeMode}. * * @return If the variable is found, returns its {@link EscapeMode}, otherwise returns {@code * EscapeMode.ESCAPE_NONE}. */ EscapeMode findVariableEscapeMode(String name); } src/com/google/clearsilver/jsilver/data/DataFactory.java0100644 0000000 0000000 00000002745 13662126234 022411 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import java.io.IOException; /** * Loads data from resources. */ public interface DataFactory { /** * Create new Data instance, ready to be populated. */ Data createData(); /** * Loads data in Hierarchical Data Format (HDF) into an existing Data object. */ void loadData(final String dataFileName, ResourceLoader resourceLoader, Data output) throws JSilverBadSyntaxException, IOException; /** * Loads data in Hierarchical Data Format (HDF) into a new Data object. */ Data loadData(String dataFileName, ResourceLoader resourceLoader) throws IOException; /** * Returns the Parser used by this factory to parse the HDF content. * * @return */ Parser getParser(); } src/com/google/clearsilver/jsilver/data/DefaultData.java0100644 0000000 0000000 00000001570 13662126234 022361 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; /** * Default implementation of Data. * * If you're not sure what Data implementation to use, just use this one - it will be a sensible * option. * * @see Data * @see NestedMapData */ public class DefaultData extends NestedMapData { } src/com/google/clearsilver/jsilver/data/DefaultDataContext.java0100644 0000000 0000000 00000026127 13662126234 023733 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.io.IOException; import java.util.Map; /** * This is the basic implementation of the DataContext. It stores the root Data node and a stack of * Data objects that hold local variables. By definition, local variables are limited to single HDF * names, with no '.' allowed. We use this to limit our search in the stack for the first occurence * of the first chunk in the variable name. */ public class DefaultDataContext implements DataContext { /** * Root node of the Data structure used by the current context. */ private final Data rootData; /** * Head of the linked list of local variables, starting with the newest variable created. */ private LocalVariable head = null; /** * Indicates whether the renderer has pushed a new variable scope but no variable has been created * yet. */ private boolean newScope = false; public DefaultDataContext(Data data) { if (data == null) { throw new IllegalArgumentException("rootData is null"); } this.rootData = data; } @Override public Data getRootData() { return rootData; } /** * Starts a new variable scope. It is illegal to call this twice in a row without declaring a * local variable. */ @Override public void pushVariableScope() { if (newScope) { throw new IllegalStateException("PushVariableScope called twice with no " + "variables declared in between."); } newScope = true; } /** * Removes the current variable scope and references to the variables in it. It is illegal to call * this more times than {@link #pushVariableScope} has been called. */ @Override public void popVariableScope() { if (newScope) { // We pushed but created no local variables. newScope = false; } else { // Note, this will throw a NullPointerException if there is no scope to // pop. head = head.nextScope; } } @Override public void createLocalVariableByValue(String name, String value) { createLocalVariableByValue(name, value, EscapeMode.ESCAPE_NONE); } @Override public void createLocalVariableByValue(String name, String value, EscapeMode mode) { LocalVariable local = createLocalVariable(name); local.value = value; local.isPath = false; local.setEscapeMode(mode); } @Override public void createLocalVariableByValue(String name, String value, boolean isFirst, boolean isLast) { LocalVariable local = createLocalVariable(name); local.value = value; local.isPath = false; local.isFirst = isFirst; local.isLast = isLast; } @Override public void createLocalVariableByPath(String name, String path) { LocalVariable local = createLocalVariable(name); local.value = path; local.isPath = true; } private LocalVariable createLocalVariable(String name) { if (head == null && !newScope) { throw new IllegalStateException("Must call pushVariableScope before " + "creating local variable."); } // First look for a local variable with the same name in the current scope // and return that if it exists. If we don't do this then loops and each // can cause the list of local variables to explode. // // We only look at the first local variable (head) if it is part of the // current scope (we're not defining a new scope). Since each and loop // variables are always in their own scope, there would only be one variable // in the current scope if it was a reuse case. For macro calls (which are // the only other way createLocalVariable is called multiple times in a // single scope and thus head may not be the only local variable in the // current scope) it is illegal to use the same argument name more than // once. Therefore we don't need to worry about checking to see if the new // local variable name matches beyond the first local variable in the // current scope. if (!newScope && head != null && name.equals(head.name)) { // Clear out the fields that aren't set by the callers. head.isFirst = true; head.isLast = true; head.node = null; return head; } LocalVariable local = new LocalVariable(); local.name = name; local.next = head; if (newScope) { local.nextScope = head; newScope = false; } else if (head != null) { local.nextScope = head.nextScope; } else { local.nextScope = null; } head = local; return local; } @Override public Data findVariable(String name, boolean create) { return findVariable(name, create, head); } @Override public EscapeMode findVariableEscapeMode(String name) { Data var = findVariable(name, false); if (var == null) { return EscapeMode.ESCAPE_NONE; } else { return var.getEscapeMode(); } } private Data findVariable(String name, boolean create, LocalVariable start) { // When traversing the stack, we first look for the first chunk of the // name. This is so we respect scope. If we are searching for 'foo.bar' // and 'foo' is defined in many scopes, we should stop searching // after the first time we find 'foo', even if that local variable does // not have a child 'bar'. String firstChunk = name; int dot = name.indexOf('.'); if (dot != -1) { firstChunk = name.substring(0, dot); } LocalVariable curr = start; while (curr != null) { if (curr.name.equals(firstChunk)) { if (curr.isPath) { // The local variable references another Data node, dereference it. if (curr.node == null) { // We haven't resolved the path yet. Do it now and cache it if // not null. Note we begin looking for the dereferenced in the next // scope. curr.node = findVariable(curr.value, create, curr.nextScope); if (curr.node == null) { // Node does not exist. Any children won't either. return null; } } // We have a reference to the Data node directly. Use it. if (dot == -1) { // This is the node we're interested in. return curr.node; } else { if (create) { return curr.node.createChild(name.substring(dot + 1)); } else { return curr.node.getChild(name.substring(dot + 1)); } } } else { // This is a literal value local variable. It has no children, nor // can it. We want to throw an error on creation of children. if (dot == -1) { return curr; } if (create) { throw new IllegalStateException("Cannot create children of a " + "local literal variable"); } else { // No children. return null; } } } curr = curr.next; } if (create) { return rootData.createChild(name); } else { return rootData.getChild(name); } } /** * This class holds the name and value/path of a local variable. Objects of this type should only * be exposed outside of this class for value-based local variables. *

* Fields are not private to avoid the performance overhead of hidden access methods used for * outer classes to access private fields of inner classes. */ private static class LocalVariable extends AbstractData { // Pointer to next LocalVariable in the list. LocalVariable next; // Pointer to the first LocalVariable in the next scope down. LocalVariable nextScope; String name; String value; // True if value represents the path to another Data variable. boolean isPath; // Once the path resolves to a valid Data node, store it here to avoid // refetching. Data node = null; // Used only for loop local variables boolean isFirst = true; boolean isLast = true; public String getName() { return name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getFullPath() { return name; } public void setAttribute(String key, String value) { throw new UnsupportedOperationException("Not allowed on local variables."); } public String getAttribute(String key) { return null; } public boolean hasAttribute(String key) { return false; } public int getAttributeCount() { return 0; } public Iterable> getAttributes() { return null; } public Data getRoot() { return null; } public Data getParent() { return null; } public boolean isFirstSibling() { return isFirst; } public boolean isLastSibling() { return isLast; } public Data getNextSibling() { throw new UnsupportedOperationException("Not allowed on local variables."); } public int getChildCount() { return 0; } public Iterable getChildren() { return null; } public Data getChild(String path) { return null; } public Data createChild(String path) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void removeTree(String path) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void setSymlink(String sourcePath, String destinationPath) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void setSymlink(String sourcePath, Data destination) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void setSymlink(Data symLink) { throw new UnsupportedOperationException("Not allowed on local variables."); } public Data getSymlink() { return this; } public void copy(String toPath, Data from) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void copy(Data from) { throw new UnsupportedOperationException("Not allowed on local variables."); } public String getValue(String path, String defaultValue) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void write(Appendable out, int indent) throws IOException { for (int i = 0; i < indent; i++) { out.append(" "); } out.append(getName()).append(" = ").append(getValue()); } } } src/com/google/clearsilver/jsilver/data/DefaultHdfParser.java0100644 0000000 0000000 00000011301 13662126234 023357 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; import java.util.ArrayList; import java.util.List; /** * Parses data in HierachicalDataFormat (HDF), generating callbacks for data encountered in the * stream. */ public class DefaultHdfParser implements Parser { private int initialContextSize = 10; public void parse(Reader reader, Data output, ErrorHandler errorHandler, ResourceLoader resourceLoader, String dataFileName, boolean ignoreAttributes) throws IOException { LineNumberReader lineReader = new LineNumberReader(reader); // Although a linked list could be used here, we iterate a lot and the // size will rarely get > 10 deep. In this case ArrayList is faster than // LinkedList. List context = new ArrayList(initialContextSize); String line; while ((line = lineReader.readLine()) != null) { parseLine(line, output, context, lineReader, dataFileName, errorHandler); } } private void parseLine(String line, Data output, List context, LineNumberReader lineReader, String dataFileName, ErrorHandler errorHandler) throws IOException { line = stripComment(line); Split split; if ((split = split(line, "=")) != null) { // some.thing = Hello output.setValue(createFullPath(context, split.left), split.right); } else if ((split = split(line, "<<")) != null) { // some.thing << EOM // Blah blah // Blah blah // EOM output.setValue(createFullPath(context, split.left), readToToken(lineReader, split.right)); } else if ((split = split(line, "{")) != null) { // some.thing { // ... context.add(split.left); } else if (split(line, "}") != null) { // ... // } context.remove(context.size() - 1); } else if ((split = split(line, ":")) != null) { // some.tree : another.tree output.setSymlink(createFullPath(context, split.left), split.right); } else if (line.trim().length() != 0) { // Anything else if (errorHandler != null) { errorHandler.error(lineReader.getLineNumber(), line, dataFileName, "Bad HDF syntax"); } } } private String stripComment(String line) { int commentPosition = line.indexOf('#'); int equalsPosition = line.indexOf('='); if (commentPosition > -1 && (equalsPosition == -1 || commentPosition < equalsPosition)) { return line.substring(0, commentPosition); } else { return line; } } /** * Reads lines from a reader until a line is encountered that matches token (or end of stream). */ private String readToToken(LineNumberReader reader, String token) throws IOException { StringBuilder result = new StringBuilder(); String line; while ((line = reader.readLine()) != null && !line.trim().equals(token)) { result.append(line).append('\n'); } return result.toString(); } /** * Creates the full path, based on the current context. */ private String createFullPath(List context, String subPath) { StringBuilder result = new StringBuilder(); for (String contextItem : context) { result.append(contextItem).append('.'); } result.append(subPath); return result.toString(); } /** * Split a line in two, based on a delimiter. If the delimiter is not found, null is returned. */ private Split split(String line, String delimiter) { int position = line.indexOf(delimiter); if (position > -1) { Split result = new Split(); result.left = line.substring(0, position).trim(); result.right = line.substring(position + delimiter.length()).trim(); return result; } else { return null; } } private static class Split { String left; String right; } /** * Returns a factory object that constructs DefaultHdfParser objects. */ public static ParserFactory newFactory() { return new ParserFactory() { public Parser newInstance() { return new DefaultHdfParser(); } }; } } src/com/google/clearsilver/jsilver/data/DelegatedData.java0100644 0000000 0000000 00000016070 13662126234 022654 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.io.IOException; import java.util.Iterator; import java.util.Map; /** * Class that wraps a Data object and exports the same interface. Useful for extending the * capabilities of an existing implementation. */ public class DelegatedData implements Data { private final Data delegate; public DelegatedData(Data delegate) { if (delegate == null) { throw new NullPointerException("Delegate Data must not be null."); } this.delegate = delegate; } /** * Subclasses will want to override this method to return a Data object of their specific type. * * @param newDelegate the Data object to wrap with a new delegator * @return a DelegateData type or subclass. */ protected DelegatedData newInstance(Data newDelegate) { return newDelegate == null ? null : new DelegatedData(newDelegate); } protected Data getDelegate() { return delegate; } protected static Data unwrap(Data data) { if (data instanceof DelegatedData) { data = ((DelegatedData) data).getDelegate(); } return data; } @Override public String getName() { return getDelegate().getName(); } @Override public String getValue() { return getDelegate().getValue(); } @Override public int getIntValue() { return getDelegate().getIntValue(); } @Override public boolean getBooleanValue() { return getDelegate().getBooleanValue(); } @Override public void setValue(String value) { getDelegate().setValue(value); } @Override public String getFullPath() { return getDelegate().getFullPath(); } @Override public void setAttribute(String key, String value) { getDelegate().setAttribute(key, value); } @Override public String getAttribute(String key) { return getDelegate().getAttribute(key); } @Override public boolean hasAttribute(String key) { return getDelegate().hasAttribute(key); } @Override public int getAttributeCount() { return getDelegate().getAttributeCount(); } @Override public Iterable> getAttributes() { return getDelegate().getAttributes(); } @Override public Data getRoot() { return newInstance(getDelegate().getRoot()); } @Override public Data getParent() { return newInstance(getDelegate().getParent()); } @Override public boolean isFirstSibling() { return getDelegate().isFirstSibling(); } @Override public boolean isLastSibling() { return getDelegate().isLastSibling(); } @Override public Data getNextSibling() { return newInstance(getDelegate().getNextSibling()); } @Override public int getChildCount() { return getDelegate().getChildCount(); } /** * Wrapping implementation of iterator that makes sure any Data object returned by the underlying * iterator is wrapped with the right DelegatedData type. */ protected class DelegatedIterator implements Iterator { private final Iterator iterator; DelegatedIterator(Iterator iterator) { this.iterator = iterator; } public boolean hasNext() { return iterator.hasNext(); } public DelegatedData next() { return newInstance(iterator.next()); } public void remove() { iterator.remove(); } } /** * Subclasses can override this method to return specialized child iterators. For example, if they * don't want to support the remove() operation. * * @return Iterator of children of delegate Data object that returns wrapped Data nodes. */ protected Iterator newChildIterator() { return new DelegatedIterator(getDelegate().getChildren().iterator()); } /** * Single Iterable object for each node. All it does is return a DelegatedIterator when asked for * iterator. */ private final Iterable delegatedIterable = new Iterable() { public Iterator iterator() { return newChildIterator(); } }; @Override public Iterable getChildren() { return delegatedIterable; } @Override public Data getChild(String path) { return newInstance(getDelegate().getChild(path)); } @Override public Data createChild(String path) { return newInstance(getDelegate().createChild(path)); } @Override public void removeTree(String path) { getDelegate().removeTree(path); } @Override public void setSymlink(String sourcePath, String destinationPath) { getDelegate().setSymlink(sourcePath, destinationPath); } @Override public void setSymlink(String sourcePath, Data destination) { destination = unwrap(destination); getDelegate().setSymlink(sourcePath, destination); } @Override public void setSymlink(Data symLink) { symLink = unwrap(symLink); getDelegate().setSymlink(symLink); } @Override public Data getSymlink() { return newInstance(getDelegate().getSymlink()); } @Override public void copy(String toPath, Data from) { from = unwrap(from); getDelegate().copy(toPath, from); } @Override public void copy(Data from) { from = unwrap(from); getDelegate().copy(from); } @Override public String getValue(String path, String defaultValue) { return getDelegate().getValue(path, defaultValue); } @Override public int getIntValue(String path, int defaultValue) { return getDelegate().getIntValue(path, defaultValue); } @Override public String getValue(String path) { return getDelegate().getValue(path); } @Override public int getIntValue(String path) { return getDelegate().getIntValue(path); } @Override public boolean getBooleanValue(String path) { return getDelegate().getBooleanValue(path); } @Override public void setValue(String path, String value) { getDelegate().setValue(path, value); } @Override public String toString() { return getDelegate().toString(); } @Override public void toString(StringBuilder out, int indent) { getDelegate().toString(out, indent); } @Override public void write(Appendable out, int indent) throws IOException { getDelegate().write(out, indent); } @Override public void optimize() { getDelegate().optimize(); } @Override public void setEscapeMode(EscapeMode mode) { getDelegate().setEscapeMode(mode); } @Override public EscapeMode getEscapeMode() { return getDelegate().getEscapeMode(); } } src/com/google/clearsilver/jsilver/data/HDFDataFactory.java0100644 0000000 0000000 00000005232 13662126234 022725 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; /** * Loads data in Hierarchical Data Format (HDF) into Data objects. */ public class HDFDataFactory implements DataFactory { private final Parser hdfParser; private final boolean ignoreAttributes; public HDFDataFactory(boolean ignoreAttributes) { this(ignoreAttributes, new NoOpStringInternStrategy()); } public HDFDataFactory(boolean ignoreAttributes, StringInternStrategy stringInternStrategy) { this(NewHdfParser.newFactory(stringInternStrategy), ignoreAttributes); } public HDFDataFactory(ParserFactory hdfParserFactory, boolean ignoreAttributes) { this.ignoreAttributes = ignoreAttributes; this.hdfParser = hdfParserFactory.newInstance(); } @Override public Data createData() { return new DefaultData(); } @Override public void loadData(final String dataFileName, ResourceLoader resourceLoader, Data output) throws JSilverBadSyntaxException, IOException { Reader reader = resourceLoader.open(dataFileName); if (reader == null) { throw new FileNotFoundException(dataFileName); } try { hdfParser.parse(reader, output, new Parser.ErrorHandler() { public void error(int line, String lineContent, String fileName, String errorMessage) { throw new JSilverBadSyntaxException("HDF parsing error : '" + errorMessage + "'", lineContent, fileName, line, JSilverBadSyntaxException.UNKNOWN_POSITION, null); } }, resourceLoader, dataFileName, ignoreAttributes); } finally { resourceLoader.close(reader); } } @Override public Data loadData(String dataFileName, ResourceLoader resourceLoader) throws IOException { Data result = createData(); loadData(dataFileName, resourceLoader, result); return result; } @Override public Parser getParser() { return hdfParser; } } src/com/google/clearsilver/jsilver/data/LocalAndGlobalData.java0100644 0000000 0000000 00000006053 13662126234 023574 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; /** * This is a special implementation of ChainedData to be used for holding the local and global Data * objects (like local and global HDFs in Clearsilver). It prevents writes and modifications to the * global Data object and applies them all to the local data object. */ public class LocalAndGlobalData extends ChainedData { private final Data local; /** * Creates a Data object that encapsulates both request-scoped local HDF and an application * global-scoped HDF that can be read from the template renderer. Part of the backwards * compatibility with JNI Clearsilver and its globalHdf support. * * @param local the request-specific HDF data that takes priority. * @param global application global HDF data that should be read but not written to from the * template renderer. */ public LocalAndGlobalData(Data local, Data global) { this(local, global, false); } /** * Creates a Data object that encapsulates both request-scoped local HDF and an application * global-scoped HDF that can be read from the template renderer. Part of the backwards * compatibility with JNI Clearsilver and its globalHdf support. We wrap the global HDF in an * UnmodifiableData delegate * * @param local the request-specific HDF data that takes priority. * @param global application global HDF data that should be read but not written to from the * template renderer. * @param allowGlobalDataModification if {@code true} then enable legacy mode where we do not wrap * the global Data with an Unmodifiable wrapper. Should not be set to {@code true} unless * incompatibilities or performance issues found. Note, that setting to {@code true} could * introduce bugs in templates that acquire local references to the global data structure * and then try to modify them, which is not the intended behavior. */ public LocalAndGlobalData(Data local, Data global, boolean allowGlobalDataModification) { super(local, prepareGlobal(global, allowGlobalDataModification)); this.local = local; } private static Data prepareGlobal(Data global, boolean allowGlobalDataModification) { if (allowGlobalDataModification) { return global; } else { return new UnmodifiableData(global); } } @Override public Data createChild(String path) { // We never want to modify the global Data object. return local.createChild(path); } } src/com/google/clearsilver/jsilver/data/NativeStringInternStrategy.java0100644 0000000 0000000 00000001631 13662126234 025521 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; /** * Implementation of {@link StringInternStrategy} using Java String Pool and {@link String#intern()} * method. */ public class NativeStringInternStrategy implements StringInternStrategy { @Override public String intern(String value) { return value.intern(); } } src/com/google/clearsilver/jsilver/data/NestedMapData.java0100644 0000000 0000000 00000044047 13662126234 022663 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; /** * Represents a hierarchical data set of primitives. * * This is the JSilver equivalent to ClearSilver's HDF object. * * This class has no synchronization logic. Follow the same thread-safety semantics as you would for * a java.util.ArrayList (i.e. concurrent reads allowed, but ensure you have exclusive access when * modifying - should not read whilst another thread writes). */ public class NestedMapData extends AbstractData { /** * Number of children a node can have before we bother allocating a HashMap. We currently allocate * the HashMap on the 5th child. */ private static final int CHILD_MAP_THRESHOLD = 4; private String name; private NestedMapData parent; private final NestedMapData root; // Lazily intitialized after CHILD_MAP_THRESHOLD is hit. private Map children = null; // Number of children private int childCount = 0; // First child (first sibling of children) private NestedMapData firstChild = null; // Last child (last sibling of children) private NestedMapData lastChild = null; /** * Single object returned by getChildren(). Constructs ChildrenIterator objects. */ private Iterable iterableChildren = null; // Holds the attributes for this HDF node. private Map attributeList = null; private String value = null; private NestedMapData symLink = this; // Doubly linked list of siblings. private NestedMapData prevSibling = null; private NestedMapData nextSibling = null; public NestedMapData() { name = null; parent = null; root = this; } protected NestedMapData(String name, NestedMapData parent, NestedMapData root) { this.name = name; this.parent = parent; this.root = root; } // ******************* Node creation and removal ******************* // Must be kept in sync. /** * Creates a child of this node. * * @param chunk name to give the new child node. * @return the NestedMapData object corresponding to the new node. */ protected NestedMapData createChildNode(String chunk) { NestedMapData sym = followSymLinkToTheBitterEnd(); NestedMapData data = new NestedMapData(chunk, sym, sym.root); // If the parent node's child count is 5 or more and it does not have a // children Hashmap, initialize it now. if (sym.children == null && sym.childCount >= CHILD_MAP_THRESHOLD) { sym.children = new HashMap(); // Copy in existing children. NestedMapData curr = sym.firstChild; while (curr != null) { sym.children.put(curr.getName(), curr); curr = curr.nextSibling; } } // If the parent node has a children map, add the new child node to it. if (sym.children != null) { sym.children.put(chunk, data); } data.prevSibling = sym.lastChild; if (sym.lastChild != null) { // Update previous lastChild to point to new child. sym.lastChild.nextSibling = data; } else { // There were no previous children so this is the first. sym.firstChild = data; } sym.lastChild = data; sym.childCount++; return data; } private void severNode() { if (parent == null) { return; } if (parent.children != null) { parent.children.remove(name); } if (prevSibling != null) { prevSibling.nextSibling = nextSibling; } else { parent.firstChild = nextSibling; } if (nextSibling != null) { nextSibling.prevSibling = prevSibling; } else { parent.lastChild = prevSibling; } parent.childCount--; // Need to cleal the parent pointer or else if someone has a direct reference to this node // they will get very strange results. parent = null; } // ******************* Node data ******************* /** * Returns the name of this HDF node. The root node has no name, so calling this on the root node * will return null. */ public String getName() { return name; } /** * Recursive method that builds the full path to this node via its parent links into the given * StringBuilder. */ private void getPathName(StringBuilder sb) { if (parent != null && parent != root) { parent.getPathName(sb); sb.append("."); } String name = getName(); if (name != null) { sb.append(name); } } /** * Returns the full path to this node via its parent links. */ public String getFullPath() { StringBuilder sb = new StringBuilder(); getPathName(sb); return sb.toString(); } /** * Returns the value of this HDF node, or null if this node has no value. Every node in the tree * can have a value, a child, and a next peer. */ public String getValue() { return followSymLinkToTheBitterEnd().value; } /** * Set the value of this node. Any symlink that may have been set for this node will be replaced. */ public void setValue(String value) { // Clearsilver behaviour is to replace any symlink that may already exist // here with the new value, rather than following the symlink. this.symLink = this; this.value = value; } // ******************* Attributes ******************* // We don't expect attributes to be heavily used. They are not used for template parsing. public void setAttribute(String key, String value) { if (key == null) { throw new NullPointerException("Attribute name cannot be null."); } if (attributeList == null) { // Do we need to worry about synchronization? attributeList = new HashMap(); } if (value == null) { attributeList.remove(key); } else { attributeList.put(key, value); } } public String getAttribute(String key) { return attributeList == null ? null : attributeList.get(key); } public boolean hasAttribute(String key) { return attributeList != null && attributeList.containsKey(key); } public int getAttributeCount() { return attributeList == null ? 0 : attributeList.size(); } public Iterable> getAttributes() { if (attributeList == null) { return Collections.emptySet(); } return attributeList.entrySet(); } // ******************* Children ******************* /** * Return the root of the tree where the current node lies. If the current node is the root, * return this. */ public Data getRoot() { return root; } /** * Get the parent node. */ public Data getParent() { return parent; } /** * Is this the first of its siblings? */ @Override public boolean isFirstSibling() { return prevSibling == null; } /** * Is this the last of its siblings? */ @Override public boolean isLastSibling() { return nextSibling == null; } public Data getNextSibling() { return nextSibling; } /** * Returns number of child nodes. */ @Override public int getChildCount() { return followSymLinkToTheBitterEnd().childCount; } /** * Returns children of this node. */ @Override public Iterable getChildren() { if (iterableChildren == null) { iterableChildren = new IterableChildren(); } return iterableChildren; } /** * Retrieves the object that is the root of the subtree at hdfpath, returning null if the subtree * doesn't exist */ public NestedMapData getChild(String path) { NestedMapData current = this; for (int lastDot = 0, nextDot = 0; nextDot != -1 && current != null; lastDot = nextDot + 1) { nextDot = path.indexOf('.', lastDot); String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot); current = current.followSymLinkToTheBitterEnd().getChildNode(chunk); } return current; } /** * Retrieves the HDF object that is the root of the subtree at hdfpath, create the subtree if it * doesn't exist */ public NestedMapData createChild(String path) { NestedMapData current = this; for (int lastDot = 0, nextDot = 0; nextDot != -1; lastDot = nextDot + 1) { nextDot = path.indexOf('.', lastDot); String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot); NestedMapData currentSymLink = current.followSymLinkToTheBitterEnd(); current = currentSymLink.getChildNode(chunk); if (current == null) { current = currentSymLink.createChildNode(chunk); } } return current; } /** * Non-recursive method that only returns a Data node if this node has a child whose name matches * the specified name. * * @param name String containing the name of the child to look for. * @return a Data node that is the child of this node and named 'name', otherwise {@code null}. */ private NestedMapData getChildNode(String name) { NestedMapData sym = followSymLinkToTheBitterEnd(); if (sym.getChildCount() == 0) { // No children. Just return null. return null; } if (sym.children != null) { // children map exists. Look it up there. return sym.children.get(name); } // Iterate through linked list of children and look for a name match. NestedMapData curr = sym.firstChild; while (curr != null) { if (curr.getName().equals(name)) { return curr; } curr = curr.nextSibling; } return null; } /** * Remove the specified subtree. */ public void removeTree(String path) { NestedMapData removed = getChild(path); if (removed != null) { removed.severNode(); } } private NestedMapData followSymLinkToTheBitterEnd() { NestedMapData current; for (current = this; current.symLink != current; current = current.symLink); return current; } // ******************* Symbolic links ******************* /** * Set the source node to be a symbolic link to the destination. */ public void setSymlink(String sourcePath, String destinationPath) { setSymlink(sourcePath, createChild(destinationPath)); } /** * Set the source node to be a symbolic link to the destination. */ public void setSymlink(String sourcePath, Data destination) { createChild(sourcePath).setSymlink(destination); } /** * Set this node to be a symbolic link to another node. */ public void setSymlink(Data symLink) { if (symLink instanceof NestedMapData) { this.symLink = (NestedMapData) symLink; } else { String errorMessage = "Cannot set symlink of incompatible Data type: " + symLink.getClass().getName(); if (symLink instanceof ChainedData) { errorMessage += "\nOther type is ChainedData indicating there are " + "multiple valid Data nodes for the path: " + symLink.getFullPath(); } throw new IllegalArgumentException(errorMessage); } } /** * Retrieve the symbolic link this node points to. Will return reference to self if not a symlink. */ public Data getSymlink() { return symLink; } // ************************ Copy ************************* public void copy(String toPath, Data from) { if (toPath == null) { throw new NullPointerException("Invalid copy destination path"); } if (from == null) { // Is this a nop or should we throw an error? return; } Data to = createChild(toPath); to.copy(from); } public void copy(Data from) { if (from == null) { // Is this a nop or should we throw an error? return; } // Clear any existing symlink. this.symLink = this; // Clear any existing attributes and copy the ones from the source. if (this.attributeList != null) { this.attributeList.clear(); } for (Map.Entry attribute : from.getAttributes()) { setAttribute(attribute.getKey(), attribute.getValue()); } // If the source node was a symlink, just copy the link over and we're done. if (from.getSymlink() != from) { setSymlink(from.getSymlink()); return; } // Copy over the node's value. setValue(from.getValue()); // For each source child, create a new child with the same name and recurse. for (Data fromChild : from.getChildren()) { Data toChild = createChild(fromChild.getName()); toChild.copy(fromChild); } } /** * Write out the String representation of this HDF node. */ public void write(Appendable out, int indent) throws IOException { if (symLink != this) { indent(out, indent); writeNameAttrs(out); out.append(" : ").append(symLink.getFullPath()).append('\n'); return; } if (getValue() != null) { indent(out, indent); writeNameAttrs(out); if (getValue().contains("\n")) { writeMultiline(out); } else { out.append(" = ").append(getValue()).append('\n'); } } if (getChildCount() > 0) { int childIndent = indent; if (this != root) { indent(out, indent); writeNameAttrs(out); out.append(" {\n"); childIndent++; } for (Data child : getChildren()) { child.write(out, childIndent); } if (this != root) { indent(out, indent); out.append("}\n"); } } } /** * Here we optimize the structure for long-term use. We call intern() on all Strings to reduce the * copies of the same string that appear */ @Override public void optimize() { name = name == null ? null : name.intern(); value = value == null ? null : value.intern(); if (attributeList != null) { Map newList = new HashMap(attributeList.size()); for (Map.Entry entry : attributeList.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); key = key == null ? null : key.intern(); value = value == null ? null : value.intern(); newList.put(key, value); } attributeList = newList; } for (NestedMapData child = firstChild; child != null; child = child.nextSibling) { child.optimize(); } } private void writeMultiline(Appendable out) throws IOException { String marker = "EOM"; while (getValue().contains(marker)) { marker += System.nanoTime() % 10; } out.append(" << ").append(marker).append('\n').append(getValue()); if (!getValue().endsWith("\n")) { out.append('\n'); } out.append(marker).append('\n'); } private void indent(Appendable out, int indent) throws IOException { for (int i = 0; i < indent; i++) { out.append(" "); } } private void writeNameAttrs(Appendable out) throws IOException { // Output name out.append(getName()); if (attributeList != null && !attributeList.isEmpty()) { // Output parameters. out.append(" ["); boolean first = true; for (Map.Entry attr : attributeList.entrySet()) { if (first) { first = false; } else { out.append(", "); } out.append(attr.getKey()); if (attr.getValue().equals("1")) { continue; } out.append(" = \""); writeAttributeValue(out, attr.getValue()); out.append('"'); } out.append(']'); } } // Visible for testing static void writeAttributeValue(Appendable out, String value) throws IOException { for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); switch (c) { case '"': out.append("\\\""); break; case '\n': out.append("\\n"); break; case '\t': out.append("\\t"); break; case '\\': out.append("\\\\"); break; case '\r': out.append("\\r"); break; default: out.append(c); } } } /** * A single instance of this is created per NestedMapData object. Its single method returns an * iterator over the children of this node. *

* Note: This returns an iterator that starts with the first child at the time of iterator() being * called, not when this Iterable object was handed to the code. This might result in slightly * unexpected behavior if the children list is modified between when getChildren() is called and * iterator is called on the returned object but this should not be an issue in practice as * iterator is usually called immediately after getChildren(). * */ private class IterableChildren implements Iterable { public Iterator iterator() { return new ChildrenIterator(followSymLinkToTheBitterEnd().firstChild); } } /** * Iterator implementation for children. We do not check for concurrent modification like in other * Collection iterators. */ private static class ChildrenIterator implements Iterator { NestedMapData current; NestedMapData next; ChildrenIterator(NestedMapData first) { next = first; current = null; } public boolean hasNext() { return next != null; } public NestedMapData next() { if (next == null) { throw new NoSuchElementException(); } current = next; next = next.nextSibling; return current; } public void remove() { if (current == null) { throw new NoSuchElementException(); } current.severNode(); current = null; } } } src/com/google/clearsilver/jsilver/data/NewHdfParser.java0100644 0000000 0000000 00000055621 13662126234 022541 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; import java.util.ArrayList; import java.util.Iterator; import java.util.Stack; /** * Parser for HDF based on the following grammar by Brandon Long. * * COMMAND := (INCLUDE | COMMENT | HDF_SET | HDF_DESCEND | HDF_ASCEND ) INCLUDE := #include * "FILENAME" EOL COMMENT := # .* EOL HDF_DESCEND := HDF_NAME_ATTRS { EOL HDF_ASCEND := } EOL * HDF_SET := (HDF_ASSIGN | HDF_MULTILINE_ASSIGN | HDF_COPY | HDF_LINK) HDF_ASSIGN := HDF_NAME_ATTRS * = .* EOL HDF_MULTILINE_ASSIGN := HDF_NAME_ATTRS << EOM_MARKER EOL (.* EOL)* EOM_MARKER EOL * HDF_COPY := HDF_NAME_ATTRS := HDF_NAME EOL HDF_LINK := HDF_NAME_ATTRS : HDF_NAME EOL * HDF_NAME_ATTRS := (HDF_NAME | HDF_NAME [HDF_ATTRS]) HDF_ATTRS := (HDF_ATTR | HDF_ATTR, HDF_ATTRS) * HDF_ATTR := (HDF_ATTR_KEY | HDF_ATTR_KEY = [^\s,\]]+ | HDF_ATTR_KEY = DQUOTED_STRING) * HDF_ATTR_KEY := [0-9a-zA-Z]+ DQUOTED_STRING := "([^\\"]|\\[ntr]|\\.)*" HDF_NAME := (HDF_SUB_NAME * | HDF_SUB_NAME\.HDF_NAME) HDF_SUB_NAME := [0-9a-zA-Z_]+ EOM_MARKER := \S.*\S EOL := \n */ public class NewHdfParser implements Parser { private final StringInternStrategy internStrategy; /** * Special exception used to detect when we unexpectedly run out of characters on the line. */ private static class OutOfCharsException extends Exception {} /** * Object used to hold the name and attributes of an HDF node before we are ready to commit it to * the Data object. */ private static class HdfNameAttrs { String name; ArrayList attrs = null; int endOfSequence; void reset(String newname) { // TODO: think about moving interning here instead of parser code this.name = newname; if (attrs != null) { attrs.clear(); } endOfSequence = 0; } void addAttribute(String key, String value) { if (attrs == null) { attrs = new ArrayList(10); } attrs.ensureCapacity(attrs.size() + 2); // TODO: think about moving interning here instead of parser code attrs.add(key); attrs.add(value); } Data toData(Data data) { Data child = data.createChild(name); if (attrs != null) { Iterator it = attrs.iterator(); while (it.hasNext()) { String key = it.next(); String value = it.next(); child.setAttribute(key, value); } } return child; } } static final String UNNAMED_INPUT = "[UNNAMED_INPUT]"; /** * State information that we pass through the parse methods. Allows parser to be reentrant as all * the state is passed through method calls. */ static class ParseState { final Stack context = new Stack(); final Data output; final LineNumberReader lineReader; final ErrorHandler errorHandler; final ResourceLoader resourceLoader; final NewHdfParser hdfParser; final boolean ignoreAttributes; final HdfNameAttrs hdfNameAttrs; final UniqueStack includeStack; final String parsedFileName; String line; Data currentNode; private ParseState(Data output, LineNumberReader lineReader, ErrorHandler errorHandler, ResourceLoader resourceLoader, NewHdfParser hdfParser, String parsedFileName, boolean ignoreAttributes, HdfNameAttrs hdfNameAttrs, UniqueStack includeStack) { this.lineReader = lineReader; this.errorHandler = errorHandler; this.output = output; currentNode = output; this.resourceLoader = resourceLoader; this.hdfParser = hdfParser; this.parsedFileName = parsedFileName; this.ignoreAttributes = ignoreAttributes; this.hdfNameAttrs = hdfNameAttrs; this.includeStack = includeStack; } public static ParseState createNewParseState(Data output, Reader reader, ErrorHandler errorHandler, ResourceLoader resourceLoader, NewHdfParser hdfParser, String parsedFileName, boolean ignoreAttributes) { if (parsedFileName == null) { parsedFileName = UNNAMED_INPUT; } UniqueStack includeStack = new UniqueStack(); includeStack.push(parsedFileName); return new ParseState(output, new LineNumberReader(reader), errorHandler, resourceLoader, hdfParser, parsedFileName, ignoreAttributes, new HdfNameAttrs(), includeStack); } public static ParseState createParseStateForIncludedFile(ParseState originalState, String includeFileName, Reader includeFileReader) { return new ParseState(originalState.output, new LineNumberReader(includeFileReader), originalState.errorHandler, originalState.resourceLoader, originalState.hdfParser, originalState.parsedFileName, originalState.ignoreAttributes, new HdfNameAttrs(), originalState.includeStack); } } /** * Constructor for {@link NewHdfParser}. * * @param internPool - {@link StringInternStrategy} instance used to optimize the HDF parsing. */ public NewHdfParser(StringInternStrategy internPool) { this.internStrategy = internPool; } private static class NewHdfParserFactory implements ParserFactory { private final StringInternStrategy stringInternStrategy; public NewHdfParserFactory(StringInternStrategy stringInternStrategy) { this.stringInternStrategy = stringInternStrategy; } @Override public Parser newInstance() { return new NewHdfParser(stringInternStrategy); } } /** * Creates a {@link ParserFactory} instance. * *

* Provided {@code stringInternStrategy} instance will be used by shared all {@link Parser} * objects created by the factory and used to optimize the HDF parsing process by reusing the * String for keys and values. * * @param stringInternStrategy - {@link StringInternStrategy} instance used to optimize the HDF * parsing. * @return an instance of {@link ParserFactory} implementation. */ public static ParserFactory newFactory(StringInternStrategy stringInternStrategy) { return new NewHdfParserFactory(stringInternStrategy); } public void parse(Reader reader, Data output, Parser.ErrorHandler errorHandler, ResourceLoader resourceLoader, String dataFileName, boolean ignoreAttributes) throws IOException { parse(ParseState.createNewParseState(output, reader, errorHandler, resourceLoader, this, dataFileName, ignoreAttributes)); } private void parse(ParseState state) throws IOException { while ((state.line = state.lineReader.readLine()) != null) { String seq = stripWhitespace(state.line); try { parseCommand(seq, state); } catch (OutOfCharsException e) { reportError(state, "End of line was prematurely reached. Parse error."); } } } private static final String INCLUDE_WS = "#include "; private void parseCommand(String seq, ParseState state) throws IOException, OutOfCharsException { if (seq.length() == 0) { // Empty line. return; } if (charAt(seq, 0) == '#') { // If there isn't a match on include then this is a comment and we do nothing. if (matches(seq, 0, INCLUDE_WS)) { // This is an include command int start = skipLeadingWhitespace(seq, INCLUDE_WS.length()); parseInclude(seq, start, state); } return; } else if (charAt(seq, 0) == '}') { if (skipLeadingWhitespace(seq, 1) != seq.length()) { reportError(state, "Extra chars after '}'"); return; } handleAscend(state); } else { parseHdfElement(seq, state); } } private void parseInclude(String seq, int start, ParseState state) throws IOException, OutOfCharsException { int end = seq.length(); if (charAt(seq, start) == '"') { if (charAt(seq, end - 1) == '"') { start++; end--; } else { reportError(state, "Missing '\"' at end of include"); return; } } handleInclude(seq.substring(start, end), state); } private static final int NO_MATCH = -1; private void parseHdfElement(String seq, ParseState state) throws IOException, OutOfCharsException { // Re-use a single element to avoid repeated allocations/trashing (serious // performance impact, 5% of real service performance) HdfNameAttrs element = state.hdfNameAttrs; if (!parseHdfNameAttrs(element, seq, 0, state)) { return; } int index = skipLeadingWhitespace(seq, element.endOfSequence); switch (charAt(seq, index)) { case '{': // Descend if (index + 1 != seq.length()) { reportError(state, "No characters expected after '{'"); return; } handleDescend(state, element); return; case '=': // Assignment index = skipLeadingWhitespace(seq, index + 1); String value = internStrategy.intern(seq.substring(index, seq.length())); handleAssign(state, element, value); return; case ':': if (charAt(seq, index + 1) == '=') { // Copy index = skipLeadingWhitespace(seq, index + 2); String src = parseHdfName(seq, index); if (src == null) { reportError(state, "Invalid HDF name"); return; } if (index + src.length() != seq.length()) { reportError(state, "No characters expected after '{'"); return; } handleCopy(state, element, src); } else { // Link index = skipLeadingWhitespace(seq, index + 1); String src = parseHdfName(seq, index); if (src == null) { reportError(state, "Invalid HDF name"); return; } if (index + src.length() != seq.length()) { reportError(state, "No characters expected after '{'"); return; } handleLink(state, element, src); } return; case '<': if (charAt(seq, index + 1) != '<') { reportError(state, "Expected '<<'"); } index = skipLeadingWhitespace(seq, index + 2); String eomMarker = seq.substring(index, seq.length()); // TODO: think about moving interning to handleAssign() String multilineValue = internStrategy.intern(parseMultilineValue(state, eomMarker)); if (multilineValue == null) { return; } handleAssign(state, element, multilineValue); return; default: reportError(state, "No valid operator"); return; } } /** * This method parses out an HDF element name and any optional attributes into a caller-supplied * HdfNameAttrs object. It returns a {@code boolean} with whether it succeeded to parse. */ private boolean parseHdfNameAttrs(HdfNameAttrs destination, String seq, int index, ParseState state) throws OutOfCharsException { String hdfName = parseHdfName(seq, index); if (hdfName == null) { reportError(state, "Invalid HDF name"); return false; } destination.reset(hdfName); index = skipLeadingWhitespace(seq, index + hdfName.length()); int end = parseAttributes(seq, index, state, destination); if (end == NO_MATCH) { // Error already reported below. return false; } else { destination.endOfSequence = end; return true; } } /** * Parses a valid hdf path name. */ private String parseHdfName(String seq, int index) throws OutOfCharsException { int end = index; while (end < seq.length() && isHdfNameChar(charAt(seq, end))) { end++; } if (end == index) { return null; } return internStrategy.intern(seq.substring(index, end)); } /** * Looks for optional attributes and adds them to the HdfNameAttrs object passed into the method. */ private int parseAttributes(String seq, int index, ParseState state, HdfNameAttrs element) throws OutOfCharsException { if (charAt(seq, index) != '[') { // No attributes to parse return index; } index = skipLeadingWhitespace(seq, index + 1); // If we don't care about attributes, just skip over them. if (state.ignoreAttributes) { while (charAt(seq, index) != ']') { index++; } return index + 1; } boolean first = true; do { if (first) { first = false; } else if (charAt(seq, index) == ',') { index = skipLeadingWhitespace(seq, index + 1); } else { reportError(state, "Error parsing attribute list"); } index = parseAttribute(seq, index, state, element); if (index == NO_MATCH) { // reportError called by parseAttribute already. return NO_MATCH; } index = skipLeadingWhitespace(seq, index); } while (charAt(seq, index) != ']'); return index + 1; } private static final String DEFAULT_ATTR_VALUE = "1"; /** * Parse out a single HDF attribute. If there is no explicit value, use default value of "1" like * in C clearsilver. Returns NO_MATCH if it fails to parse an attribute. */ private int parseAttribute(String seq, int index, ParseState state, HdfNameAttrs element) throws OutOfCharsException { int end = parseAttributeKey(seq, index); if (index == end) { reportError(state, "No valid attribute key"); return NO_MATCH; } String attrKey = internStrategy.intern(seq.substring(index, end)); index = skipLeadingWhitespace(seq, end); if (charAt(seq, index) != '=') { // No value for this attribute key. Use default value of "1" element.addAttribute(attrKey, DEFAULT_ATTR_VALUE); return index; } // We need to parse out the attribute value. index = skipLeadingWhitespace(seq, index + 1); if (charAt(seq, index) == '"') { index++; StringBuilder sb = new StringBuilder(); end = parseQuotedAttributeValue(seq, index, sb); if (end == NO_MATCH) { reportError(state, "Unable to parse quoted attribute value"); return NO_MATCH; } String attrValue = internStrategy.intern(sb.toString()); element.addAttribute(attrKey, attrValue); end++; } else { // Simple attribute that has no whitespace. String attrValue = parseAttributeValue(seq, index, state); if (attrValue == null || attrValue.length() == 0) { reportError(state, "No attribute for key " + attrKey); return NO_MATCH; } attrValue = internStrategy.intern(attrValue); element.addAttribute(attrKey, attrValue); end = index + attrValue.length(); } return end; } /** * Returns the range in the sequence starting at start that corresponds to a valid attribute key. */ private int parseAttributeKey(String seq, int index) throws OutOfCharsException { while (isAlphaNumericChar(charAt(seq, index))) { index++; } return index; } /** * Parses a quoted attribute value. Unescapes octal characters and \n, \r, \t, \", etc. */ private int parseQuotedAttributeValue(String seq, int index, StringBuilder sb) throws OutOfCharsException { char c; while ((c = charAt(seq, index)) != '"') { if (c == '\\') { // Escaped character. Look for 1 to 3 digits in a row as octal or n,t,r. index++; char next = charAt(seq, index); if (isNumericChar(next)) { // Parse the next 1 to 3 characters if they are digits. Treat it as an octal code. int val = next - '0'; if (isNumericChar(charAt(seq, index + 1))) { index++; val = val * 8 + (charAt(seq, index) - '0'); if (isNumericChar(charAt(seq, index + 1))) { index++; val = val * 8 + (charAt(seq, index) - '0'); } } c = (char) val; } else if (next == 'n') { c = '\n'; } else if (next == 't') { c = '\t'; } else if (next == 'r') { c = '\r'; } else { // Regular escaped char like " or / c = next; } } sb.append(c); index++; } return index; } /** * Parses a simple attribute value that cannot have any whitespace or specific punctuation * reserved by the HDF grammar. */ private String parseAttributeValue(String seq, int index, ParseState state) throws OutOfCharsException { int end = index; char c = charAt(seq, end); while (c != ',' && c != ']' && c != '"' && !Character.isWhitespace(c)) { end++; c = charAt(seq, end); } return seq.substring(index, end); } private String parseMultilineValue(ParseState state, String eomMarker) throws IOException { StringBuilder sb = new StringBuilder(256); String line; while ((line = state.lineReader.readLine()) != null) { if (line.startsWith(eomMarker) && skipLeadingWhitespace(line, eomMarker.length()) == line.length()) { return sb.toString(); } else { sb.append(line).append('\n'); } } reportError(state, "EOM " + eomMarker + " never found"); return null; } // ////////////////////////////////////////////////////////////////////////// // // Handlers private void handleDescend(ParseState state, HdfNameAttrs element) { Data child = handleNodeCreation(state.currentNode, element); state.context.push(state.currentNode); state.currentNode = child; } private Data handleNodeCreation(Data node, HdfNameAttrs element) { return element.toData(node); } private void handleAssign(ParseState state, HdfNameAttrs element, String value) { // TODO: think about moving interning here Data child = handleNodeCreation(state.currentNode, element); child.setValue(value); } private void handleCopy(ParseState state, HdfNameAttrs element, String srcName) { Data child = handleNodeCreation(state.currentNode, element); Data src = state.output.getChild(srcName); if (src != null) { child.setValue(src.getValue()); } else { child.setValue(""); } } private void handleLink(ParseState state, HdfNameAttrs element, String srcName) { Data child = handleNodeCreation(state.currentNode, element); child.setSymlink(state.output.createChild(srcName)); } private void handleAscend(ParseState state) { if (state.context.isEmpty()) { reportError(state, "Too many '}'"); return; } state.currentNode = state.context.pop(); } private void handleInclude(String seq, ParseState state) throws IOException { String includeFileName = internStrategy.intern(seq); // Load the file Reader reader = state.resourceLoader.open(includeFileName); if (reader == null) { reportError(state, "Unable to find file " + includeFileName); return; } // Check whether we are in include loop if (!state.includeStack.push(includeFileName)) { reportError(state, createIncludeStackTraceMessage(state.includeStack, includeFileName)); return; } // Parse the file state.hdfParser.parse(ParseState .createParseStateForIncludedFile(state, includeFileName, reader)); if (!includeFileName.equals(state.includeStack.pop())) { // Include stack trace is corrupted throw new IllegalStateException("Unable to find on include stack: " + includeFileName); } } private String createIncludeStackTraceMessage(UniqueStack includeStack, String includeFileName) { StringBuilder message = new StringBuilder(); message.append("File included twice: "); message.append(includeFileName); message.append(" Include stack: "); for (String fileName : includeStack) { message.append(fileName); message.append(" -> "); } message.append(includeFileName); return message.toString(); } // ///////////////////////////////////////////////////////////////////////// // // Character values private static boolean isNumericChar(char c) { if ('0' <= c && c <= '9') { return true; } else { return false; } } private static boolean isAlphaNumericChar(char c) { if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) { return true; } else { return false; } } private static boolean isHdfNameChar(char c) { if (isAlphaNumericChar(c) || c == '_' || c == '.') { return true; } else { return false; } } private static String stripWhitespace(String seq) { int start = skipLeadingWhitespace(seq, 0); int end = seq.length() - 1; while (end > start && Character.isWhitespace(seq.charAt(end))) { --end; } if (start == 0 && end == seq.length() - 1) { return seq; } else { return seq.substring(start, end + 1); } } private static int skipLeadingWhitespace(String seq, int index) { while (index < seq.length() && Character.isWhitespace(seq.charAt(index))) { index++; } return index; } /** * Determines if a character sequence appears in the given sequence starting at a specified index. * * @param seq the sequence that we want to see if it contains the string match. * @param start the index into seq where we want to check for match * @param match the String we want to look for in the sequence. * @return {@code true} if the string match appears in seq starting at the index start, {@code * false} otherwise. */ private static boolean matches(String seq, int start, String match) { if (seq.length() - start < match.length()) { return false; } for (int i = 0; i < match.length(); i++) { if (match.charAt(i) != seq.charAt(start + i)) { return false; } } return true; } /** * Reads the character at the specified index in the given String. Throws an exception to be * caught above if the index is out of range. */ private static char charAt(String seq, int index) throws OutOfCharsException { if (0 <= index && index < seq.length()) { return seq.charAt(index); } else { throw new OutOfCharsException(); } } private static void reportError(ParseState state, String errorMessage) { if (state.errorHandler != null) { state.errorHandler.error(state.lineReader.getLineNumber(), state.line, state.parsedFileName, errorMessage); } else { throw new RuntimeException("Parse Error on line " + state.lineReader.getLineNumber() + ": " + errorMessage + " : " + state.line); } } } src/com/google/clearsilver/jsilver/data/NoOpStringInternStrategy.java0100644 0000000 0000000 00000001536 13662126234 025152 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; /** * Pass-through implementation of {@link StringInternStrategy}. */ public class NoOpStringInternStrategy implements StringInternStrategy { @Override public String intern(String value) { return value; } } src/com/google/clearsilver/jsilver/data/Parser.java0100644 0000000 0000000 00000004416 13662126234 021441 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import java.io.Reader; import java.io.IOException; /** * Parses data in HierachicalDataFormat (HDF), generating callbacks for data encountered in the * stream. */ public interface Parser { /** Called whenever an error occurs. */ public interface ErrorHandler { /** * Report an error to the ErrorHandler. * * @param line number of the line where error occurred. The value of -1 represents line number * unknown * @param lineContent text of the line with error * @param fileName name of the file in which the error occurred * @param errorMessage description of an error */ void error(int line, String lineContent, String fileName, String errorMessage); } /** * Reads in a stream of characters and parses data from it, putting it into the given Data object. * * @param reader Reader used to read in the formatted data. * @param output Data object that the read data structure will be dumped into. * @param errorHandler Error callback to be called on any error. * @param resourceLoader ResourceLoader to use to read in included files. * @param dataFileName Name of a file that is read with reader. It is needed for the purpose of * handling include loops and error messages. * @param ignoreAttributes whether to store parsed HDF attributes in the Data object or not. * @throws IOException when errors occur reading input. */ void parse(Reader reader, Data output, ErrorHandler errorHandler, ResourceLoader resourceLoader, String dataFileName, boolean ignoreAttributes) throws IOException; } src/com/google/clearsilver/jsilver/data/ParserFactory.java0100644 0000000 0000000 00000001470 13662126234 022766 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; /** * Interface for allowing multiple implementations of HdfParser. */ public interface ParserFactory { /** * Returns a new HdfParser object. */ Parser newInstance(); } src/com/google/clearsilver/jsilver/data/StringInternStrategy.java0100644 0000000 0000000 00000002644 13662126234 024357 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; /** * Encapsulates the {@code WeakInterningPool} functionality with added optimizations. To be * used to optimize the memory usage and garbage collection during text processing. */ public interface StringInternStrategy { /** * Interns a String object in a pool and returns a String equal to the one provided. * *

* If there exists a String in the pool equal to the provided value then it will be returned. * Otherwise provided String may be interned. * *

* There is no guarantees on when the pool will return the same object as provided. It is possible * that value == intern(value) will never be true. * * @param value String to be interned * @return a String that is equal to the one provided. */ String intern(String value); } src/com/google/clearsilver/jsilver/data/TypeConverter.java0100644 0000000 0000000 00000012032 13662126234 023007 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; /** * Static methods for converting stuff in a ClearSilver compatible way. */ public class TypeConverter { private TypeConverter() {} private static final String ZERO = "0"; private static final String ONE = "1"; /** * Determines if the given data node exists in a ClearSilver compatible way. */ public static boolean exists(Data data) { return data != null && data.getValue() != null; } /** * Helper method to safely convert an arbitrary data instance (including null) into a valid * (non-null) string representation. */ public static String asString(Data data) { // Non-existent variables become the empty string // (the data instance will return null to us) String value = data != null ? data.getValue() : null; return value != null ? value : ""; } /** * Parses a non-null string in a ClearSilver compatible way. * * The is the underlying parsing function which can fail for badly formatted strings. It is really * important that anyone doing parsing of strings calls this function (rather than doing it * themselves). * * This is an area where JSilver and ClearSilver have some notable differences. ClearSilver relies * on the template compiler to parse strings in the template and a different parser at runtime for * HDF values. JSilver uses the same code for both cases. * * In ClearSilver HDF: Numbers are parsed sequentially and partial results are returned when an * invalid character is reached. This means that {@code "123abc"} parses to {@code 123}. * * Additionally, ClearSilver doesn't do hex in HDF values, so {@code "a.b=0x123"} will just * resolve to {@code 0}. * * In ClearSilver templates: Hex is supported, including negative values. * * In JSilver: A string must be a complete, valid numeric value for parsing. This means {@code * "123abc"} is invalid and will default to {@code 0}. * * In JSilver: Positive hex values are supported for both HDF and templates but negative values * aren't. This means a template containing something like "" will parse * correctly but fail to render. * * @throws NumberFormatException is the string is badly formatted */ public static int parseNumber(String value) throws NumberFormatException { // NOTE: This is likely to be one of the areas we will want to optimize // for speed eventually. if (value.startsWith("0x") || value.startsWith("0X")) { return Integer.parseInt(value.substring(2), 16); } else { return Integer.parseInt(value); } } /** * Parses and returns the given string as an integer in a ClearSilver compatible way. */ public static int asNumber(String value) { if (value == null || value.isEmpty()) { return 0; } // fast detection for common constants to avoid parsing common values // TODO: Maybe push this down into parseNumber ?? if (value.equals(ONE)) { return 1; } if (value.equals(ZERO)) { return 0; } try { return parseNumber(value); } catch (NumberFormatException e) { return 0; } } /** * Helper method to safely convert an arbitrary data instance (including null) into a valid * integer representation. */ public static int asNumber(Data data) { // Non-existent variables become zero return data != null ? data.getIntValue() : 0; } /** * Parses and returns the given string as a boolean in a ClearSilver compatible way. */ public static boolean asBoolean(String value) { if (value == null || value.isEmpty()) { return false; } // fast detection for common constants to avoid parsing common values if (value.equals(ONE)) { return true; } if (value.equals(ZERO)) { return false; } // fast detection of any string not starting with '0' if (value.charAt(0) != '0') { return true; } try { return parseNumber(value) != 0; } catch (NumberFormatException e) { // Unlike number parsing, we return a positive value when the // string is badly formatted (it's what clearsilver does). return true; } } /** * Helper method to safely convert an arbitrary data instance (including null) into a valid * boolean representation. */ public static boolean asBoolean(Data data) { // Non-existent variables become false return data != null ? data.getBooleanValue() : false; } } src/com/google/clearsilver/jsilver/data/UniqueStack.java0100644 0000000 0000000 00000010336 13662126234 022437 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.NoSuchElementException; /** * The {@code ResourceStack} represents a LIFO stack of unique objects (resources). * *

* An attempt to insert on a stack an object that is already there will fail and result with a * method {@link #push(Object)} returning false. * *

* All provided operations ({@link #pop()} and {@link #push(Object)}) are done in average constant * time. * *

* This class is iterable */ public class UniqueStack implements Iterable { // Field used for optimization: when only one object was // added we postpone the initialization and use this field. private T firstObject = null; // Contains a stack of all stored objects. private LinkedList objectStack = null; // A HashSet allowing quick look-ups on the stack. private HashSet objectsSet = null; /** * A wrapper for a {@code Iterator} object that provides immutability. * * @param */ private static class ImmutableIterator implements Iterator { private static final String MODIFICATION_ERROR_MESSAGE = "ResourceStack cannot be modyfied by Iterator.remove()"; private final Iterator iterator; private ImmutableIterator(Iterator iterator) { this.iterator = iterator; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { return iterator.next(); } @Override public void remove() { throw new UnsupportedOperationException(MODIFICATION_ERROR_MESSAGE); } } /** * Add an object to a stack. Object will be added only if it is not already on the stack. * * @param object to be added. If it is {@code null} a {@code NullPointerException} will be thrown. * @return true if the object was added successfully */ public boolean push(T object) { if (object == null) { throw new NullPointerException(); } if (objectStack == null) { if (firstObject == null) { firstObject = object; return true; } else { if (firstObject.equals(object)) { return false; } initStackAndSet(); } } else { if (objectsSet.contains(object)) { return false; } } objectStack.offerLast(object); objectsSet.add(object); return true; } private void initStackAndSet() { objectStack = new LinkedList(); objectsSet = new HashSet(); objectStack.offerLast(firstObject); objectsSet.add(firstObject); // there is no need for using firstObject pointer anymore firstObject = null; } /** * Removes last added object from the stack. * * @return last added object * @throws NoSuchElementException - if the stack is empty */ public T pop() { T returnedValue = null; if (isEmpty()) { throw new NoSuchElementException(); } if (objectStack == null) { returnedValue = firstObject; firstObject = null; } else { returnedValue = objectStack.pollLast(); objectsSet.remove(returnedValue); } return returnedValue; } /** * Returns {@code true} if this stack contains no elements. * * @return {@code true} if this stack contains no elements. */ public boolean isEmpty() { if (firstObject != null) { return false; } return (objectStack == null || objectStack.isEmpty()); } @Override public Iterator iterator() { if (objectStack == null) { initStackAndSet(); } return new ImmutableIterator(objectStack.iterator()); } } src/com/google/clearsilver/jsilver/data/UnmodifiableData.java0100644 0000000 0000000 00000007001 13662126234 023366 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.util.Iterator; /** * Data wrapper that prevents modifying the delegated Data node or its tree. */ public class UnmodifiableData extends DelegatedData { private static final String MODIFICATION_ERROR_MSG = "Cannot modify this Data object."; public UnmodifiableData(Data delegate) { super(delegate); } @Override protected DelegatedData newInstance(Data newDelegate) { return newDelegate == null ? null : new UnmodifiableData(newDelegate); } @Override public void copy(Data from) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void copy(String toPath, Data from) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } /** * {@link #createChild} calls {@link #getChild} and throws {@link UnsupportedOperationException} * if no object was found. */ @Override public Data createChild(String path) { // Check if child already exists Data child = getChild(path); if (child == null) { // If the child described by path does not exist we throw // an exception as we cannot create a new one. throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } return child; } protected class UnmodifiableIterator extends DelegatedIterator { UnmodifiableIterator(Iterator iterator) { super(iterator); } public void remove() { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } } /** * Override in order to not allow modifying children with remove(). */ @Override protected Iterator newChildIterator() { return new UnmodifiableIterator(getDelegate().getChildren().iterator()); } // Below we disable modification operations. @Override public void setSymlink(String sourcePath, Data destination) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setSymlink(String sourcePath, String destinationPath) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setSymlink(Data symLink) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setAttribute(String key, String value) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void removeTree(String path) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setValue(String path, String value) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setValue(String value) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setEscapeMode(EscapeMode mode) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } } src/com/google/clearsilver/jsilver/examples/0040755 0000000 0000000 00000000000 13662126234 020245 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/examples/basic/0040755 0000000 0000000 00000000000 13662126234 021326 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/examples/basic/HelloWorld.java0100644 0000000 0000000 00000002542 13662126234 024244 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.examples.basic; import com.google.clearsilver.jsilver.JSilver; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader; import java.io.IOException; /** * Hello world of templates. */ public class HelloWorld { public static void main(String[] args) throws IOException { // Load resources (e.g. templates) from classpath, along side this class. JSilver jSilver = new JSilver(new ClassResourceLoader(HelloWorld.class)); // Set up some data. Data data = jSilver.createData(); data.setValue("name.first", "Mr"); data.setValue("name.last", "Man"); // Render template to System.out. jSilver.render("hello-world.cs", data, System.out); } } src/com/google/clearsilver/jsilver/examples/basic/Iterate.java0100644 0000000 0000000 00000003171 13662126234 023565 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.examples.basic; import com.google.clearsilver.jsilver.JSilver; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader; import java.io.IOException; /** * A template that iterates over some items. */ public class Iterate { public static void main(String[] args) throws IOException { // Load resources (e.g. templates) from classpath, along side this class. JSilver jSilver = new JSilver(new ClassResourceLoader(Iterate.class)); // Set up some data. Data data = jSilver.createData(); data.setValue("query", "Fruit"); data.setValue("results.0.title", "Banana"); data.setValue("results.0.url", "http://banana.com/"); data.setValue("results.1.title", "Apple"); data.setValue("results.1.url", "http://apple.com/"); data.setValue("results.2.title", "Lemon"); data.setValue("results.2.url", "http://lemon.com/"); // Render template to System.out. jSilver.render("iterate.cs", data, System.out); } } src/com/google/clearsilver/jsilver/examples/basic/JSilverTest.java0100644 0000000 0000000 00000003037 13662126234 024407 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.examples.basic; import com.google.clearsilver.jsilver.JSilver; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader; import java.io.IOException; /** * Command-line template renderer. * * Usage: JSilverTest file.cs [file.hdf file2.hdf ...] */ public class JSilverTest { public static void main(String[] args) throws IOException { if (args.length < 1) { System.out.println("Usage: JSilverTest file.cs [file.hdf file2.hdf ...]"); System.exit(1); } // Load resources from filesystem, relative to the current directory. JSilver jSilver = new JSilver(new FileSystemResourceLoader(".")); // Load data. Data data = jSilver.createData(); for (int i = 1; i < args.length; i++) { jSilver.loadData(args[i], data); } // Render template to System.out. jSilver.render(args[0], data, System.out); } } src/com/google/clearsilver/jsilver/examples/basic/hello-world.cs0100644 0000000 0000000 00000000107 13662126234 024100 0ustar000000000 0000000 Hello . How are you today? src/com/google/clearsilver/jsilver/examples/basic/iterate.cs0100644 0000000 0000000 00000000215 13662126234 023305 0ustar000000000 0000000 Query : Results: ---> src/com/google/clearsilver/jsilver/exceptions/0040755 0000000 0000000 00000000000 13662126234 020610 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/exceptions/ExceptionUtil.java0100644 0000000 0000000 00000002272 13662126234 024247 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.exceptions; import java.io.FileNotFoundException; /** * Class to hold utilities related to exceptions used by JSilver code. */ public final class ExceptionUtil { private ExceptionUtil() {} /** * Determines if the given exception was caused by an exception equivalent to FileNotFound. */ public static boolean isFileNotFoundException(Throwable th) { while (th != null) { if (th instanceof JSilverTemplateNotFoundException || th instanceof FileNotFoundException) { return true; } th = th.getCause(); } return false; } } src/com/google/clearsilver/jsilver/exceptions/JSilverAutoEscapingException.java0100644 0000000 0000000 00000003517 13662126234 027216 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.exceptions; /** * Thrown when there is a problem applying auto escaping. */ public class JSilverAutoEscapingException extends JSilverException { public static final int UNKNOWN_POSITION = -1; public JSilverAutoEscapingException(String message, String templateName, int line, int column) { super(createMessage(message, templateName, line, column)); } public JSilverAutoEscapingException(String message, String templateName) { this(message, templateName, UNKNOWN_POSITION, UNKNOWN_POSITION); } /** * Keeping the same format as JSilverBadSyntaxException. */ private static String createMessage(String message, String resourceName, int line, int column) { StringBuilder result = new StringBuilder(message); if (resourceName != null) { result.append(" resource=").append(resourceName); } if (line != UNKNOWN_POSITION) { result.append(" line=").append(line); } if (column != UNKNOWN_POSITION) { result.append(" column=").append(column); } return result.toString(); } public JSilverAutoEscapingException(String message) { super(message); } public JSilverAutoEscapingException(String message, Throwable cause) { super(message, cause); } } src/com/google/clearsilver/jsilver/exceptions/JSilverBadSyntaxException.java0100644 0000000 0000000 00000005650 13662126234 026531 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.exceptions; /** * Thrown when resource (e.g. template or HDF) contains bad syntax. */ public class JSilverBadSyntaxException extends JSilverException { private final String resourceName; private final int line; private final int column; /** * Signifies line or column is not known. */ public static final int UNKNOWN_POSITION = -1; /** * Constructor of JSilverBadSyntaxException. * * @param message text of an error message * @param lineContent content of a line where error occurred (can be null) * @param resourceName name of a file where error occurred (can be null) * @param line number of a line in {@code resourceName} where error occurred (ignored if set to * {@link #UNKNOWN_POSITION}) * @param column number of a column in {@code resourceName} where error occurred (ignored if set * to {@link #UNKNOWN_POSITION}) * @param cause an original exception of an error. Null value is permitted and indicates that the * cause is nonexistent or unknown. */ public JSilverBadSyntaxException(String message, String lineContent, String resourceName, int line, int column, Throwable cause) { super(makeMessage(message, lineContent, resourceName, line, column), cause); this.resourceName = resourceName; this.line = line; this.column = column; } private static String makeMessage(String message, String lineContent, String resourceName, int line, int column) { StringBuilder result = new StringBuilder(message); if (resourceName != null) { result.append(" resource=").append(resourceName); } if (lineContent != null) { result.append(" content=").append(lineContent); } if (line != UNKNOWN_POSITION) { result.append(" line=").append(line); } if (column != UNKNOWN_POSITION) { result.append(" column=").append(column); } return result.toString(); } /** * Name of resource that had syntax error (typically a file name). */ public String getResourceName() { return resourceName; } /** * Line number this syntax error occured, or {@link #UNKNOWN_POSITION}. */ public int getLine() { return line; } /** * Column number this syntax error occured, or {@link #UNKNOWN_POSITION}. */ public int getColumn() { return column; } } src/com/google/clearsilver/jsilver/exceptions/JSilverException.java0100644 0000000 0000000 00000001647 13662126234 024715 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.exceptions; /** * Base class for all JSilver exceptions. */ public abstract class JSilverException extends RuntimeException { protected JSilverException(String message) { super(message); } protected JSilverException(String message, Throwable cause) { super(message, cause); } } src/com/google/clearsilver/jsilver/exceptions/JSilverIOException.java0100644 0000000 0000000 00000001677 13662126234 025150 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.exceptions; import java.io.IOException; /** * JSilver failed to access underlying IO stream. Wraps an IOException to make it a * RuntimeException. */ public class JSilverIOException extends JSilverException { public JSilverIOException(IOException cause) { super(cause.getMessage()); initCause(cause); } } src/com/google/clearsilver/jsilver/exceptions/JSilverInterpreterException.java0100644 0000000 0000000 00000001547 13662126234 027140 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.exceptions; /** * Signifies JSilver failed to interpret a template at runtime. */ public class JSilverInterpreterException extends JSilverException { public JSilverInterpreterException(String message) { super(message); } } src/com/google/clearsilver/jsilver/exceptions/JSilverTemplateNotFoundException.java0100644 0000000 0000000 00000001603 13662126234 030056 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.exceptions; /** * Thrown when JSilver is asked to load a template that does not exist. */ public class JSilverTemplateNotFoundException extends JSilverException { public JSilverTemplateNotFoundException(String templateName) { super(templateName); } } src/com/google/clearsilver/jsilver/functions/0040755 0000000 0000000 00000000000 13662126234 020437 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/EscapingFunction.java0100644 0000000 0000000 00000001421 13662126234 024534 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions; public abstract class EscapingFunction implements Function { @Override public boolean isEscapingFunction() { return true; } } src/com/google/clearsilver/jsilver/functions/Function.java0100644 0000000 0000000 00000001714 13662126234 023067 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions; import com.google.clearsilver.jsilver.values.Value; /** * Plugin for JSilver functions made available to templates. e.g <cs var:my_function(x, y) > */ public interface Function { /** * Execute a function. Should always return a result. */ Value execute(Value... args); boolean isEscapingFunction(); } src/com/google/clearsilver/jsilver/functions/FunctionExecutor.java0100644 0000000 0000000 00000002552 13662126234 024607 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions; import com.google.clearsilver.jsilver.values.Value; import java.io.IOException; /** * Execute functions in templates. */ public interface FunctionExecutor { /** * Lookup a function by name, execute it and return the results. */ Value executeFunction(String functionName, Value... args); /** * Escapes some text. * * @param name Strategy for escaping text. If null or "none", text will be left as is. * @param input Text to be escaped. * @param output Where to write the result to. */ void escape(String name, String input, Appendable output) throws IOException; /** * Look up a function by name, and report whether it is an escaping function. */ boolean isEscapingFunction(String name); } src/com/google/clearsilver/jsilver/functions/FunctionRegistry.java0100644 0000000 0000000 00000011120 13662126234 024610 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException; import com.google.clearsilver.jsilver.values.Value; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * Simple implementation of FunctionFinder that you can register your own functions with. * * @see FunctionExecutor */ public class FunctionRegistry implements FunctionExecutor { protected Map functions = new HashMap(); protected Map escapers = new HashMap(); public FunctionRegistry() { setupDefaultFunctions(); } @Override public Value executeFunction(String name, Value... args) { Function function = functions.get(name); if (function == null) { throw new JSilverInterpreterException("Function not found " + name); } Value result = function.execute(args); if (result == null) { throw new JSilverInterpreterException("Function " + name + " did not return value"); } return result; } @Override public void escape(String name, String input, Appendable output) throws IOException { if (name == null || name.isEmpty() || name.equals("none")) { output.append(input); } else { TextFilter escaper = escapers.get(name); if (escaper == null) { throw new JSilverInterpreterException("Unknown escaper: " + name); } escaper.filter(input, output); } } @Override public boolean isEscapingFunction(String name) { Function function = functions.get(name); if (function == null) { throw new JSilverInterpreterException("Function not found " + name); } return function.isEscapingFunction(); } /** * Subclasses can override this to register their own functions. */ protected void setupDefaultFunctions() {} /** * Register a Function with a given name. */ public void registerFunction(String name, Function function) { functions.put(name, function); } /** * Register a TextFilter as a Function that takes a single String argument and returns the * filtered value. */ public void registerFunction(String name, final TextFilter textFilter) { registerFunction(name, textFilter, false); } public void registerFunction(String name, final TextFilter textFilter, final boolean isEscaper) { // Adapt a TextFilter to the Function interface. registerFunction(name, new Function() { @Override public Value execute(Value... args) { if (args.length != 1) { throw new IllegalArgumentException("Expected 1 argument"); } String in = args[0].asString(); StringBuilder out = new StringBuilder(in.length()); try { textFilter.filter(in, out); } catch (IOException e) { throw new JSilverInterpreterException(e.getMessage()); } EscapeMode mode; boolean isPartiallyEscaped; if (isEscaper) { // This function escapes its input. Hence the output is // partiallyEscaped. mode = EscapeMode.ESCAPE_IS_CONSTANT; isPartiallyEscaped = true; } else { mode = EscapeMode.ESCAPE_NONE; isPartiallyEscaped = false; for (Value arg : args) { if (arg.isPartiallyEscaped()) { isPartiallyEscaped = true; break; } } } return Value.literalValue(out.toString(), mode, isPartiallyEscaped); } public boolean isEscapingFunction() { return isEscaper; } }); } /** * Registers an escaper, that is called when executing a <?cs escape ?> command. * * @param name The name with which <?cs escape ?> will invoke this escaper. * @param escaper A TextFilter that implements the escaping functionality. */ public void registerEscapeMode(String name, TextFilter escaper) { escapers.put(name, escaper); } } src/com/google/clearsilver/jsilver/functions/NonEscapingFunction.java0100644 0000000 0000000 00000001425 13662126234 025213 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions; public abstract class NonEscapingFunction implements Function { @Override public boolean isEscapingFunction() { return false; } } src/com/google/clearsilver/jsilver/functions/TextFilter.java0100644 0000000 0000000 00000001441 13662126234 023371 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions; import java.io.IOException; /** * Filters some text. */ public interface TextFilter { void filter(String in, Appendable out) throws IOException; } src/com/google/clearsilver/jsilver/functions/bundles/0040755 0000000 0000000 00000000000 13662126234 022073 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/bundles/ClearSilverCompatibleFunctions.java0100644 0000000 0000000 00000010547 13662126234 031046 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.bundles; import com.google.clearsilver.jsilver.functions.escape.*; import com.google.clearsilver.jsilver.functions.html.CssUrlValidateFunction; import com.google.clearsilver.jsilver.functions.html.HtmlStripFunction; import com.google.clearsilver.jsilver.functions.html.HtmlUrlValidateFunction; import com.google.clearsilver.jsilver.functions.html.TextHtmlFunction; import com.google.clearsilver.jsilver.functions.numeric.AbsFunction; import com.google.clearsilver.jsilver.functions.numeric.MaxFunction; import com.google.clearsilver.jsilver.functions.numeric.MinFunction; import com.google.clearsilver.jsilver.functions.string.CrcFunction; import com.google.clearsilver.jsilver.functions.string.FindFunction; import com.google.clearsilver.jsilver.functions.string.LengthFunction; import com.google.clearsilver.jsilver.functions.string.SliceFunction; import com.google.clearsilver.jsilver.functions.structure.FirstFunction; import com.google.clearsilver.jsilver.functions.structure.LastFunction; import com.google.clearsilver.jsilver.functions.structure.SubcountFunction; /** * Set of functions required to allow JSilver to be compatible with ClearSilver. */ public class ClearSilverCompatibleFunctions extends CoreOperators { @Override protected void setupDefaultFunctions() { super.setupDefaultFunctions(); // Structure functions. registerFunction("subcount", new SubcountFunction()); registerFunction("first", new FirstFunction()); registerFunction("last", new LastFunction()); // Deprecated - but here for ClearSilver compatibility. registerFunction("len", new SubcountFunction()); // Numeric functions. registerFunction("abs", new AbsFunction()); registerFunction("max", new MaxFunction()); registerFunction("min", new MinFunction()); // String functions. registerFunction("string.slice", new SliceFunction()); registerFunction("string.find", new FindFunction()); registerFunction("string.length", new LengthFunction()); registerFunction("string.crc", new CrcFunction()); // Escaping functions. registerFunction("url_escape", new UrlEscapeFunction("UTF-8"), true); registerEscapeMode("url", new UrlEscapeFunction("UTF-8")); registerFunction("html_escape", new HtmlEscapeFunction(false), true); registerEscapeMode("html", new HtmlEscapeFunction(false)); registerFunction("js_escape", new JsEscapeFunction(false), true); registerEscapeMode("js", new JsEscapeFunction(false)); // These functions are available as arguments to // though they aren't in ClearSilver. This is so that auto escaping // can automatically add nodes with these modes registerEscapeMode("html_unquoted", new HtmlEscapeFunction(true)); registerEscapeMode("js_attr_unquoted", new JsEscapeFunction(true)); registerEscapeMode("js_check_number", new JsValidateUnquotedLiteral()); registerEscapeMode("url_validate_unquoted", new HtmlUrlValidateFunction(true)); registerEscapeMode("css", new StyleEscapeFunction(false)); registerEscapeMode("css_unquoted", new StyleEscapeFunction(true)); // HTML functions. registerFunction("html_strip", new HtmlStripFunction()); registerFunction("text_html", new TextHtmlFunction()); // url_validate is available as an argument to // though it isn't in ClearSilver. registerFunction("url_validate", new HtmlUrlValidateFunction(false), true); registerEscapeMode("url_validate", new HtmlUrlValidateFunction(false)); registerFunction("css_url_validate", new CssUrlValidateFunction(), true); // Register as an EscapingFunction so that autoescaping will be disabled // for the output of this function. registerFunction("null_escape", new NullEscapeFunction(), true); } } src/com/google/clearsilver/jsilver/functions/bundles/CoreOperators.java0100644 0000000 0000000 00000007137 13662126234 025532 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.bundles; import com.google.clearsilver.jsilver.functions.FunctionRegistry; import com.google.clearsilver.jsilver.functions.operators.AddFunction; import com.google.clearsilver.jsilver.functions.operators.AndFunction; import com.google.clearsilver.jsilver.functions.operators.DivideFunction; import com.google.clearsilver.jsilver.functions.operators.EqualFunction; import com.google.clearsilver.jsilver.functions.operators.ExistsFunction; import com.google.clearsilver.jsilver.functions.operators.GreaterFunction; import com.google.clearsilver.jsilver.functions.operators.GreaterOrEqualFunction; import com.google.clearsilver.jsilver.functions.operators.LessFunction; import com.google.clearsilver.jsilver.functions.operators.LessOrEqualFunction; import com.google.clearsilver.jsilver.functions.operators.ModuloFunction; import com.google.clearsilver.jsilver.functions.operators.MultiplyFunction; import com.google.clearsilver.jsilver.functions.operators.NotEqualFunction; import com.google.clearsilver.jsilver.functions.operators.NotFunction; import com.google.clearsilver.jsilver.functions.operators.NumericAddFunction; import com.google.clearsilver.jsilver.functions.operators.NumericEqualFunction; import com.google.clearsilver.jsilver.functions.operators.NumericFunction; import com.google.clearsilver.jsilver.functions.operators.NumericNotEqualFunction; import com.google.clearsilver.jsilver.functions.operators.OrFunction; import com.google.clearsilver.jsilver.functions.operators.SubtractFunction; import com.google.clearsilver.jsilver.functions.structure.NameFunction; /** * Function registry containing core operators used in expressions. * * These are: + - * / % ? ! && || == != < > <= >=, name. * * @see FunctionRegistry */ public class CoreOperators extends FunctionRegistry { @Override protected void setupDefaultFunctions() { super.setupDefaultFunctions(); registerFunction("+", new AddFunction()); registerFunction("#+", new NumericAddFunction()); registerFunction("-", new SubtractFunction()); registerFunction("*", new MultiplyFunction()); registerFunction("/", new DivideFunction()); registerFunction("%", new ModuloFunction()); registerFunction("?", new ExistsFunction()); registerFunction("!", new NotFunction()); registerFunction("&&", new AndFunction()); registerFunction("||", new OrFunction()); registerFunction("==", new EqualFunction()); registerFunction("#==", new NumericEqualFunction()); registerFunction("!=", new NotEqualFunction()); registerFunction("#!=", new NumericNotEqualFunction()); registerFunction("<", new LessFunction()); registerFunction(">", new GreaterFunction()); registerFunction("<=", new LessOrEqualFunction()); registerFunction(">=", new GreaterOrEqualFunction()); registerFunction("#", new NumericFunction()); // Not an operator, but JSilver cannot function without as it's used by // the command. registerFunction("name", new NameFunction()); } } src/com/google/clearsilver/jsilver/functions/escape/0040755 0000000 0000000 00000000000 13662126234 021677 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/escape/HtmlEscapeFunction.java0100644 0000000 0000000 00000006406 13662126234 026300 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.escape; /** * This class HTML escapes a string in the same way as the ClearSilver html_escape function. * * This implementation has been optimized for performance. * */ public class HtmlEscapeFunction extends SimpleEscapingFunction { // The escape chars private static final char[] ESCAPE_CHARS = {'<', '>', '&', '\'', '"'}; // UNQUOTED_ESCAPE_CHARS = ESCAPE_CHARS + UNQUOTED_EXTRA_CHARS + chars < 0x20 + 0x7f private static final char[] UNQUOTED_ESCAPE_CHARS; private static final char[] UNQUOTED_EXTRA_CHARS = {'=', ' '}; // The corresponding escape strings for all ascii characters. // With control characters, we simply strip them out if necessary. private static String[] ESCAPE_CODES = {"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "!", """, "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", ""}; static { UNQUOTED_ESCAPE_CHARS = new char[33 + ESCAPE_CHARS.length + UNQUOTED_EXTRA_CHARS.length]; // In unquoted HTML attributes, strip out control characters also, as they could // get interpreted as end of attribute, just like spaces. for (int n = 0; n <= 0x1f; n++) { UNQUOTED_ESCAPE_CHARS[n] = (char) n; } UNQUOTED_ESCAPE_CHARS[32] = (char) 0x7f; System.arraycopy(ESCAPE_CHARS, 0, UNQUOTED_ESCAPE_CHARS, 33, ESCAPE_CHARS.length); System.arraycopy(UNQUOTED_EXTRA_CHARS, 0, UNQUOTED_ESCAPE_CHARS, 33 + ESCAPE_CHARS.length, UNQUOTED_EXTRA_CHARS.length); } /** * isUnquoted should be true if the function is escaping a string that will appear inside an * unquoted HTML attribute. * * If the string is unquoted, we strip out all characters 0 - 0x1f and 0x7f for security reasons. */ public HtmlEscapeFunction(boolean isUnquoted) { if (isUnquoted) { super.setEscapeChars(UNQUOTED_ESCAPE_CHARS); } else { super.setEscapeChars(ESCAPE_CHARS); } } @Override protected String getEscapeString(char c) { if (c < 0x80) { return ESCAPE_CODES[c]; } throw new IllegalArgumentException("Unexpected escape character " + c + "[" + (int) c + "]"); } } src/com/google/clearsilver/jsilver/functions/escape/JsEscapeFunction.java0100644 0000000 0000000 00000004545 13662126234 025752 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.escape; /** * This Javascript escapes the string so it will be valid data for placement into a Javascript * string. This converts characters such as ", ', and \ into their Javascript string safe * equivilants \", \', and \\. * * This behaves in the same way as the ClearSilver js_escape function. * * This implementation has been optimized for performance. */ public class JsEscapeFunction extends SimpleEscapingFunction { private static final char[] DIGITS = "0123456789ABCDEF".toCharArray(); private static final char[] ESCAPE_CHARS; private static final char[] UNQUOTED_ESCAPE_CHARS; static { char[] SPECIAL_CHARS = {'/', '"', '\'', '\\', '>', '<', '&', ';'}; char[] UNQUOTED_SPECIAL_CHARS = {'/', '"', '\'', '\\', '>', '<', '&', ';', '=', ' '}; ESCAPE_CHARS = new char[32 + SPECIAL_CHARS.length]; UNQUOTED_ESCAPE_CHARS = new char[33 + UNQUOTED_SPECIAL_CHARS.length]; for (int n = 0; n < 32; n++) { ESCAPE_CHARS[n] = (char) n; UNQUOTED_ESCAPE_CHARS[n] = (char) n; } System.arraycopy(SPECIAL_CHARS, 0, ESCAPE_CHARS, 32, SPECIAL_CHARS.length); UNQUOTED_ESCAPE_CHARS[32] = 0x7F; System.arraycopy(UNQUOTED_SPECIAL_CHARS, 0, UNQUOTED_ESCAPE_CHARS, 33, UNQUOTED_SPECIAL_CHARS.length); } /** * isUnquoted should be true if the function is escaping a string that will appear inside an * unquoted JS attribute (like onClick or onMouseover). * */ public JsEscapeFunction(boolean isAttrUnquoted) { if (isAttrUnquoted) { super.setEscapeChars(UNQUOTED_ESCAPE_CHARS); } else { super.setEscapeChars(ESCAPE_CHARS); } } @Override protected String getEscapeString(char c) { return "\\x" + DIGITS[(c >> 4) & 0xF] + DIGITS[c & 0xF]; } } src/com/google/clearsilver/jsilver/functions/escape/JsValidateUnquotedLiteral.java0100644 0000000 0000000 00000004436 13662126234 027636 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.escape; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.IOException; /** * This function will be used to sanitize variables introduced into javascript that are not string * literals. e.g. * * Currently it only accepts boolean and numeric literals. All other values are replaced with a * 'null'. This behavior may be extended if required at a later time. This replicates the * autoescaping behavior of Clearsilver. */ public class JsValidateUnquotedLiteral implements TextFilter { public void filter(String in, Appendable out) throws IOException { /* Permit boolean literals */ if (in.equals("true") || in.equals("false")) { out.append(in); return; } boolean valid = true; if (in.startsWith("0x") || in.startsWith("0X")) { /* * There must be at least one hex digit after the 0x for it to be valid. Hex number. Check * that it is of the form 0(x|X)[0-9A-Fa-f]+ */ for (int i = 2; i < in.length(); i++) { char c = in.charAt(i); if (!((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9'))) { valid = false; break; } } } else { /* * Must be a base-10 (or octal) number. Check that it has the form [0-9+-.eE]+ */ for (int i = 0; i < in.length(); i++) { char c = in.charAt(i); if (!((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.' || c == 'e' || c == 'E')) { valid = false; break; } } } if (valid) { out.append(in); } else { out.append("null"); } } } src/com/google/clearsilver/jsilver/functions/escape/NullEscapeFunction.java0100644 0000000 0000000 00000002121 13662126234 026274 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.escape; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.IOException; /** * Returns the input string without any modification. * * This function is intended to be registered as an {@code EscapingFunction} so that it can be used * to disable autoescaping of expressions. */ public class NullEscapeFunction implements TextFilter { public void filter(String in, Appendable out) throws IOException { out.append(in); } } src/com/google/clearsilver/jsilver/functions/escape/SimpleEscapingFunction.java0100644 0000000 0000000 00000007403 13662126234 027154 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.escape; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.IOException; /** * Base class to make writing fast, simple escaping functions easy. A simple escaping function is * one where each character in the input is treated independently and there is no runtime state. The * only decision you make is whether the current character should be escaped into some different * string or not. * * The only serious limitation on using this class it that only low valued characters can be * escaped. This is because (for speed) we use an array of escaped strings, indexed by character * value. In future this limitation may be lifted if there's a call for it. */ public abstract class SimpleEscapingFunction implements TextFilter { // The limit for how many strings we can store here (max) private static final int CHAR_INDEX_LIMIT = 256; // Our fast lookup array of escaped strings. This array is indexed by char // value so it's important not to have it grow too large. For now we have // an artificial limit on it. private String[] ESCAPE_STRINGS; /** * Creates an instance to escape the given set of characters. */ protected SimpleEscapingFunction(char[] ESCAPE_CHARS) { setEscapeChars(ESCAPE_CHARS); } protected SimpleEscapingFunction() { ESCAPE_STRINGS = new String[0]; } protected void setEscapeChars(char[] ESCAPE_CHARS) throws AssertionError { int highestChar = -1; for (char c : ESCAPE_CHARS) { if (c > highestChar) { highestChar = c; } } if (highestChar >= CHAR_INDEX_LIMIT) { throw new AssertionError("Cannot escape characters with values above " + CHAR_INDEX_LIMIT); } ESCAPE_STRINGS = new String[highestChar + 1]; for (char c : ESCAPE_CHARS) { ESCAPE_STRINGS[c] = getEscapeString(c); } } /** * Given one of the escape characters supplied to this instance's constructor, return the escape * string for it. This method does not need to be efficient. */ protected abstract String getEscapeString(char c); /** * Algorithm is as follows: *

    *
  1. Scan block for contiguous unescaped sequences *
  2. Append unescaped sequences to output *
  3. Append escaped string to output (if found) *
  4. Rinse & Repeat *
*/ @Override public void filter(String in, Appendable out) throws IOException { final int len = in.length(); int pos = 0; int start = pos; while (pos < len) { // We really hope that the hotspot compiler inlines this call properly // (without optimization it accounts for > 50% of the time in this call) final char chr = in.charAt(pos); final String escapeString; if (chr < ESCAPE_STRINGS.length && (escapeString = ESCAPE_STRINGS[chr]) != null) { // We really hope our appendable handles sub-strings nicely // (we know that StringBuilder / StringBuffer does). if (pos > start) { out.append(in, start, pos); } out.append(escapeString); pos += 1; start = pos; continue; } pos += 1; } if (pos > start) { out.append(in, start, pos); } } } src/com/google/clearsilver/jsilver/functions/escape/StyleEscapeFunction.java0100644 0000000 0000000 00000005616 13662126234 026476 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.escape; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.IOException; /** * This function will be used to sanitize variables in 'style' attributes. It strips out any * characters that are not part of a whitelist of safe characters. This replicates the autoescaping * behavior of Clearsilver. * * It does not extend SimpleEscapingFunction because SimpleEscapingFunction requires a blacklist of * characters to escape. The StyleAttrEscapeFunction instead applies a whitelist, and strips out any * characters not in the whitelist. */ public class StyleEscapeFunction implements TextFilter { private static final boolean[] UNQUOTED_VALID_CHARS; private static final boolean[] VALID_CHARS; private static final int MAX_CHARS = 0x80; static { // Allow characters likely to occur inside a style property value. // Refer http://www.w3.org/TR/CSS21/ for more details. String SPECIAL_CHARS = "_.,!#%- "; String UNQUOTED_SPECIAL_CHARS = "_.,!#%-"; VALID_CHARS = new boolean[MAX_CHARS]; UNQUOTED_VALID_CHARS = new boolean[MAX_CHARS]; for (int n = 0; n < MAX_CHARS; n++) { VALID_CHARS[n] = false; UNQUOTED_VALID_CHARS[n] = false; if (Character.isLetterOrDigit(n)) { VALID_CHARS[n] = true; UNQUOTED_VALID_CHARS[n] = true; } else { if (SPECIAL_CHARS.indexOf(n) != -1) { VALID_CHARS[n] = true; } if (UNQUOTED_SPECIAL_CHARS.indexOf(n) != -1) { UNQUOTED_VALID_CHARS[n] = true; } } } } private final boolean[] validChars; /** * isUnquoted should be true if the function is escaping a string that will appear inside an * unquoted style attribute. * */ public StyleEscapeFunction(boolean isUnquoted) { if (isUnquoted) { validChars = UNQUOTED_VALID_CHARS; } else { validChars = VALID_CHARS; } } public void filter(String in, Appendable out) throws IOException { for (char c : in.toCharArray()) { if (c < MAX_CHARS && validChars[c]) { out.append(c); } else if (c >= MAX_CHARS) { out.append(c); } } } public void dumpInfo() { for (int i = 0; i < MAX_CHARS; i++) { System.out.println(i + "(" + (char) i + ")" + " :" + VALID_CHARS[i]); } } } src/com/google/clearsilver/jsilver/functions/escape/UrlEscapeFunction.java0100644 0000000 0000000 00000003563 13662126234 026137 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.escape; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; /** * This URL encodes the string. This converts characters such as ?, ampersands, and = into their URL * safe equivilants using the %hh syntax. */ public class UrlEscapeFunction implements TextFilter { private final String encoding; public UrlEscapeFunction(String encoding) { try { // Sanity check. Fail at construction time rather than render time. new OutputStreamWriter(new ByteArrayOutputStream(), encoding); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Unsupported encoding : " + encoding); } this.encoding = encoding; } @Override public void filter(String in, Appendable out) throws IOException { try { out.append(URLEncoder.encode(in, encoding)); } catch (UnsupportedEncodingException e) { // The sanity check in the constructor should have caught this. // Things must be really broken for this to happen, so throw an Error. throw new Error("Unsuported encoding : " + encoding); } } } src/com/google/clearsilver/jsilver/functions/html/0040755 0000000 0000000 00000000000 13662126234 021403 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/html/BaseUrlValidateFunction.java0100644 0000000 0000000 00000006565 13662126234 026774 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.html; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.IOException; import java.lang.Character.UnicodeBlock; /** * Validates that a given string is either something that looks like a relative URI, or looks like * an absolute URI using one of a set of allowed schemes (http, https, ftp, mailto). If the string * is valid according to these criteria, the string is escaped with an appropriate escaping * function. Otherwise, the string "#" is returned. * * Subclasses will apply the necessary escaping function to the string by overriding {@code * applyEscaping}. * *

* Note: this function does not validate that the URI is well-formed beyond the scheme part * (and if the URI appears to be relative, not even then). Note in particular that this function * considers strings of the form "www.google.com:80" to be invalid. */ public abstract class BaseUrlValidateFunction implements TextFilter { @Override public void filter(String in, Appendable out) throws IOException { if (!isValidUri(in)) { out.append('#'); return; } applyEscaping(in, out); } /** * Called by {@code filter} after verifying that the input is a valid URI. Should apply any * appropriate escaping to the input string. * * @throws IOException */ protected abstract void applyEscaping(String in, Appendable out) throws IOException; /** * @return true if a given string either looks like a relative URI, or like an absolute URI with * an allowed scheme. */ protected boolean isValidUri(String in) { // Quick check for the allowed absolute URI schemes. String maybeScheme = toLowerCaseAsciiOnly(in.substring(0, Math.min(in.length(), 8))); if (maybeScheme.startsWith("http://") || maybeScheme.startsWith("https://") || maybeScheme.startsWith("ftp://") || maybeScheme.startsWith("mailto:")) { return true; } // If it's an absolute URI with a different scheme, it's invalid. // ClearSilver defines an absolute URI as one that contains a colon prior // to any slash. int slashPos = in.indexOf('/'); if (slashPos != -1) { // only colons before this point are bad. return in.lastIndexOf(':', slashPos - 1) == -1; } else { // then any colon is bad. return in.indexOf(':') == -1; } } /** * Converts an ASCII string to lowercase. Non-ASCII characters are replaced with '?'. */ private String toLowerCaseAsciiOnly(String string) { char[] ca = string.toCharArray(); for (int i = 0; i < ca.length; i++) { char ch = ca[i]; ca[i] = (Character.UnicodeBlock.of(ch) == UnicodeBlock.BASIC_LATIN) ? Character.toLowerCase(ch) : '?'; } return new String(ca); } } src/com/google/clearsilver/jsilver/functions/html/CssUrlValidateFunction.java0100644 0000000 0000000 00000004601 13662126234 026637 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.html; import java.io.IOException; /** * Validates that input string is a valid URI. If it is not valid, the string {@code #} is returned. * If it is valid, the characters [\n\r\\'"()<>*] are URL encoded to ensure the string can be safely * inserted in a CSS URL context. In particular: *

    *
  1. In an '@import url("URL");' statement *
  2. In a CSS property such as 'background: url("URL");' *
* In both cases, enclosing quotes are optional but parenthesis are not. This filter ensures that * the URL cannot exit the parens enclosure, close a STYLE tag or reset the browser's CSS parser * (via comments or newlines). *

* References: *

    *
  1. CSS 2.1 URLs: http://www.w3.org/TR/CSS21/syndata.html#url *
  2. CSS 1 URLs: http://www.w3.org/TR/REC-CSS1/#url *
* * @see BaseUrlValidateFunction */ public class CssUrlValidateFunction extends BaseUrlValidateFunction { protected void applyEscaping(String in, Appendable out) throws IOException { for (int i = 0; i < in.length(); i++) { char ch = in.charAt(i); switch (ch) { case '\n': out.append("%0A"); break; case '\r': out.append("%0D"); break; case '"': out.append("%22"); break; case '\'': out.append("%27"); break; case '(': out.append("%28"); break; case ')': out.append("%29"); break; case '*': out.append("%2A"); break; case '<': out.append("%3C"); break; case '>': out.append("%3E"); break; case '\\': out.append("%5C"); break; default: out.append(ch); } } } } src/com/google/clearsilver/jsilver/functions/html/HtmlStripFunction.java0100644 0000000 0000000 00000015410 13662126234 025700 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.html; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * This class implements the html_strip function. It removes html tags from text, and expands * numbered and named html entities to their corresponding special characters. */ public class HtmlStripFunction implements TextFilter { // The maximum length of an entity (preceded by an &) private static final int MAX_AMP_LENGTH = 9; // The state the strip function can be, normal, in an amp escaped entity or // inside a html tag. private enum State { DEFAULT, IN_AMP, IN_TAG } // Map of entity names to special characters. private static final Map entityValues; // Initialize the entityName lookup map. static { Map tempMap = new HashMap(); // Html specific characters. tempMap.put("amp", "&"); tempMap.put("quot", "\""); tempMap.put("gt", ">"); tempMap.put("lt", "<"); tempMap.put("agrave", "\u00e0"); tempMap.put("aacute", "\u00e1"); tempMap.put("acirc", "\u00e2"); tempMap.put("atilde", "\u00e3"); tempMap.put("auml", "\u00e4"); tempMap.put("aring", "\u00e5"); tempMap.put("aelig", "\u00e6"); tempMap.put("ccedil", "\u00e7"); tempMap.put("egrave", "\u00e8"); tempMap.put("eacute", "\u00e9"); tempMap.put("ecirc", "\u00ea"); tempMap.put("euml", "\u00eb"); tempMap.put("eth", "\u00f0"); tempMap.put("igrave", "\u00ec"); tempMap.put("iacute", "\u00ed"); tempMap.put("icirc", "\u00ee"); tempMap.put("iuml", "\u00ef"); tempMap.put("ntilde", "\u00f1"); tempMap.put("nbsp", " "); tempMap.put("ograve", "\u00f2"); tempMap.put("oacute", "\u00f3"); tempMap.put("ocirc", "\u00f4"); tempMap.put("otilde", "\u00f5"); tempMap.put("ouml", "\u00f6"); tempMap.put("oslash", "\u00f8"); tempMap.put("szlig", "\u00df"); tempMap.put("thorn", "\u00fe"); tempMap.put("ugrave", "\u00f9"); tempMap.put("uacute", "\u00fa"); tempMap.put("ucirc", "\u00fb"); tempMap.put("uuml", "\u00fc"); tempMap.put("yacute", "\u00fd"); // Clearsilver's Copyright symbol! tempMap.put("copy", "(C)"); // Copy the temporary map to an unmodifiable map for the static lookup. entityValues = Collections.unmodifiableMap(tempMap); } @Override public void filter(String in, Appendable out) throws IOException { char[] inChars = in.toCharArray(); // Holds the contents of an & (amp) entity before its decoded. StringBuilder amp = new StringBuilder(); State state = State.DEFAULT; // Loop over the input string, ignoring tags, and decoding entities. for (int i = 0; i < inChars.length; i++) { char c = inChars[i]; switch (state) { case DEFAULT: switch (c) { case '&': state = State.IN_AMP; break; case '<': state = State.IN_TAG; break; default: // If this is isn't the start of an amp of a tag, treat as plain // text and just output. out.append(c); } break; case IN_TAG: // When in a tag, all input is ignored until the end of the tag. if (c == '>') { state = State.DEFAULT; } break; case IN_AMP: // Semi-colon terminates an entity, try and decode it. if (c == ';') { state = State.DEFAULT; appendDecodedEntityReference(out, amp); amp = new StringBuilder(); } else { if (amp.length() < MAX_AMP_LENGTH) { // If this is not the last character in the input, append to the // amp buffer and continue, if it is the last, dump the buffer // to stop the contents of it being lost. if (i != inChars.length - 1) { amp.append(c); } else { out.append('&').append(amp).append(c); } } else { // More than 8 chars, so not a valid entity, dump as plain text. out.append('&').append(amp).append(c); amp = new StringBuilder(); state = State.DEFAULT; } } break; } } } /** * Attempts to decode the entity provided, if it succeeds appends it to the out string. * * @param out the string builder to add the decoded entity to. * @param entityName to decode. */ private void appendDecodedEntityReference(Appendable out, CharSequence entityName) throws IOException { // All the valid entities are at least two characters long. if (entityName.length() < 2) { return; } entityName = entityName.toString().toLowerCase(); // Numbered entity. if (entityName.charAt(0) == '#') { appendNumberedEntity(out, entityName.subSequence(1, entityName.length())); return; } // If the entity is not a numeric value, try looking it up by name. String entity = entityValues.get(entityName); // If there is an entity by that name add it to the output. if (entity != null) { out.append(entity); } } /** * Appends an entity to a string by numeric code. * * @param out the string to add the entity to. * @param entity the numeric code for the entity as a char sequence. */ private void appendNumberedEntity(Appendable out, CharSequence entity) throws IOException { if (entity.length() != 0) { try { char c; // Hex numbered entities start with x. if (entity.charAt(0) == 'x') { c = (char) Integer.parseInt(entity.subSequence(1, entity.length()).toString(), 16); } else { // If its numbered, but not hex, its decimal. c = (char) Integer.parseInt(entity.toString(), 10); } // Don't append null characters, this is to remain Clearsilver compatible. if (c != '\u0000') { out.append(c); } } catch (NumberFormatException e) { // Do nothing if this is not a valid numbered entity. } } } } src/com/google/clearsilver/jsilver/functions/html/HtmlUrlValidateFunction.java0100644 0000000 0000000 00000002624 13662126234 027016 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.html; import com.google.clearsilver.jsilver.functions.escape.HtmlEscapeFunction; import java.io.IOException; /** * Validates that a given string is a valid URI and return the HTML escaped string if it is. * Otherwise the string {@code #} is returned. * * @see BaseUrlValidateFunction */ public class HtmlUrlValidateFunction extends BaseUrlValidateFunction { private final HtmlEscapeFunction htmlEscape; /** * isUnquoted should be true if the URL appears in an unquoted attribute. like: <a href=<?cs * var: uri ?>> */ public HtmlUrlValidateFunction(boolean isUnquoted) { htmlEscape = new HtmlEscapeFunction(isUnquoted); } protected void applyEscaping(String in, Appendable out) throws IOException { htmlEscape.filter(in, out); } } src/com/google/clearsilver/jsilver/functions/html/TextHtmlFunction.java0100644 0000000 0000000 00000020451 13662126234 025524 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.html; import com.google.clearsilver.jsilver.functions.TextFilter; import com.google.clearsilver.jsilver.functions.escape.HtmlEscapeFunction; import com.google.clearsilver.jsilver.functions.escape.SimpleEscapingFunction; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class implements the ClearSilver text_html function. * * It converts plain text into html, including adding 'tt' tags to ascii art and linking email and * web addresses. * * Note this implementation differs from ClearSilver, in that it html escapes the contents of links * and mailtos. */ public class TextHtmlFunction implements TextFilter { // These regular expressions are adapted from html.c in the ClearSilver // source. // Regular expression used to match email addresses, taken from the // ClearSilver source to maintain compatibility. private static final String EMAIL_REGEXP = "[^]\\[@:;<>\\\"()\\s\\p{Cntrl}]+@[-+a-zA-Z0-9]+\\.[-+a-zA-Z0-9\\.]+[-+a-zA-Z0-9]"; // Regular expression used to match urls without a scheme (www.foo.com), // adapted from the ClearSilver source to maintain compatibility. private static final String WITH_SCHEME_REGEXP = "(?:http|https|ftp|mailto):[^\\s>\"]*"; // Regular expression used to match urls with a scheme (http://www.foo.com), // adapted from the ClearSilver source to maintain compatibility. private static final String WITHOUT_SCHEME_REGEXP = "www\\.[-a-z0-9\\.]+[^\\s;\">]*"; // Pattern to match any string in the input that is linkable. private static final Pattern LINKABLES = Pattern.compile("(" + EMAIL_REGEXP + ")|(" + WITH_SCHEME_REGEXP + ")|(" + WITHOUT_SCHEME_REGEXP + ")", Pattern.CASE_INSENSITIVE); // Matching groups for the LINKABLES pattern. private static final int EMAIL_GROUP = 1; private static final int WITH_SCHEME_GROUP = 2; // We don't have access to the global html escaper here, so create a new one. private final HtmlEscapeFunction htmlEscaper = new HtmlEscapeFunction(false); // Escapes a small set of non-safe html characters, and does a a very small // amount of formatting. private final SimpleEscapingFunction htmlCharEscaper = new SimpleEscapingFunction(new char[] {'<', '>', '&', '\n', '\r'}) { @Override protected String getEscapeString(char c) { switch (c) { case '<': return "<"; case '>': return ">"; case '&': return "&"; case '\n': return "
\n"; case '\r': return ""; default: return null; } } }; @Override public void filter(String in, Appendable out) throws IOException { boolean hasAsciiArt = hasAsciiArt(in); // Add 'tt' tag to a string that contains 'ascii-art'. if (hasAsciiArt) { out.append(""); } splitAndConvert(in, out); if (hasAsciiArt) { out.append(""); } } /** * Splits the input string into blocks of normal text or linkable text. The linkable text is * converted into anchor tags before being appended to the output. The normal text is escaped and * appended to the output. */ private void splitAndConvert(String in, Appendable out) throws IOException { Matcher matcher = LINKABLES.matcher(in); int end = in.length(); int matchStart; int matchEnd; int regionStart = 0; // Keep looking for email addresses and web links until there are none left. while (matcher.find()) { matchStart = matcher.start(); matchEnd = matcher.end(); // Escape all the text from the end of the previous match to the start of // this match, and append it to the output. htmlCharEscaper.filter(in.subSequence(regionStart, matchStart).toString(), out); // Don't include a . or , in the text that is linked. if (in.charAt(matchEnd - 1) == ',' || in.charAt(matchEnd - 1) == '.') { matchEnd--; } if (matcher.group(EMAIL_GROUP) != null) { formatEmail(in, matchStart, matchEnd, out); } else { formatUrl(in, matchStart, matchEnd, // Add a scheme if the one wasn't found. matcher.group(WITH_SCHEME_GROUP) == null, out); } regionStart = matchEnd; } // Escape the text after the last match, and append it to the output. htmlCharEscaper.filter(in.substring(regionStart, end), out); } /** * Formats the input sequence into a suitable mailto: anchor tag and appends it to the output. * * @param in The string that contains the email. * @param start The start of the email address in the whole string. * @param end The end of the email in the whole string. * @param out The text output that the email address should be appended to. * @throws IOException */ private void formatEmail(String in, int start, int end, Appendable out) throws IOException { String emailPart = in.substring(start, end); out.append(""); htmlEscaper.filter(emailPart, out); out.append(""); } /** * Formats the input sequence into a suitable anchor tag and appends it to the output. * * @param in The string that contains the url. * @param start The start of the url in the containing string. * @param end The end of the url in the containing string. * @param addScheme true if 'http://' should be added to the anchor. * @param out The text output that the url should be appended to. * @throws IOException */ private void formatUrl(String in, int start, int end, boolean addScheme, Appendable out) throws IOException { String urlPart = in.substring(start, end); out.append(" "); htmlEscaper.filter(urlPart, out); out.append(""); } /** * Attempts to detect if a string contains ascii art, whitespace such as tabs will suppress ascii * art detection. * * This method takes its conditions from ClearSilver to maintain compatibility. See * has_space_formatting in html.c in the ClearSilver source. * * @param in The string to analyze for ascii art. * @return true if it is believed that the string contains ascii art. */ private boolean hasAsciiArt(String in) { int spaces = 0; int returns = 0; int asciiArt = 0; int x = 0; char[] inChars = in.toCharArray(); int length = in.length(); for (x = 0; x < length; x++) { switch (inChars[x]) { case '\t': return false; case '\r': break; case ' ': // Ignore spaces after full stops. if (x == 0 || inChars[x - 1] != '.') { spaces++; } break; case '\n': spaces = 0; returns++; break; // Characters to count towards the art total. case '/': case '\\': case '<': case '>': case ':': case '[': case ']': case '!': case '@': case '#': case '$': case '%': case '^': case '&': case '*': case '(': case ')': case '|': asciiArt++; if (asciiArt > 3) { return true; } break; default: if (returns > 2) { return false; } if (spaces > 2) { return false; } returns = 0; spaces = 0; asciiArt = 0; break; } } return false; } } src/com/google/clearsilver/jsilver/functions/numeric/0040755 0000000 0000000 00000000000 13662126234 022101 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/numeric/AbsFunction.java0100644 0000000 0000000 00000002275 13662126234 025162 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.numeric; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import static java.lang.Math.abs; /** * Returns the absolute value of the numeric expressions. */ public class AbsFunction extends NonEscapingFunction { /** * @param args Single numeric value * @return Absolute value */ public Value execute(Value... args) { Value arg = args[0]; return literalConstant(abs(arg.asNumber()), arg); } } src/com/google/clearsilver/jsilver/functions/numeric/MaxFunction.java0100644 0000000 0000000 00000002354 13662126234 025200 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.numeric; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import static java.lang.Math.max; /** * Returns the larger of two numeric expressions. */ public class MaxFunction extends NonEscapingFunction { /** * @param args Two numeric values * @return Largest of these */ public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(max(left.asNumber(), right.asNumber()), left, right); } } src/com/google/clearsilver/jsilver/functions/numeric/MinFunction.java0100644 0000000 0000000 00000002355 13662126234 025177 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.numeric; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import static java.lang.Math.min; /** * Returns the smaller of two numeric expressions. */ public class MinFunction extends NonEscapingFunction { /** * @param args Two numeric values * @return Smallest of these */ public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(min(left.asNumber(), right.asNumber()), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/0040755 0000000 0000000 00000000000 13662126234 022455 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/operators/AddFunction.java0100644 0000000 0000000 00000002447 13662126234 025522 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalValue; /** * X + Y (string). */ public class AddFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; EscapeMode mode = EscapeMode.combineModes(left.getEscapeMode(), right.getEscapeMode()); return literalValue(left.asString() + right.asString(), mode, left.isPartiallyEscaped() || right.isPartiallyEscaped()); } } src/com/google/clearsilver/jsilver/functions/operators/AndFunction.java0100644 0000000 0000000 00000002125 13662126234 025525 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X && Y. */ public class AndFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asBoolean() && right.asBoolean(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/DivideFunction.java0100644 0000000 0000000 00000002124 13662126234 026226 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X / Y. */ public class DivideFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() / right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/EqualFunction.java0100644 0000000 0000000 00000002115 13662126234 026071 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X == Y (string). */ public class EqualFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.equals(right), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/ExistsFunction.java0100644 0000000 0000000 00000002035 13662126234 026302 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * ?X. */ public class ExistsFunction extends NonEscapingFunction { public Value execute(Value... args) { Value value = args[0]; return literalConstant(value.exists(), value); } } src/com/google/clearsilver/jsilver/functions/operators/GreaterFunction.java0100644 0000000 0000000 00000002130 13662126234 026410 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X > Y. */ public class GreaterFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() > right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/GreaterOrEqualFunction.java0100644 0000000 0000000 00000002141 13662126234 027703 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X >= Y. */ public class GreaterOrEqualFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() >= right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/LessFunction.java0100644 0000000 0000000 00000002125 13662126234 025731 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X < Y. */ public class LessFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() < right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/LessOrEqualFunction.java0100644 0000000 0000000 00000002136 13662126234 027224 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X <= Y. */ public class LessOrEqualFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() <= right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/ModuloFunction.java0100644 0000000 0000000 00000002124 13662126234 026261 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X % Y. */ public class ModuloFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() % right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/MultiplyFunction.java0100644 0000000 0000000 00000002126 13662126234 026643 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X * Y. */ public class MultiplyFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() * right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/NotEqualFunction.java0100644 0000000 0000000 00000002121 13662126234 026547 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X != Y (string). */ public class NotEqualFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(!left.equals(right), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/NotFunction.java0100644 0000000 0000000 00000002036 13662126234 025564 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * !X. */ public class NotFunction extends NonEscapingFunction { public Value execute(Value... args) { Value value = args[0]; return literalConstant(!value.asBoolean(), value); } } src/com/google/clearsilver/jsilver/functions/operators/NumericAddFunction.java0100644 0000000 0000000 00000002142 13662126234 027035 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X + Y (numeric). */ public class NumericAddFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() + right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/NumericEqualFunction.java0100644 0000000 0000000 00000002146 13662126234 027420 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X == Y (numeric). */ public class NumericEqualFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() == right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/NumericFunction.java0100644 0000000 0000000 00000002106 13662126234 026424 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * #X (numeric). Forces a value to a number. */ public class NumericFunction extends NonEscapingFunction { public Value execute(Value... args) { Value value = args[0]; return literalConstant(value.asNumber(), value); } } src/com/google/clearsilver/jsilver/functions/operators/NumericNotEqualFunction.java0100644 0000000 0000000 00000002151 13662126234 030075 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X != Y (numeric). */ public class NumericNotEqualFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() != right.asNumber(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/OrFunction.java0100644 0000000 0000000 00000002124 13662126234 025402 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X || Y. */ public class OrFunction extends NonEscapingFunction { public Value execute(Value... args) { Value left = args[0]; Value right = args[1]; return literalConstant(left.asBoolean() || right.asBoolean(), left, right); } } src/com/google/clearsilver/jsilver/functions/operators/SubtractFunction.java0100644 0000000 0000000 00000002343 13662126234 026614 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.operators; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * X - Y. */ public class SubtractFunction extends NonEscapingFunction { public Value execute(Value... args) { if (args.length == 1) { Value value = args[0]; return literalConstant(0 - value.asNumber(), value); } else { Value left = args[0]; Value right = args[1]; return literalConstant(left.asNumber() - right.asNumber(), left, right); } } } src/com/google/clearsilver/jsilver/functions/string/0040755 0000000 0000000 00000000000 13662126234 021745 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/string/CrcFunction.java0100644 0000000 0000000 00000003417 13662126234 025027 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.string; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import java.io.UnsupportedEncodingException; import java.util.zip.CRC32; import java.util.zip.Checksum; /** * Returns the CRC-32 of a string. */ public class CrcFunction extends NonEscapingFunction { /** * @param args 1 string expression * @return CRC-32 of string as number value */ public Value execute(Value... args) { String string = args[0].asString(); // This function produces a 'standard' CRC-32 (IV -1, reflected polynomial, // and final complement step). The CRC-32 of "123456789" is 0xCBF43926. Checksum crc = new CRC32(); byte[] b; try { b = string.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new AssertionError("UTF-8 must be supported"); } crc.update(b, 0, b.length); // Note that we need to cast to signed int, but that's okay because the // CRC fits into 32 bits by definition. return literalConstant((int) crc.getValue(), args[0]); } } src/com/google/clearsilver/jsilver/functions/string/FindFunction.java0100644 0000000 0000000 00000002677 13662126234 025207 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.string; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * Returns the numeric position of the substring in the string (if found), otherwise returns -1 * similar to the Python string.find method. */ public class FindFunction extends NonEscapingFunction { /** * @param args 2 string expressions (full string and substring) * @return Position of the start of substring (or -1 if not found) as number value */ public Value execute(Value... args) { Value fullStringValue = args[0]; Value subStringValue = args[1]; return literalConstant(fullStringValue.asString().indexOf(subStringValue.asString()), fullStringValue, subStringValue); } } src/com/google/clearsilver/jsilver/functions/string/LengthFunction.java0100644 0000000 0000000 00000002245 13662126234 025537 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.string; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; /** * Returns the length of the string expression. */ public class LengthFunction extends NonEscapingFunction { /** * @param args A single string value * @return Length as number value */ public Value execute(Value... args) { Value value = args[0]; return literalConstant(value.asString().length(), value); } } src/com/google/clearsilver/jsilver/functions/string/SliceFunction.java0100644 0000000 0000000 00000003765 13662126234 025365 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.string; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import static com.google.clearsilver.jsilver.values.Value.literalValue; import static java.lang.Math.max; import static java.lang.Math.min; /** * Returns the string slice starting at start and ending at end, similar to the Python slice * operator. */ public class SliceFunction extends NonEscapingFunction { /** * @param args 1 string values then 2 numeric values (start and end). * @return Sliced string */ public Value execute(Value... args) { Value stringValue = args[0]; Value startValue = args[1]; Value endValue = args[2]; String string = stringValue.asString(); int start = startValue.asNumber(); int end = endValue.asNumber(); int length = string.length(); if (start < 0) { start += max(-start, length); if (end == 0) { end = length; } } if (end < 0) { end += length; } end = min(end, length); if (end < start) { return literalConstant("", args[0]); } return literalValue(string.substring(start, end), stringValue.getEscapeMode(), stringValue .isPartiallyEscaped() || startValue.isPartiallyEscaped() || endValue.isPartiallyEscaped()); } } src/com/google/clearsilver/jsilver/functions/structure/0040755 0000000 0000000 00000000000 13662126234 022477 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/functions/structure/FirstFunction.java0100644 0000000 0000000 00000002676 13662126234 026147 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.structure; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import com.google.clearsilver.jsilver.values.VariableValue; /** * Returns true if the local variable is the first in a loop or each. */ public class FirstFunction extends NonEscapingFunction { /** * @param args A local variable. * @return Boolean value. */ public Value execute(Value... args) { VariableValue arg = (VariableValue) args[0]; if (arg.getReference() == null) { return literalConstant(false, arg); } Data thisNode = arg.getReference().getSymlink(); return literalConstant(thisNode.isFirstSibling(), arg); } } src/com/google/clearsilver/jsilver/functions/structure/LastFunction.java0100644 0000000 0000000 00000002673 13662126234 025760 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.structure; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import com.google.clearsilver.jsilver.values.VariableValue; /** * Returns true if the local variable is the last in a loop or each. */ public class LastFunction extends NonEscapingFunction { /** * @param args A local variable. * @return Boolean value. */ public Value execute(Value... args) { VariableValue arg = (VariableValue) args[0]; if (arg.getReference() == null) { return literalConstant(false, arg); } Data thisNode = arg.getReference().getSymlink(); return literalConstant(thisNode.isLastSibling(), arg); } } src/com/google/clearsilver/jsilver/functions/structure/NameFunction.java0100644 0000000 0000000 00000003231 13662126234 025724 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.structure; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import static com.google.clearsilver.jsilver.values.Value.literalValue; import com.google.clearsilver.jsilver.values.VariableValue; /** * Returns the Data variable name for a local variable alias. */ public class NameFunction extends NonEscapingFunction { /** * @param args A local variable * @return Name (as string) */ public Value execute(Value... args) { Value value = args[0]; if (value instanceof VariableValue) { VariableValue variableValue = (VariableValue) value; Data variable = variableValue.getReference(); if (variable != null) { return literalValue(variable.getSymlink().getName(), variableValue.getEscapeMode(), variableValue.isPartiallyEscaped()); } } return literalConstant("", value); } } src/com/google/clearsilver/jsilver/functions/structure/SubcountFunction.java0100644 0000000 0000000 00000002715 13662126234 026654 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.functions.structure; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.functions.NonEscapingFunction; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalConstant; import com.google.clearsilver.jsilver.values.VariableValue; /** * Returns the number of child nodes for the HDF variable. */ public class SubcountFunction extends NonEscapingFunction { /** * @param args A variable value referring to an HDF node * @return Number of children */ public Value execute(Value... args) { VariableValue arg = (VariableValue) args[0]; if (arg.getReference() == null) { return literalConstant(0, arg); } Data thisNode = arg.getReference().getSymlink(); return literalConstant(thisNode.getChildCount(), arg); } } src/com/google/clearsilver/jsilver/interpreter/0040755 0000000 0000000 00000000000 13662126234 020772 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/interpreter/ExpressionEvaluator.java0100644 0000000 0000000 00000021613 13662126234 025657 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.DataContext; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAddExpression; import com.google.clearsilver.jsilver.syntax.node.AAndExpression; import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression; import com.google.clearsilver.jsilver.syntax.node.ADescendVariable; import com.google.clearsilver.jsilver.syntax.node.ADivideExpression; import com.google.clearsilver.jsilver.syntax.node.AEqExpression; import com.google.clearsilver.jsilver.syntax.node.AExistsExpression; import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression; import com.google.clearsilver.jsilver.syntax.node.AGtExpression; import com.google.clearsilver.jsilver.syntax.node.AGteExpression; import com.google.clearsilver.jsilver.syntax.node.AHexExpression; import com.google.clearsilver.jsilver.syntax.node.ALtExpression; import com.google.clearsilver.jsilver.syntax.node.ALteExpression; import com.google.clearsilver.jsilver.syntax.node.AModuloExpression; import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression; import com.google.clearsilver.jsilver.syntax.node.ANameVariable; import com.google.clearsilver.jsilver.syntax.node.ANeExpression; import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression; import com.google.clearsilver.jsilver.syntax.node.ANotExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericExpression; import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression; import com.google.clearsilver.jsilver.syntax.node.AOrExpression; import com.google.clearsilver.jsilver.syntax.node.AStringExpression; import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression; import com.google.clearsilver.jsilver.syntax.node.AVariableExpression; import com.google.clearsilver.jsilver.syntax.node.PExpression; import com.google.clearsilver.jsilver.values.Value; import static com.google.clearsilver.jsilver.values.Value.literalValue; import java.util.LinkedList; /** * Walks the tree of a PExpression node and evaluates the expression. * @see #evaluate(PExpression) */ public class ExpressionEvaluator extends DepthFirstAdapter { private Value currentValue; private final DataContext context; private final FunctionExecutor functionExecutor; /** * @param context * @param functionExecutor Used for executing functions in expressions. As well as looking up * named functions (e.g. html_escape), it also uses */ public ExpressionEvaluator(DataContext context, FunctionExecutor functionExecutor) { this.context = context; this.functionExecutor = functionExecutor; } /** * Evaluate an expression into a single value. */ public Value evaluate(PExpression expression) { assert currentValue == null; expression.apply(this); Value result = currentValue; currentValue = null; assert result != null : "No result set from " + expression.getClass(); return result; } @Override public void caseAVariableExpression(AVariableExpression node) { VariableLocator variableLocator = new VariableLocator(this); String variableName = variableLocator.getVariableName(node.getVariable()); setResult(Value.variableValue(variableName, context)); } @Override public void caseAStringExpression(AStringExpression node) { String value = node.getValue().getText(); value = value.substring(1, value.length() - 1); // Remove enclosing quotes. // The expression was a constant string literal. Does not // need to be autoescaped, as it was created by the template developer. Value result = literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, false); setResult(result); } @Override public void caseADecimalExpression(ADecimalExpression node) { String value = node.getValue().getText(); setResult(literalValue(Integer.parseInt(value), EscapeMode.ESCAPE_IS_CONSTANT, false)); } @Override public void caseAHexExpression(AHexExpression node) { String value = node.getValue().getText(); value = value.substring(2); // Remove 0x prefix. setResult(literalValue(Integer.parseInt(value, 16), EscapeMode.ESCAPE_IS_CONSTANT, false)); } @Override public void caseANumericExpression(ANumericExpression node) { executeFunction("#", node.getExpression()); } @Override public void caseANotExpression(ANotExpression node) { executeFunction("!", node.getExpression()); } @Override public void caseAExistsExpression(AExistsExpression node) { executeFunction("?", node.getExpression()); } @Override public void caseAEqExpression(AEqExpression node) { executeFunction("==", node.getLeft(), node.getRight()); } @Override public void caseANumericEqExpression(ANumericEqExpression node) { executeFunction("#==", node.getLeft(), node.getRight()); } @Override public void caseANeExpression(ANeExpression node) { executeFunction("!=", node.getLeft(), node.getRight()); } @Override public void caseANumericNeExpression(ANumericNeExpression node) { executeFunction("#!=", node.getLeft(), node.getRight()); } @Override public void caseALtExpression(ALtExpression node) { executeFunction("<", node.getLeft(), node.getRight()); } @Override public void caseAGtExpression(AGtExpression node) { executeFunction(">", node.getLeft(), node.getRight()); } @Override public void caseALteExpression(ALteExpression node) { executeFunction("<=", node.getLeft(), node.getRight()); } @Override public void caseAGteExpression(AGteExpression node) { executeFunction(">=", node.getLeft(), node.getRight()); } @Override public void caseAAndExpression(AAndExpression node) { executeFunction("&&", node.getLeft(), node.getRight()); } @Override public void caseAOrExpression(AOrExpression node) { executeFunction("||", node.getLeft(), node.getRight()); } @Override public void caseAAddExpression(AAddExpression node) { executeFunction("+", node.getLeft(), node.getRight()); } @Override public void caseANumericAddExpression(ANumericAddExpression node) { executeFunction("#+", node.getLeft(), node.getRight()); } @Override public void caseASubtractExpression(ASubtractExpression node) { executeFunction("-", node.getLeft(), node.getRight()); } @Override public void caseAMultiplyExpression(AMultiplyExpression node) { executeFunction("*", node.getLeft(), node.getRight()); } @Override public void caseADivideExpression(ADivideExpression node) { executeFunction("/", node.getLeft(), node.getRight()); } @Override public void caseAModuloExpression(AModuloExpression node) { executeFunction("%", node.getLeft(), node.getRight()); } @Override public void caseANegativeExpression(ANegativeExpression node) { executeFunction("-", node.getExpression()); } @Override public void caseAFunctionExpression(AFunctionExpression node) { LinkedList argsList = node.getArgs(); PExpression[] args = argsList.toArray(new PExpression[argsList.size()]); executeFunction(getFullFunctionName(node), args); } private void executeFunction(String name, PExpression... expressions) { Value[] args = new Value[expressions.length]; for (int i = 0; i < args.length; i++) { args[i] = evaluate(expressions[i]); } setResult(functionExecutor.executeFunction(name, args)); } /** * Sets a result from inside an expression. */ private void setResult(Value value) { assert value != null; currentValue = value; } private String getFullFunctionName(AFunctionExpression node) { final StringBuilder result = new StringBuilder(); node.getName().apply(new DepthFirstAdapter() { @Override public void caseANameVariable(ANameVariable node) { result.append(node.getWord().getText()); } @Override public void caseADescendVariable(ADescendVariable node) { node.getParent().apply(this); result.append('.'); node.getChild().apply(this); } }); return result.toString(); } } src/com/google/clearsilver/jsilver/interpreter/InterpretedMacro.java0100644 0000000 0000000 00000007452 13662126234 025111 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.node.PCommand; import com.google.clearsilver.jsilver.template.Macro; import com.google.clearsilver.jsilver.template.RenderingContext; import com.google.clearsilver.jsilver.template.Template; import java.io.IOException; /** * User defined macro that will be executed by the interpreter. * * NOTE: This is not thread safe and cannot be shared between RenderingContexts. This is taken care * of by the TemplateInterpreter. */ public class InterpretedMacro implements Macro { private final PCommand command; private final Template owningTemplate; private final String macroName; private final String[] argumentNames; private final TemplateInterpreter templateInterpreter; private final RenderingContext owningContext; public InterpretedMacro(PCommand command, Template owningTemplate, String macroName, String[] argumentNames, TemplateInterpreter templateInterpreter, RenderingContext owningContext) { this.command = command; this.owningTemplate = owningTemplate; this.macroName = macroName; this.argumentNames = argumentNames; this.templateInterpreter = templateInterpreter; this.owningContext = owningContext; } @Override public void render(RenderingContext context) throws IOException { assert context == owningContext : "Cannot render macro defined in another context"; context.pushExecutionContext(this); boolean doRuntimeAutoEscaping = !(context.isRuntimeAutoEscaping()); if (doRuntimeAutoEscaping) { context.startRuntimeAutoEscaping(); } command.apply(templateInterpreter); if (doRuntimeAutoEscaping) { context.stopRuntimeAutoEscaping(); } context.popExecutionContext(); } @Override public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException { render(createRenderingContext(data, out, resourceLoader)); } @Override public RenderingContext createRenderingContext(Data data, Appendable out, ResourceLoader resourceLoader) { return owningTemplate.createRenderingContext(data, out, resourceLoader); } @Override public String getTemplateName() { return owningTemplate.getTemplateName(); } @Override public EscapeMode getEscapeMode() { return owningTemplate.getEscapeMode(); } @Override public String getDisplayName() { return owningTemplate.getDisplayName() + ":" + macroName; } @Override public String getMacroName() { return macroName; } @Override public String getArgumentName(int index) { if (index >= argumentNames.length) { // TODO: Make sure this behavior of failing if too many // arguments are passed to a macro is consistent with JNI / interpreter. throw new JSilverInterpreterException("Too many arguments supplied to macro " + macroName); } return argumentNames[index]; } @Override public int getArgumentCount() { return argumentNames.length; } } src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplate.java0100644 0000000 0000000 00000006256 13662126234 025624 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataContext; import com.google.clearsilver.jsilver.data.DefaultDataContext; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree; import com.google.clearsilver.jsilver.template.DefaultRenderingContext; import com.google.clearsilver.jsilver.template.RenderingContext; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; import java.io.IOException; /** * Template implementation that uses the interpreter to render itself. */ public class InterpretedTemplate implements Template { private final TemplateLoader loader; private final TemplateSyntaxTree syntaxTree; private final String name; private final FunctionExecutor functionExecutor; private final EscapeMode escapeMode; private final AutoEscapeOptions autoEscapeOptions; public InterpretedTemplate(TemplateLoader loader, TemplateSyntaxTree syntaxTree, String name, FunctionExecutor functionExecutor, AutoEscapeOptions autoEscapeOptions, EscapeMode mode) { this.loader = loader; this.syntaxTree = syntaxTree; this.name = name; this.functionExecutor = functionExecutor; this.escapeMode = mode; this.autoEscapeOptions = autoEscapeOptions; } @Override public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException { render(createRenderingContext(data, out, resourceLoader)); } @Override public void render(RenderingContext context) throws IOException { TemplateInterpreter interpreter = new TemplateInterpreter(this, loader, context, functionExecutor); context.pushExecutionContext(this); syntaxTree.apply(interpreter); context.popExecutionContext(); } @Override public RenderingContext createRenderingContext(Data data, Appendable out, ResourceLoader resourceLoader) { DataContext dataContext = new DefaultDataContext(data); return new DefaultRenderingContext(dataContext, resourceLoader, out, functionExecutor, autoEscapeOptions); } @Override public String getTemplateName() { return name; } @Override public EscapeMode getEscapeMode() { return escapeMode; } @Override public String getDisplayName() { return name; } } src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplateLoader.java0100644 0000000 0000000 00000004751 13662126234 026751 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; /** * TemplateLoader that loads InterpretedTemplates. */ public class InterpretedTemplateLoader implements DelegatingTemplateLoader { private final TemplateFactory templateFactory; private final FunctionExecutor globalFunctionExecutor; private final AutoEscapeOptions autoEscapeOptions; private TemplateLoader templateLoaderDelegate = this; public InterpretedTemplateLoader(TemplateFactory templateFactory, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) { this.templateFactory = templateFactory; this.globalFunctionExecutor = globalFunctionExecutor; this.autoEscapeOptions = autoEscapeOptions; } @Override public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) { this.templateLoaderDelegate = templateLoaderDelegate; } @Override public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) { return new InterpretedTemplate(templateLoaderDelegate, templateFactory.find(templateName, resourceLoader, escapeMode), templateName, globalFunctionExecutor, autoEscapeOptions, escapeMode); } @Override public Template createTemp(String name, String content, EscapeMode escapingMode) { return new InterpretedTemplate(templateLoaderDelegate, templateFactory.createTemp(content, escapingMode), name, globalFunctionExecutor, autoEscapeOptions, escapingMode); } } src/com/google/clearsilver/jsilver/interpreter/LoadingTemplateFactory.java0100644 0000000 0000000 00000003513 13662126234 026235 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.exceptions.JSilverIOException; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.SyntaxTreeBuilder; import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree; import java.io.IOException; import java.io.Reader; import java.io.StringReader; /** * Loads a template from disk, and parses it into an AST. Does not do any caching. */ public class LoadingTemplateFactory implements TemplateFactory { private final SyntaxTreeBuilder syntaxTreeBuilder = new SyntaxTreeBuilder(); public TemplateSyntaxTree find(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) { try { Reader reader = resourceLoader.openOrFail(templateName); try { return syntaxTreeBuilder.parse(reader, templateName, escapeMode); } finally { reader.close(); } } catch (IOException e) { throw new JSilverIOException(e); } } public TemplateSyntaxTree createTemp(String content, EscapeMode escapeMode) { return syntaxTreeBuilder.parse(new StringReader(content), "", escapeMode); } } src/com/google/clearsilver/jsilver/interpreter/OptimizerProvider.java0100644 0000000 0000000 00000001636 13662126234 025335 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.syntax.node.Switch; /** * This interface is used to provide Optimizer Switches to OptimizingTemplateFactory, some which may * need to be constructed each time it runs. */ public interface OptimizerProvider { Switch getOptimizer(); } src/com/google/clearsilver/jsilver/interpreter/OptimizingTemplateFactory.java0100644 0000000 0000000 00000004606 13662126234 027015 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree; import java.util.ArrayList; import java.util.List; /** * Wraps a template factory with a series of optimization steps. Any null optimization steps are * ignored. */ public class OptimizingTemplateFactory implements TemplateFactory { private final TemplateFactory wrapped; private final List optimizers; /** * Creates a factory from the given optimization steps that wraps another TemplateFactory. * * @param wrapped the template factory instance to be wrapped. * @param optimizers the optimizers to apply (null optimizations are ignored). */ public OptimizingTemplateFactory(TemplateFactory wrapped, OptimizerProvider... optimizers) { this.wrapped = wrapped; // Ignore null providers during construction. this.optimizers = new ArrayList(); for (OptimizerProvider optimizer : optimizers) { if (optimizer != null) { this.optimizers.add(optimizer); } } } private void optimize(TemplateSyntaxTree ast) { for (OptimizerProvider optimizer : optimizers) { ast.apply(optimizer.getOptimizer()); } } @Override public TemplateSyntaxTree createTemp(String content, EscapeMode escapeMode) { TemplateSyntaxTree result = wrapped.createTemp(content, escapeMode); optimize(result); return result; } @Override public TemplateSyntaxTree find(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) { TemplateSyntaxTree result = wrapped.find(templateName, resourceLoader, escapeMode); optimize(result); return result; } } src/com/google/clearsilver/jsilver/interpreter/TemplateFactory.java0100644 0000000 0000000 00000003462 13662126234 024742 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree; /** * Responsible for creating/retrieving an AST tree for a template with a given name. *

* This interface always expects to take a ResourceLoader object from the caller. This helps * guarantee that per-Template resourceLoaders are respected. */ public interface TemplateFactory { /** * Load a template from the source. * * @param templateName e.g. some/path/to/template.cs * @param resourceLoader use this ResourceLoader to locate the named template file and any * included files. * @param escapeMode the type of escaping to apply to the entire template. */ TemplateSyntaxTree find(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode); /** * Create a temporary template from content. * * @param content e.g. "Hello <cs var:name >" * @param escapeMode * @param escapeMode the type of escaping to apply to the entire template. */ TemplateSyntaxTree createTemp(String content, EscapeMode escapeMode); } src/com/google/clearsilver/jsilver/interpreter/TemplateInterpreter.java0100644 0000000 0000000 00000057255 13662126234 025647 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataContext; import com.google.clearsilver.jsilver.exceptions.ExceptionUtil; import com.google.clearsilver.jsilver.exceptions.JSilverIOException; import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAltCommand; import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand; import com.google.clearsilver.jsilver.syntax.node.ACallCommand; import com.google.clearsilver.jsilver.syntax.node.ADataCommand; import com.google.clearsilver.jsilver.syntax.node.ADefCommand; import com.google.clearsilver.jsilver.syntax.node.AEachCommand; import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand; import com.google.clearsilver.jsilver.syntax.node.AEvarCommand; import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand; import com.google.clearsilver.jsilver.syntax.node.AIfCommand; import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand; import com.google.clearsilver.jsilver.syntax.node.ALvarCommand; import com.google.clearsilver.jsilver.syntax.node.ANameCommand; import com.google.clearsilver.jsilver.syntax.node.ANameVariable; import com.google.clearsilver.jsilver.syntax.node.ASetCommand; import com.google.clearsilver.jsilver.syntax.node.AUvarCommand; import com.google.clearsilver.jsilver.syntax.node.AVarCommand; import com.google.clearsilver.jsilver.syntax.node.AWithCommand; import com.google.clearsilver.jsilver.syntax.node.PCommand; import com.google.clearsilver.jsilver.syntax.node.PExpression; import com.google.clearsilver.jsilver.syntax.node.PPosition; import com.google.clearsilver.jsilver.syntax.node.PVariable; import com.google.clearsilver.jsilver.syntax.node.TCsOpen; import com.google.clearsilver.jsilver.syntax.node.TWord; import com.google.clearsilver.jsilver.template.Macro; import com.google.clearsilver.jsilver.template.RenderingContext; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; import com.google.clearsilver.jsilver.values.Value; import com.google.clearsilver.jsilver.values.VariableValue; import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; /** * Main JSilver interpreter. This walks a template's AST and renders the result out. */ public class TemplateInterpreter extends DepthFirstAdapter { private final Template template; private final ExpressionEvaluator expressionEvaluator; private final VariableLocator variableLocator; private final TemplateLoader templateLoader; private final RenderingContext context; private final DataContext dataContext; public TemplateInterpreter(Template template, TemplateLoader templateLoader, RenderingContext context, FunctionExecutor functionExecutor) { this.template = template; this.templateLoader = templateLoader; this.context = context; this.dataContext = context.getDataContext(); expressionEvaluator = new ExpressionEvaluator(dataContext, functionExecutor); variableLocator = new VariableLocator(expressionEvaluator); } // ------------------------------------------------------------------------ // COMMAND PROCESSING /** * Chunk of data (i.e. not a CS command). */ @Override public void caseADataCommand(ADataCommand node) { context.writeUnescaped(node.getData().getText()); } /** * <?cs var:blah > expression. Evaluate as string and write output, using default escaping. */ @Override public void caseAVarCommand(AVarCommand node) { setLastPosition(node.getPosition()); // Evaluate expression. Value value = expressionEvaluator.evaluate(node.getExpression()); writeVariable(value); } /** * <?cs uvar:blah > expression. Evaluate as string and write output, but don't escape. */ @Override public void caseAUvarCommand(AUvarCommand node) { setLastPosition(node.getPosition()); // Evaluate expression. Value value = expressionEvaluator.evaluate(node.getExpression()); context.writeUnescaped(value.asString()); } /** * <?cs lvar:blah > command. Evaluate expression and execute commands within. */ @Override public void caseALvarCommand(ALvarCommand node) { setLastPosition(node.getPosition()); evaluateVariable(node.getExpression(), "[lvar expression]"); } /** * <?cs evar:blah > command. Evaluate expression and execute commands within. */ @Override public void caseAEvarCommand(AEvarCommand node) { setLastPosition(node.getPosition()); evaluateVariable(node.getExpression(), "[evar expression]"); } private void evaluateVariable(PExpression expression, String stackTraceDescription) { // Evaluate expression. Value value = expressionEvaluator.evaluate(expression); // Now parse result, into new mini template. Template template = templateLoader.createTemp(stackTraceDescription, value.asString(), context .getAutoEscapeMode()); // Intepret new template. try { template.render(context); } catch (IOException e) { throw new JSilverInterpreterException(e.getMessage()); } } /** * <?cs linclude!'somefile.cs' > command. Lazily includes another template (at render time). * Throw an error if file does not exist. */ @Override public void caseAHardLincludeCommand(AHardLincludeCommand node) { setLastPosition(node.getPosition()); include(node.getExpression(), false); } /** * <?cs linclude:'somefile.cs' > command. Lazily includes another template (at render time). * Silently ignore if the included file does not exist. */ @Override public void caseALincludeCommand(ALincludeCommand node) { setLastPosition(node.getPosition()); include(node.getExpression(), true); } /** * <?cs include!'somefile.cs' > command. Throw an error if file does not exist. */ @Override public void caseAHardIncludeCommand(AHardIncludeCommand node) { setLastPosition(node.getPosition()); include(node.getExpression(), false); } /** * <?cs include:'somefile.cs' > command. Silently ignore if the included file does not * exist. */ @Override public void caseAIncludeCommand(AIncludeCommand node) { setLastPosition(node.getPosition()); include(node.getExpression(), true); } /** * <?cs set:x='y' > command. */ @Override public void caseASetCommand(ASetCommand node) { setLastPosition(node.getPosition()); String variableName = variableLocator.getVariableName(node.getVariable()); try { Data variable = dataContext.findVariable(variableName, true); Value value = expressionEvaluator.evaluate(node.getExpression()); variable.setValue(value.asString()); // TODO: what about nested structures? // "set" was used to set a variable to a constant or escaped value like // X" ?> or // Keep track of this so autoescaping code can take it into account. variable.setEscapeMode(value.getEscapeMode()); } catch (UnsupportedOperationException e) { // An error occurred - probably due to trying to modify an UnmodifiableData throw new UnsupportedOperationException(createUnsupportedOperationMessage(node, context .getIncludedTemplateNames()), e); } } /** * <?cs name:blah > command. Writes out the name of the original variable referred to by a * given node. */ @Override public void caseANameCommand(ANameCommand node) { setLastPosition(node.getPosition()); String variableName = variableLocator.getVariableName(node.getVariable()); Data variable = dataContext.findVariable(variableName, false); if (variable != null) { context.writeEscaped(variable.getSymlink().getName()); } } /** * <?cs if:blah > ... <?cs else > ... <?cs /if > command. */ @Override public void caseAIfCommand(AIfCommand node) { setLastPosition(node.getPosition()); Value value = expressionEvaluator.evaluate(node.getExpression()); if (value.asBoolean()) { node.getBlock().apply(this); } else { node.getOtherwise().apply(this); } } /** * <?cs escape:'html' > command. Changes default escaping function. */ @Override public void caseAEscapeCommand(AEscapeCommand node) { setLastPosition(node.getPosition()); Value value = expressionEvaluator.evaluate(node.getExpression()); String escapeStrategy = value.asString(); context.pushEscapingFunction(escapeStrategy); node.getCommand().apply(this); context.popEscapingFunction(); } /** * A fake command injected by AutoEscaper. * * AutoEscaper determines the html context in which an include or lvar or evar command is called * and stores this context in the AAutoescapeCommand node. */ @Override public void caseAAutoescapeCommand(AAutoescapeCommand node) { setLastPosition(node.getPosition()); Value value = expressionEvaluator.evaluate(node.getExpression()); String escapeStrategy = value.asString(); EscapeMode mode = EscapeMode.computeEscapeMode(escapeStrategy); context.pushAutoEscapeMode(mode); node.getCommand().apply(this); context.popAutoEscapeMode(); } /** * <?cs with:x=Something > ... <?cs /with > command. Aliases a value within a specific * scope. */ @Override public void caseAWithCommand(AWithCommand node) { setLastPosition(node.getPosition()); VariableLocator variableLocator = new VariableLocator(expressionEvaluator); String withVar = variableLocator.getVariableName(node.getVariable()); Value value = expressionEvaluator.evaluate(node.getExpression()); if (value instanceof VariableValue) { if (((VariableValue) value).getReference() == null) { // With refers to a non-existent variable. Do nothing. return; } } dataContext.pushVariableScope(); setTempVariable(withVar, value); node.getCommand().apply(this); dataContext.popVariableScope(); } /** * <?cs loop:10 > ... <?cs /loop > command. Loops over a range of numbers, starting at * zero. */ @Override public void caseALoopToCommand(ALoopToCommand node) { setLastPosition(node.getPosition()); int end = expressionEvaluator.evaluate(node.getExpression()).asNumber(); // Start is always zero, increment is always 1, so end < 0 is invalid. if (end < 0) { return; // Incrementing the wrong way. Avoid infinite loop. } loop(node.getVariable(), 0, end, 1, node.getCommand()); } /** * <?cs loop:0,10 > ... <?cs /loop > command. Loops over a range of numbers. */ @Override public void caseALoopCommand(ALoopCommand node) { setLastPosition(node.getPosition()); int start = expressionEvaluator.evaluate(node.getStart()).asNumber(); int end = expressionEvaluator.evaluate(node.getEnd()).asNumber(); // Start is always zero, increment is always 1, so end < 0 is invalid. if (end < start) { return; // Incrementing the wrong way. Avoid infinite loop. } loop(node.getVariable(), start, end, 1, node.getCommand()); } /** * <?cs loop:0,10,2 > ... <?cs /loop > command. Loops over a range of numbers, with a * specific increment. */ @Override public void caseALoopIncCommand(ALoopIncCommand node) { setLastPosition(node.getPosition()); int start = expressionEvaluator.evaluate(node.getStart()).asNumber(); int end = expressionEvaluator.evaluate(node.getEnd()).asNumber(); int incr = expressionEvaluator.evaluate(node.getIncrement()).asNumber(); if (incr == 0) { return; // No increment. Avoid infinite loop. } if (incr > 0 && start > end) { return; // Incrementing the wrong way. Avoid infinite loop. } if (incr < 0 && start < end) { return; // Incrementing the wrong way. Avoid infinite loop. } loop(node.getVariable(), start, end, incr, node.getCommand()); } /** * <?cs each:x=Stuff > ... <?cs /each > command. Loops over child items of a data * node. */ @Override public void caseAEachCommand(AEachCommand node) { setLastPosition(node.getPosition()); Value expression = expressionEvaluator.evaluate(node.getExpression()); if (expression instanceof VariableValue) { VariableValue variableValue = (VariableValue) expression; Data parent = variableValue.getReference(); if (parent != null) { each(node.getVariable(), variableValue.getName(), parent, node.getCommand()); } } } /** * <?cs alt:someValue > ... <?cs /alt > command. If value exists, write it, otherwise * write the body of the command. */ @Override public void caseAAltCommand(AAltCommand node) { setLastPosition(node.getPosition()); Value value = expressionEvaluator.evaluate(node.getExpression()); if (value.asBoolean()) { writeVariable(value); } else { node.getCommand().apply(this); } } private void writeVariable(Value value) { if (template.getEscapeMode().isAutoEscapingMode()) { autoEscapeAndWriteVariable(value); } else if (value.isPartiallyEscaped()) { context.writeUnescaped(value.asString()); } else { context.writeEscaped(value.asString()); } } private void autoEscapeAndWriteVariable(Value value) { if (isTrustedValue(value) || value.isPartiallyEscaped()) { context.writeUnescaped(value.asString()); } else { context.writeEscaped(value.asString()); } } private boolean isTrustedValue(Value value) { // True if PropagateEscapeStatus is enabled and value has either been // escaped or contains a constant string. return context.getAutoEscapeOptions().getPropagateEscapeStatus() && !value.getEscapeMode().equals(EscapeMode.ESCAPE_NONE); } // ------------------------------------------------------------------------ // MACROS /** * <?cs def:someMacro(x,y) > ... <?cs /def > command. Define a macro (available for * the remainder of the interpreter context. */ @Override public void caseADefCommand(ADefCommand node) { String macroName = makeWord(node.getMacro()); LinkedList arguments = node.getArguments(); String[] argumentNames = new String[arguments.size()]; int i = 0; for (PVariable argument : arguments) { if (!(argument instanceof ANameVariable)) { throw new JSilverInterpreterException("Invalid name for macro '" + macroName + "' argument " + i + " : " + argument); } argumentNames[i++] = ((ANameVariable) argument).getWord().getText(); } // TODO: Should we enforce that macro args can't repeat the same // name? context.registerMacro(macroName, new InterpretedMacro(node.getCommand(), template, macroName, argumentNames, this, context)); } private String makeWord(LinkedList words) { if (words.size() == 1) { return words.getFirst().getText(); } StringBuilder result = new StringBuilder(); for (TWord word : words) { if (result.length() > 0) { result.append('.'); } result.append(word.getText()); } return result.toString(); } /** * <?cs call:someMacro(x,y) command. Call a macro. Need to create a new variable scope to hold * the local variables defined by the parameters of the macro definition */ @Override public void caseACallCommand(ACallCommand node) { String macroName = makeWord(node.getMacro()); Macro macro = context.findMacro(macroName); // Make sure that the number of arguments passed to the macro match the // number expected. if (node.getArguments().size() != macro.getArgumentCount()) { throw new JSilverInterpreterException("Number of arguments to macro " + macroName + " (" + node.getArguments().size() + ") does not match " + "number of expected arguments (" + macro.getArgumentCount() + ")"); } int numArgs = node.getArguments().size(); if (numArgs > 0) { Value[] argValues = new Value[numArgs]; // We must first evaluate the parameters we are passing or there could be // conflicts if new argument names match existing variables. Iterator argumentValues = node.getArguments().iterator(); for (int i = 0; argumentValues.hasNext(); i++) { argValues[i] = expressionEvaluator.evaluate(argumentValues.next()); } // No need to bother pushing and popping the variable scope stack // if there are no new local variables to declare. dataContext.pushVariableScope(); for (int i = 0; i < argValues.length; i++) { setTempVariable(macro.getArgumentName(i), argValues[i]); } } try { macro.render(context); } catch (IOException e) { throw new JSilverIOException(e); } if (numArgs > 0) { // No need to bother pushing and popping the variable scope stack // if there are no new local variables to declare. dataContext.popVariableScope(); } } // ------------------------------------------------------------------------ // HELPERS // // Much of the functionality in this section could easily be inlined, // however it makes the rest of the interpreter much easier to understand // and refactor with them defined here. private void each(PVariable variable, String parentName, Data items, PCommand command) { // Since HDF variables are now passed to macro parameters by path name // we need to create a path for each child when generating the // VariableValue object. VariableLocator variableLocator = new VariableLocator(expressionEvaluator); String eachVar = variableLocator.getVariableName(variable); StringBuilder pathBuilder = new StringBuilder(parentName); pathBuilder.append('.'); int length = pathBuilder.length(); dataContext.pushVariableScope(); for (Data child : items.getChildren()) { pathBuilder.delete(length, pathBuilder.length()); pathBuilder.append(child.getName()); setTempVariable(eachVar, Value.variableValue(pathBuilder.toString(), dataContext)); command.apply(this); } dataContext.popVariableScope(); } private void loop(PVariable loopVar, int start, int end, int incr, PCommand command) { VariableLocator variableLocator = new VariableLocator(expressionEvaluator); String varName = variableLocator.getVariableName(loopVar); dataContext.pushVariableScope(); // Loop deals with counting forward or backwards. for (int index = start; incr > 0 ? index <= end : index >= end; index += incr) { // We reuse the same scope for efficiency and simply overwrite the // previous value of the loop variable. dataContext.createLocalVariableByValue(varName, String.valueOf(index), index == start, index == end); command.apply(this); } dataContext.popVariableScope(); } /** * Code common to all three include commands. * * @param expression expression representing name of file to include. * @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template * loader should be ignored, {@code false} otherwise. */ private void include(PExpression expression, boolean ignoreMissingFile) { // Evaluate expression. Value path = expressionEvaluator.evaluate(expression); String templateName = path.asString(); if (!context.pushIncludeStackEntry(templateName)) { throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context .getIncludedTemplateNames())); } loadAndRenderIncludedTemplate(templateName, ignoreMissingFile); if (!context.popIncludeStackEntry(templateName)) { // Include stack trace is corrupted throw new IllegalStateException("Unable to find on include stack: " + templateName); } } private String createIncludeLoopErrorMessage(String templateName, Iterable includeStack) { StringBuilder message = new StringBuilder(); message.append("File included twice: "); message.append(templateName); message.append(" Include stack:"); for (String fileName : includeStack) { message.append("\n -> "); message.append(fileName); } message.append("\n -> "); message.append(templateName); return message.toString(); } private String createUnsupportedOperationMessage(PCommand node, Iterable includeStack) { StringBuilder message = new StringBuilder(); message.append("exception thrown while parsing node: "); message.append(node.toString()); message.append(" (class ").append(node.getClass().getSimpleName()).append(")"); message.append("\nTemplate include stack: "); for (Iterator iter = includeStack.iterator(); iter.hasNext();) { message.append(iter.next()); if (iter.hasNext()) { message.append(" -> "); } } message.append("\n"); return message.toString(); } // This method should ONLY be called from include() private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile) { // Now load new template with given name. Template template = null; try { template = templateLoader.load(templateName, context.getResourceLoader(), context .getAutoEscapeMode()); } catch (RuntimeException e) { if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) { return; } else { throw e; } } // Intepret loaded template. try { // TODO: Execute lincludes (but not includes) in a separate // context. template.render(context); } catch (IOException e) { throw new JSilverInterpreterException(e.getMessage()); } } private void setLastPosition(PPosition position) { // Walks position node which will eventually result in calling // caseTCsOpen(). position.apply(this); } /** * Every time a <cs token is found, grab the line and position (for helpful error messages). */ @Override public void caseTCsOpen(TCsOpen node) { int line = node.getLine(); int column = node.getPos(); context.setCurrentPosition(line, column); } private void setTempVariable(String variableName, Value value) { if (value instanceof VariableValue) { // If the value is a Data variable name, then we store a reference to its // name as discovered by the expression evaluator and resolve it each // time for correctness. dataContext.createLocalVariableByPath(variableName, ((VariableValue) value).getName()); } else { dataContext.createLocalVariableByValue(variableName, value.asString(), value.getEscapeMode()); } } } src/com/google/clearsilver/jsilver/interpreter/VariableLocator.java0100644 0000000 0000000 00000007377 13662126234 024721 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.interpreter; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.ADecNumberVariable; import com.google.clearsilver.jsilver.syntax.node.ADescendVariable; import com.google.clearsilver.jsilver.syntax.node.AExpandVariable; import com.google.clearsilver.jsilver.syntax.node.AHexNumberVariable; import com.google.clearsilver.jsilver.syntax.node.ANameVariable; import com.google.clearsilver.jsilver.syntax.node.PVariable; import com.google.clearsilver.jsilver.values.Value; /** * Walks a PVariable node from the parse tree and returns a Data path name. * * @see #getVariableName(com.google.clearsilver.jsilver.syntax.node.PVariable) */ public class VariableLocator extends DepthFirstAdapter { private StringBuilder currentName; private final ExpressionEvaluator expressionEvaluator; public VariableLocator(ExpressionEvaluator expressionEvaluator) { this.expressionEvaluator = expressionEvaluator; } /** * If the root PVariable we are evaluating is a simple one then skip creating the StringBuilder * and descending the tree. * * @param variable the variable node to evaluate. * @return a String representing the Variable name, or {@code null} if it is a compound variable * node. */ private String quickEval(PVariable variable) { if (variable instanceof ANameVariable) { return ((ANameVariable) variable).getWord().getText(); } else if (variable instanceof ADecNumberVariable) { return ((ADecNumberVariable) variable).getDecNumber().getText(); } else if (variable instanceof AHexNumberVariable) { return ((AHexNumberVariable) variable).getHexNumber().getText(); } else { // This is a compound variable. Evaluate the slow way. return null; } } /** * Returns a Data variable name extracted during evaluation. * * @param variable the parsed variable name node to traverse */ public String getVariableName(PVariable variable) { String result = quickEval(variable); if (result != null) { return result; } StringBuilder lastName = currentName; currentName = new StringBuilder(10); variable.apply(this); result = currentName.toString(); currentName = lastName; return result; } @Override public void caseANameVariable(ANameVariable node) { descendVariable(node.getWord().getText()); } @Override public void caseADecNumberVariable(ADecNumberVariable node) { descendVariable(node.getDecNumber().getText()); } @Override public void caseAHexNumberVariable(AHexNumberVariable node) { descendVariable(node.getHexNumber().getText()); } @Override public void caseADescendVariable(ADescendVariable node) { node.getParent().apply(this); node.getChild().apply(this); } @Override public void caseAExpandVariable(AExpandVariable node) { node.getParent().apply(this); Value value = expressionEvaluator.evaluate(node.getChild()); descendVariable(value.asString()); } private void descendVariable(String name) { if (currentName.length() != 0) { currentName.append('.'); } currentName.append(name); } } src/com/google/clearsilver/jsilver/output/0040755 0000000 0000000 00000000000 13662126234 017767 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/output/InstanceOutputBufferProvider.java0100644 0000000 0000000 00000002114 13662126234 026457 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.output; /** * Implementation of OutputBufferProvider that creates a new StringBuilder */ public class InstanceOutputBufferProvider implements OutputBufferProvider { private final int bufferSize; public InstanceOutputBufferProvider(int bufferSize) { this.bufferSize = bufferSize; } @Override public Appendable get() { return new StringBuilder(bufferSize); } @Override public void release(Appendable buffer) { // Nothing to do. } } src/com/google/clearsilver/jsilver/output/OutputBufferProvider.java0100644 0000000 0000000 00000002011 13662126234 024766 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.output; /** * Simple Provider interface for the output buffer. */ public interface OutputBufferProvider { /** * Returns a clean Appendable buffer ready to use while rendering. */ Appendable get(); /** * Tells the provider that this buffer is free to be reused. * * @param buffer the Appendable object handed out by {@link #get} */ void release(Appendable buffer); } src/com/google/clearsilver/jsilver/output/ThreadLocalOutputBufferProvider.java0100644 0000000 0000000 00000003340 13662126234 027077 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.output; /** * Implementation of OutputBufferProvider that reuses the same StringBuilder in each Thread. */ public class ThreadLocalOutputBufferProvider implements OutputBufferProvider { private final ThreadLocal pool; private final ThreadLocal available; public ThreadLocalOutputBufferProvider(final int bufferSize) { pool = new ThreadLocal() { protected StringBuilder initialValue() { return new StringBuilder(bufferSize); } }; available = new ThreadLocal() { protected Boolean initialValue() { return true; } }; } @Override public Appendable get() { if (!available.get()) { throw new IllegalStateException("Thread buffer is not free."); } StringBuilder sb = pool.get(); available.set(false); sb.setLength(0); return sb; } @Override public void release(Appendable buffer) { if (buffer != pool.get()) { throw new IllegalArgumentException("Can't release buffer that does not " + "correspond to this thread."); } available.set(true); } } src/com/google/clearsilver/jsilver/precompiler/0040755 0000000 0000000 00000000000 13662126234 020750 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateLoader.java0100644 0000000 0000000 00000012141 13662126234 026675 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.precompiler; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.compiler.BaseCompiledTemplate; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; /** * TemplateLoader that stores objects from precompiled Template classes and serves them when asked * for them. If not found, it passes the request on to the delegate TemplateLoader. */ public class PrecompiledTemplateLoader implements DelegatingTemplateLoader { /** * This is the next TemplateLoader to ask if we don't find the template. */ private final TemplateLoader nextLoader; private final Map templateMap; private final AutoEscapeOptions autoEscapeOptions; public PrecompiledTemplateLoader(TemplateLoader nextLoader, Map templateToClassNameMap, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) { this.nextLoader = nextLoader; this.autoEscapeOptions = autoEscapeOptions; this.templateMap = makeTemplateMap(templateToClassNameMap, globalFunctionExecutor); } private Map makeTemplateMap( Map templateToClassNameMap, FunctionExecutor globalFunctionExecutor) { Map templateMap = new HashMap(); ClassLoader classLoader = getClass().getClassLoader(); for (Map.Entry entry : templateToClassNameMap.entrySet()) { String className = entry.getValue(); BaseCompiledTemplate compiledTemplate = loadTemplateObject(className, classLoader); // Fill in the necessary compiledTemplate.setFunctionExecutor(globalFunctionExecutor); compiledTemplate.setTemplateName(entry.getKey().toString()); compiledTemplate.setTemplateLoader(this); if (entry.getKey() instanceof PrecompiledTemplateMapKey) { PrecompiledTemplateMapKey mapKey = (PrecompiledTemplateMapKey) entry.getKey(); // The template's escapeMode is not currently used as the autoescaping is all // handled at compile time. Still set it in case it is needed later on. compiledTemplate.setEscapeMode(mapKey.getEscapeMode()); } else { compiledTemplate.setEscapeMode(EscapeMode.ESCAPE_NONE); } compiledTemplate.setAutoEscapeOptions(autoEscapeOptions); templateMap.put(entry.getKey(), compiledTemplate); } return ImmutableMap.copyOf(templateMap); } @VisibleForTesting protected BaseCompiledTemplate loadTemplateObject(String className, ClassLoader classLoader) { try { Class templateClass = classLoader.loadClass(className); // TODO: Not safe to use in untrusted environments // Does not handle ClassCastException or // verify class type before calling newInstance. return (BaseCompiledTemplate) templateClass.newInstance(); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Class not found: " + className, e); } catch (IllegalAccessException e) { throw new Error(e); } catch (InstantiationException e) { throw new Error(e); } } @Override public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) { for (BaseCompiledTemplate template : templateMap.values()) { template.setTemplateLoader(templateLoaderDelegate); } } @Override public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) { Object key = resourceLoader.getKey(templateName); PrecompiledTemplateMapKey mapKey = new PrecompiledTemplateMapKey(key, escapeMode); Template template = templateMap.get(mapKey); if (template != null) { return template; } else { return nextLoader.load(templateName, resourceLoader, escapeMode); } } /** * We don't cache temporary templates here so we just call delegate TemplateLoader. */ @Override public Template createTemp(String name, String content, EscapeMode escapeMode) { return nextLoader.createTemp(name, content, escapeMode); } } src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapFileReader.java0100644 0000000 0000000 00000012510 13662126234 030127 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.precompiler; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.Reader; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; /** * Utility class that reads in the file output by BatchCompiler that is a list of template names and * corresponding class names and returns a Map of template filenames to class names which can be fed * to {@see com.google.clearsilver.jsilver.JSilverOptions#setPrecompiledTemplateMap} */ public class PrecompiledTemplateMapFileReader { private final String mapFileName; private final String dirPattern; private final String rootDir; private Map templateMap = null; /** * Helper object that reads in the specified resource file and generates a mapping of template * filenames to corresponding BaseCompiledTemplate class names. * * @param filename name of the resource file to read the map from. * @param dirPattern prefix to remove from read in template names. Used in conjunction with * rootDir to update template file paths. * @param rootDir optional string to prepend to all non-absolute template filenames. Should be set * to the location of the templates in production via a flag. */ public PrecompiledTemplateMapFileReader(String filename, String dirPattern, String rootDir) { this.mapFileName = filename; this.dirPattern = dirPattern; this.rootDir = rootDir; } public Map getTemplateMap() throws IOException { if (templateMap == null) { templateMap = makeTemplateMap(mapFileName, rootDir); } return templateMap; } private Map makeTemplateMap(String templateMapFile, String rootDir) throws IOException { Map templateMap = new HashMap(); LineNumberReader reader = null; try { reader = new LineNumberReader(getMapFileReader(templateMapFile)); for (String line = reader.readLine(); line != null; line = reader.readLine()) { // Process single line from the templateMapFile // and put found templates into templateMap. processTemplateMapFileLine(line, reader.getLineNumber(), templateMap, templateMapFile, rootDir); } } finally { if (reader != null) { reader.close(); } } return ImmutableMap.copyOf(templateMap); } private void processTemplateMapFileLine(String line, int lineNumber, Map templateMap, String templateMapFile, String rootDir) { line = line.trim(); if (line.isEmpty() || line.startsWith("#")) { // Ignore blank lines and comment lines. return; } StringTokenizer st = new StringTokenizer(line); if (!st.hasMoreTokens()) { throw new IllegalArgumentException("No template file name found in " + templateMapFile + " on line " + lineNumber + ": " + line); } String templateName = st.nextToken(); if (dirPattern != null && templateName.startsWith(dirPattern)) { templateName = templateName.substring(dirPattern.length()); } if (rootDir != null) { // If it is not an absolute path and we were given a root directory, // prepend it. templateName = rootDir + templateName; } if (!st.hasMoreTokens()) { throw new IllegalArgumentException("No class name found in " + templateMapFile + " on line " + lineNumber + ": " + line); } String className = st.nextToken(); EscapeMode escapeMode; if (!st.hasMoreTokens()) { escapeMode = EscapeMode.ESCAPE_NONE; } else { String escapeCmd = st.nextToken(); try { escapeMode = EscapeMode.computeEscapeMode(escapeCmd); } catch (JSilverAutoEscapingException e) { throw new IllegalArgumentException("Invalid escape mode found in " + templateMapFile + " on line " + lineNumber + ": " + escapeCmd); } } PrecompiledTemplateMapKey key = new PrecompiledTemplateMapKey(templateName, escapeMode); templateMap.put(key, className); } @VisibleForTesting protected Reader getMapFileReader(String templateMapFile) throws IOException { ClassLoader classLoader = getClass().getClassLoader(); InputStream in = classLoader.getResourceAsStream(templateMapFile); if (in == null) { throw new FileNotFoundException("Unable to locate resource: " + templateMapFile); } return new InputStreamReader(in, "UTF-8"); } } src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapKey.java0100644 0000000 0000000 00000004257 13662126234 026666 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.precompiler; import com.google.clearsilver.jsilver.autoescape.EscapeMode; /** * Object to use as key when looking up precompiled templates. It encapsulates the template name, as * well as the {@link EscapeMode} for which the template was compiled. */ public class PrecompiledTemplateMapKey { private final Object templateName; private final EscapeMode escapeMode; private final String toStringName; public PrecompiledTemplateMapKey(Object templateName, EscapeMode escapeMode) { this.templateName = templateName; this.escapeMode = escapeMode; if (escapeMode == EscapeMode.ESCAPE_NONE) { toStringName = templateName.toString(); } else { toStringName = templateName.toString() + "_" + escapeMode.getEscapeCommand(); } } public boolean equals(Object o) { if (o == this) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PrecompiledTemplateMapKey that = (PrecompiledTemplateMapKey) o; return templateName.equals(that.templateName) && (escapeMode == that.escapeMode); } public int hashCode() { int hash = 17; hash = 31 * hash + templateName.hashCode(); hash = 31 * hash + escapeMode.hashCode(); return hash; } /** * String representation of key. If the template was auto escaped, it appends the * {@link EscapeMode} to the template name. * */ public String toString() { return toStringName; } /** * Return the escape mode used for this template. */ public EscapeMode getEscapeMode() { return escapeMode; } } src/com/google/clearsilver/jsilver/precompiler/compile_cs0100644 0000000 0000000 00000005165 13662126234 023014 0ustar000000000 0000000 # -*- mode: python; -*- # Copyright 2008 Google Inc. All Rights Reserved. # Use with # subinclude('//java/com/google/clearsilver/jsilver/precompiler:compile_cs') """compile_cs build target compile_cs(name, srcs) This rule produces generated Java source code that represents JSilver Template classes for rendering the given source CS files It'll output one .java file for each .cs file. Arguments * name: A unique name for this rule. (Name; required) * srcs: The list of cs files to pass to the code generator. (List of files, required) """ def compile_cs(name, srcs, mode='none'): if not srcs: raise BadRule(None, '%s: srcs is empty' % name) if mode == 'none': suffix = '.java' else: suffix = '_' + mode + '.java' gen_java_files = [ file.replace('.cs', suffix) for file in srcs] input_file = 'gen_cs_' + name + '.in' output_file = 'gen_cs_' + name + '.out' map_file = name + '.map' genrule(name = 'gen_cs_' + name, srcs = srcs, outs = [ map_file ] + gen_java_files, deps = [ '//java/com/google/clearsilver/jsilver/precompiler:BatchCompiler', ], cmd = ( 'echo "$(SRCS)" > $(@D)/' + input_file + ' && ' 'echo "$(OUTS)" > $(@D)/' + output_file + ' && ' '//java/com/google/clearsilver/jsilver/precompiler:BatchCompiler ' '--src_list_file=$(@D)/' + input_file + ' ' '--out_list_file=$(@D)/' + output_file + ' ' '--escape_mode=' + mode ) ) java_library(name = name, srcs = gen_java_files, resources = [ map_file ], deps = [ '//java/com/google/clearsilver/jsilver/compiler' ] ) """join_compiled_cs build target join_compiled_cs(name, deps) This rule merges multiple compile_cs output libraries and maps into one Java library and one map file that will be included as a system resource and can be read into the binary that wants to load the compiled template classes. Arguments * name: A unique name for this rule. (Name; required) * deps: The list of compile_cs BUILD targets to merge (List of labels, required) """ def join_compiled_cs(name, deps): if not deps: raise BadRule(None, '%s: deps is empty' % name) map_files = [ file + '.map' for file in deps] joined_map_file = name + '.map' genrule(name = 'gen_' + joined_map_file, srcs = map_files, outs = [ joined_map_file ], cmd = ('cat $(SRCS) > $@') ) java_library(name = name, resources = [ joined_map_file ], deps = deps) src/com/google/clearsilver/jsilver/resourceloader/0040755 0000000 0000000 00000000000 13662126234 021445 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/resourceloader/BaseResourceLoader.java0100644 0000000 0000000 00000003300 13662126234 026012 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import java.io.IOException; import java.io.Reader; /** * Implementations of ResourceLoader should extend this class rather than directly implement the * ResourceLoader interface - this allows changes to be made to the ResourceLoader interface whilst * retaining backwards compatibility with existing implementations. * * @see ResourceLoader */ public abstract class BaseResourceLoader implements ResourceLoader { @Override public void close(Reader reader) throws IOException { reader.close(); } /** * Default implementation returns the filename as the ResourceLoaders that subclass this class * tend to assume they are the only ResourceLoader in use. Or at least that the filename is the * only necessary form of uniqueness between two instances of this same ResourceLoader. */ @Override public Object getKey(String filename) { return filename; } /** * Default implementation does not check whether the resource has changed. */ @Override public Object getResourceVersionId(String filename) { return filename; } } src/com/google/clearsilver/jsilver/resourceloader/BufferedResourceLoader.java0100644 0000000 0000000 00000003226 13662126234 026671 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import java.io.Reader; import java.io.BufferedReader; /** * Base class for ResourceLoader implementations that require the Reader to be buffered (i.e. * there's IO latency involved). * * @see ResourceLoader */ public abstract class BufferedResourceLoader extends BaseResourceLoader { public static final int DEFAULT_BUFFER_SIZE = 1024; public static final String DEFAULT_CHARACTER_SET = "UTF-8"; private int bufferSize = DEFAULT_BUFFER_SIZE; private String characterSet = DEFAULT_CHARACTER_SET; /** * Subclasses can wrap a Reader in a BufferedReader by calling this method. */ protected Reader buffer(Reader reader) { return reader == null ? null : new BufferedReader(reader, bufferSize); } public int getBufferSize() { return bufferSize; } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } public void setCharacterSet(String characterSet) { this.characterSet = characterSet; } public String getCharacterSet() { return characterSet; } } src/com/google/clearsilver/jsilver/resourceloader/ClassLoaderResourceLoader.java0100644 0000000 0000000 00000004461 13662126234 027345 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; /** * Loads resources from classpath. * *

* For example, suppose the classpath contains: * *

 * com/foo/my-template.cs
 * com/foo/subdir/another-template.cs
 * 
* *

* You can access the resources like this: * *

 * ResourceLoader loader =
 *     new ClassPathResourceLoader(getClassLoader(), "com/foo");
 * loader.open("my-template.cs");
 * loader.open("subdir/my-template.cs");
 * 
* * @see ResourceLoader * @see ClassResourceLoader */ public class ClassLoaderResourceLoader extends BufferedResourceLoader { private final ClassLoader classLoader; private String basePath; public ClassLoaderResourceLoader(ClassLoader classLoader, String basePath) { this.classLoader = classLoader; this.basePath = basePath; } public ClassLoaderResourceLoader(ClassLoader classLoader) { this(classLoader, "."); } @Override public Reader open(String name) throws IOException { String path = basePath + '/' + name; InputStream stream = classLoader.getResourceAsStream(path); return stream == null ? null : buffer(new InputStreamReader(stream, getCharacterSet())); } @Override public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException { Reader reader = open(name); if (reader == null) { throw new JSilverTemplateNotFoundException("No class loader resource '" + name + "' in '" + basePath + "'"); } else { return reader; } } } src/com/google/clearsilver/jsilver/resourceloader/ClassResourceLoader.java0100644 0000000 0000000 00000005110 13662126234 026206 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; /** * Loads resources from classpath, alongside a given class. * *

For example, suppose the classpath contains: *

 * com/foo/SomeThing.class
 * com/foo/my-template.cs
 * com/foo/subdir/another-template.cs
 * 
* *

You can access the resources in the class's package like this: *

 * ResourceLoader loader = new ClassResourceLoader(SomeThing.class);
 * loader.open("my-template.cs");
 * loader.open("subdir/my-template.cs");
 * 
* Or by using a relative path: *
 * ResourceLoader loader = new ClassResourceLoader(Something.class, "subdir");
 * loader.open("my-template.cs");
 * 
* * @see ResourceLoader * @see ClassLoaderResourceLoader */ public class ClassResourceLoader extends BufferedResourceLoader { private final Class cls; private final String basePath; public ClassResourceLoader(Class cls) { this.cls = cls; this.basePath = "/" + cls.getPackage().getName().replace('.', '/'); } /** * Load resources from the given subdirectory {@code basePath}, * relative to the .class file of {@code cls}. */ public ClassResourceLoader(Class cls, String basePath) { this.cls = cls; this.basePath = basePath; } @Override public Reader open(String name) throws IOException { InputStream stream = cls.getResourceAsStream(basePath + '/' + name); return stream == null ? null : buffer(new InputStreamReader(stream, getCharacterSet())); } @Override public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException { Reader reader = open(name); if (reader == null) { throw new JSilverTemplateNotFoundException("No '" + name + "' as class resource of " + cls.getName()); } else { return reader; } } } src/com/google/clearsilver/jsilver/resourceloader/CompositeResourceLoader.java0100644 0000000 0000000 00000007020 13662126234 027105 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException; import java.io.FilterReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; /** * ResourceLoader composed of other ResourceLoaders. When a resource is loaded, it will search * through each ResourceLoader until it finds something. * * @see ResourceLoader */ public class CompositeResourceLoader implements ResourceLoader { private final List loaders = new ArrayList(); public CompositeResourceLoader(Iterable loaders) { for (ResourceLoader loader : loaders) { add(loader); } } public CompositeResourceLoader(ResourceLoader... loaders) { for (ResourceLoader loader : loaders) { add(loader); } } public void add(ResourceLoader loader) { loaders.add(loader); } public Reader open(String name) throws IOException { for (ResourceLoader loader : loaders) { Reader reader = loader.open(name); if (reader != null) { return new ReaderTracer(reader, loader); } } return null; } @Override public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException { Reader reader = open(name); if (reader == null) { throw new JSilverTemplateNotFoundException(name); } else { return reader; } } public void close(Reader reader) throws IOException { if (!(reader instanceof ReaderTracer)) { throw new IllegalArgumentException("I can't close a reader I didn't open."); } reader.close(); } /** * We return the filename as the key of uniqueness as we assume that if this * CompositeResourceLoader is in use, then there won't be another ResourceLoader that we are * competing against. If we did need to worry about it we would want to prepend the key from * above. */ @Override public Object getKey(String filename) { return filename; } /** * Return the first non-null version identifier found among the ResourceLoaders, using the same * search order as {@link #open(String)}. */ @Override public Object getResourceVersionId(String filename) { for (ResourceLoader loader : loaders) { Object currentKey = loader.getResourceVersionId(filename); if (currentKey != null) { return currentKey; } } return null; } /** * Wraps a reader, associating it with the original ResourceLoader - this is necessary so when * close() is called, we delegate back to original ResourceLoader. */ private static class ReaderTracer extends FilterReader { private final ResourceLoader originalLoader; public ReaderTracer(Reader in, ResourceLoader originalLoader) { super(in); this.originalLoader = originalLoader; } public void close() throws IOException { originalLoader.close(in); } } } src/com/google/clearsilver/jsilver/resourceloader/FileSystemResourceLoader.java0100644 0000000 0000000 00000005177 13662126234 027242 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.Reader; /** * Loads resources from a directory. * * @see ResourceLoader */ public class FileSystemResourceLoader extends BufferedResourceLoader { private final File rootDir; public FileSystemResourceLoader(File rootDir) { this.rootDir = rootDir; } public FileSystemResourceLoader(String rootDir) { this(new File(rootDir)); } @Override public Reader open(String name) throws IOException { File file = new File(rootDir, name); // Check for non-directory rather than is-file so that reads from // e.g. pipes work. if (file.exists() && !file.isDirectory() && file.canRead()) { return buffer(new InputStreamReader(new FileInputStream(file), getCharacterSet())); } else { return null; } } @Override public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException { Reader reader = open(name); if (reader == null) { throw new JSilverTemplateNotFoundException("No file '" + name + "' inside directory '" + rootDir + "'"); } else { return reader; } } /** * Some applications, e.g. online help, need to know when a file has changed due to a symlink * modification hence the use of {@link File#getCanonicalFile()}, if possible. */ @Override public Object getResourceVersionId(String filename) { File file = new File(rootDir, filename); // Check for non-directory rather than is-file so that reads from // e.g. pipes work. if (file.exists() && !file.isDirectory() && file.canRead()) { String fullPath; try { fullPath = file.getCanonicalPath(); } catch (IOException e) { fullPath = file.getAbsolutePath(); } return String.format("%s@%s", fullPath, file.lastModified()); } else { return null; } } } src/com/google/clearsilver/jsilver/resourceloader/InMemoryResourceLoader.java0100644 0000000 0000000 00000003732 13662126234 026710 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * ResourceLoader that pulls all items from memory. This is particularly useful for small templates * that can be embedded in code (e.g. in unit tests). * * Content needs to be stored first using the {@link #store(String, String)} method. * * @see ResourceLoader */ public class InMemoryResourceLoader extends BaseResourceLoader { private ConcurrentMap items = new ConcurrentHashMap(); @Override public Reader open(String name) throws IOException { String content = items.get(name); return content == null ? null : new StringReader(content); } @Override public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException { Reader reader = open(name); if (reader == null) { throw new JSilverTemplateNotFoundException(name); } else { return reader; } } public void store(String name, String contents) { items.put(name, contents); } public void remove(String name) { items.remove(name); } public ConcurrentMap getItems() { return items; } } src/com/google/clearsilver/jsilver/resourceloader/ResourceLoader.java0100644 0000000 0000000 00000007403 13662126234 025227 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.resourceloader; import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException; import java.io.IOException; import java.io.Reader; /** * Loads resources, from somewhere. * * This is a service provider interface (SPI) for JSilver. Users of JSilver can easily create their * own implementations. However, it is recommended that new implementations don't implement this * interface directly, but instead extends {@link BaseResourceLoader}. This allows API changes to be * made to JSilver that maintain compatibility with existing ResourceLoader implementations. * * @see BaseResourceLoader * @see InMemoryResourceLoader * @see FileSystemResourceLoader * @see ClassLoaderResourceLoader * @see ClassResourceLoader */ public interface ResourceLoader { /** * Open a resource. If this resource is not found, null should be returned. * * The caller of this method is guaranteed to call {@link #close(Reader)} when done with the * reader. * * @param name the name of the resource * @return Reader, or null if not found. * @throws IOException if resource fails to open */ Reader open(String name) throws IOException; /** * Open a resource or throw an exception if no such resource is found. * * The caller of this method is guaranteed to call {@link #close(Reader)} when done with the * reader. * * @param name the name of the resource * @return Reader, or null if not found. * @throws JSilverTemplateNotFoundException if resource is not found * @throws IOException if resource fails to open */ Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException; /** * Close the reader. Allows ResourceLoader to perform any additional clean up. * * @param reader the reader to close * @throws IOException if reader fasils to close */ void close(Reader reader) throws IOException; /** * Returns an object that can be used to uniquely identify the file corresponding to the given * file name in the context of this ResourceLoader. (e.g. ordered list of directories + filename, * or absolute file path.). * * @param filename the name we want to identify * @return unique identifier */ Object getKey(String filename); /** * Returns an object that can be used to identify when a resource has changed. This key should be * based on some piece(s) of metadata that strongly indicates the resource has changed, for * example a file's last modified time. Since the object is expected to be used as part of a cache * key, it should be immutable and implement {@link Object#equals(Object)} and * {@link Object#hashCode()} . * * If the ResourceLoader does not or cannot compute a version identifier then it is sufficient to * always return the same Object, e.g. the resource name. Null, however, should only be returned * if a call to {@link #open(String)} would also return null. * * @param name the name of the resource to check for resources * @return unique identifier for the current version of the resource or null if the resource * cannot be found */ Object getResourceVersionId(String name); } src/com/google/clearsilver/jsilver/syntax/0040755 0000000 0000000 00000000000 13662126234 017755 5ustar000000000 0000000 src/com/google/clearsilver/jsilver/syntax/AutoEscaper.java0100644 0000000 0000000 00000031423 13662126234 023033 0ustar000000000 0000000 /* * Copyright (C) 2010 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.clearsilver.jsilver.syntax; import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAltCommand; import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand; import com.google.clearsilver.jsilver.syntax.node.ACallCommand; import com.google.clearsilver.jsilver.syntax.node.AContentTypeCommand; import com.google.clearsilver.jsilver.syntax.node.ACsOpenPosition; import com.google.clearsilver.jsilver.syntax.node.ADataCommand; import com.google.clearsilver.jsilver.syntax.node.ADefCommand; import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand; import com.google.clearsilver.jsilver.syntax.node.AEvarCommand; import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand; import com.google.clearsilver.jsilver.syntax.node.AIfCommand; import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALvarCommand; import com.google.clearsilver.jsilver.syntax.node.ANameCommand; import com.google.clearsilver.jsilver.syntax.node.AStringExpression; import com.google.clearsilver.jsilver.syntax.node.AUvarCommand; import com.google.clearsilver.jsilver.syntax.node.AVarCommand; import com.google.clearsilver.jsilver.syntax.node.Node; import com.google.clearsilver.jsilver.syntax.node.PCommand; import com.google.clearsilver.jsilver.syntax.node.PPosition; import com.google.clearsilver.jsilver.syntax.node.Start; import com.google.clearsilver.jsilver.syntax.node.TCsOpen; import com.google.clearsilver.jsilver.syntax.node.TString; import com.google.clearsilver.jsilver.syntax.node.Token; /** * Run a context parser (currently only HTML parser) over the AST, determine nodes that need * escaping, and apply the appropriate escaping command to those nodes. The parser is fed literal * data (from DataCommands), which it uses to track the context. When variables (e.g. VarCommand) * are encountered, we query the parser for its current context, and apply the appropriate escaping * command. */ public class AutoEscaper extends DepthFirstAdapter { private AutoEscapeContext autoEscapeContext; private boolean skipAutoEscape; private final EscapeMode escapeMode; private final String templateName; private boolean contentTypeCalled; /** * Create an AutoEscaper, which will apply the specified escaping mode. If templateName is * non-null, it will be used while displaying error messages. * * @param mode * @param templateName */ public AutoEscaper(EscapeMode mode, String templateName) { this.templateName = templateName; if (mode.equals(EscapeMode.ESCAPE_NONE)) { throw new JSilverAutoEscapingException("AutoEscaper called when no escaping is required", templateName); } escapeMode = mode; if (mode.isAutoEscapingMode()) { autoEscapeContext = new AutoEscapeContext(mode, templateName); skipAutoEscape = false; } else { autoEscapeContext = null; } } /** * Create an AutoEscaper, which will apply the specified escaping mode. When possible, use * #AutoEscaper(EscapeMode, String) instead. It specifies the template being auto escaped, which * is useful when displaying error messages. * * @param mode */ public AutoEscaper(EscapeMode mode) { this(mode, null); } @Override public void caseStart(Start start) { if (!escapeMode.isAutoEscapingMode()) { // For an explicit EscapeMode like {@code EscapeMode.ESCAPE_HTML}, we // do not need to parse the rest of the tree. Instead, we just wrap the // entire tree in a node. handleExplicitEscapeMode(start); } else { AutoEscapeContext.AutoEscapeState startState = autoEscapeContext.getCurrentState(); // call super.caseStart, which will make us visit the rest of the tree, // so we can determine the appropriate escaping to apply for each // variable. super.caseStart(start); AutoEscapeContext.AutoEscapeState endState = autoEscapeContext.getCurrentState(); if (!autoEscapeContext.isPermittedStateChangeForIncludes(startState, endState)) { // If template contains a content-type command, the escaping context // was intentionally changed. Such a change in context is fine as long // as the current template is not included inside another. There is no // way to verify that the template is not an include template however, // so ignore the error and depend on developers doing the right thing. if (contentTypeCalled) { return; } // We do not permit templates to end in a different context than they start in. // This is so that an included template does not modify the context of // the template that includes it. throw new JSilverAutoEscapingException("Template starts in context " + startState + " but ends in different context " + endState, templateName); } } } private void handleExplicitEscapeMode(Start start) { AStringExpression escapeExpr = new AStringExpression(new TString("\"" + escapeMode.getEscapeCommand() + "\"")); PCommand node = start.getPCommand(); AEscapeCommand escape = new AEscapeCommand(new ACsOpenPosition(new TCsOpen("