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.java 0100644 0000000 0000000 00000007713 13662126234 022541 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/adaptor/JCs.java 0100644 0000000 0000000 00000012774 13662126234 021413 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000015667 13662126234 021553 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000006477 13662126234 023645 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000012146 13662126234 024450 0 ustar 00 0000000 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 extends K, ? extends V> m) {
for (Map.Entry extends K, ? extends V> 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.java 0100644 0000000 0000000 00000014755 13662126234 025166 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/autoescape/AutoEscapeContext.java 0100644 0000000 0000000 00000046376 13662126234 025036 0 ustar 00 0000000 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/html
* HtmlParser.Mode.HTML
*
*
* text/plain
* HtmlParser.Mode.HTML
*
*
* application/javascript
* HtmlParser.Mode.JS
*
*
* application/json
* HtmlParser.Mode.JS
*
*
* text/javascript
* HtmlParser.Mode.JS
*
*
* text/css
* HtmlParser.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.java 0100644 0000000 0000000 00000002435 13662126234 025031 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000011704 13662126234 023430 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/compatibility/ClearsilverRenderer.java 0100644 0000000 0000000 00000011335 13662126234 026105 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/compiler/BaseCompiledTemplate.java 0100644 0000000 0000000 00000025646 13662126234 025141 0 ustar 00 0000000 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 extends Data> 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.java 0100644 0000000 0000000 00000016607 13662126234 025151 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000031062 13662126234 024517 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000033040 13662126234 025312 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001775 13662126234 026547 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000034026 13662126234 024047 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000017455 13662126234 024354 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000015315 13662126234 024354 0 ustar 00 0000000 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 extends JavaFileObject> diagnostic : diagnostics) {
message.append(diagnostic).append('\n');
}
message.append("------ ------\n");
throw new JSilverCompilationException(message.toString(), cause);
}
}
src/com/google/clearsilver/jsilver/compiler/TemplateTranslator.java 0100644 0000000 0000000 00000100276 13662126234 024734 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000012564 13662126234 024710 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/data/AbstractData.java 0100644 0000000 0000000 00000010153 13662126234 022535 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000014010 13662126234 022321 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000020117 13662126234 021052 0 ustar 00 0000000 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 extends Data> 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.java 0100644 0000000 0000000 00000011602 13662126234 022416 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002745 13662126234 022411 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001570 13662126234 022361 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000026127 13662126234 023733 0 ustar 00 0000000 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 extends Data> 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.java 0100644 0000000 0000000 00000011301 13662126234 023357 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000016070 13662126234 022654 0 ustar 00 0000000 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 extends Data> iterator;
DelegatedIterator(Iterator extends Data> 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 extends Data> 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.java 0100644 0000000 0000000 00000005232 13662126234 022725 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000006053 13662126234 023574 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001631 13662126234 025521 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000044047 13662126234 022663 0 ustar 00 0000000 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 extends Data> 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.java 0100644 0000000 0000000 00000055621 13662126234 022541 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001536 13662126234 025152 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004416 13662126234 021441 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001470 13662126234 022766 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002644 13662126234 024357 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000012032 13662126234 023007 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000010336 13662126234 022437 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000007001 13662126234 023366 0 ustar 00 0000000 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 extends Data> 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/examples/basic/ 0040755 0000000 0000000 00000000000 13662126234 021326 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/examples/basic/HelloWorld.java 0100644 0000000 0000000 00000002542 13662126234 024244 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003171 13662126234 023565 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003037 13662126234 024407 0 ustar 00 0000000 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.cs 0100644 0000000 0000000 00000000107 13662126234 024100 0 ustar 00 0000000 0000000 Hello .
How are you today?
src/com/google/clearsilver/jsilver/examples/basic/iterate.cs 0100644 0000000 0000000 00000000215 13662126234 023305 0 ustar 00 0000000 0000000 Query :
Results:
--->
src/com/google/clearsilver/jsilver/exceptions/ 0040755 0000000 0000000 00000000000 13662126234 020610 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/exceptions/ExceptionUtil.java 0100644 0000000 0000000 00000002272 13662126234 024247 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003517 13662126234 027216 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000005650 13662126234 026531 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001647 13662126234 024715 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001677 13662126234 025150 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001547 13662126234 027140 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001603 13662126234 030056 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/EscapingFunction.java 0100644 0000000 0000000 00000001421 13662126234 024534 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001714 13662126234 023067 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002552 13662126234 024607 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000011120 13662126234 024610 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001425 13662126234 025213 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001441 13662126234 023371 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/bundles/ClearSilverCompatibleFunctions.java 0100644 0000000 0000000 00000010547 13662126234 031046 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000007137 13662126234 025532 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/escape/HtmlEscapeFunction.java 0100644 0000000 0000000 00000006406 13662126234 026300 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004545 13662126234 025752 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004436 13662126234 027636 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002121 13662126234 026274 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000007403 13662126234 027154 0 ustar 00 0000000 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:
*
* Scan block for contiguous unescaped sequences
* Append unescaped sequences to output
* Append escaped string to output (if found)
* 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.java 0100644 0000000 0000000 00000005616 13662126234 026476 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003563 13662126234 026137 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/html/BaseUrlValidateFunction.java 0100644 0000000 0000000 00000006565 13662126234 026774 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004601 13662126234 026637 0 ustar 00 0000000 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:
*
* In an '@import url("URL");' statement
* 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:
*
* CSS 2.1 URLs: http://www.w3.org/TR/CSS21/syndata.html#url
* 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.java 0100644 0000000 0000000 00000015410 13662126234 025700 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002624 13662126234 027016 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000020451 13662126234 025524 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/numeric/AbsFunction.java 0100644 0000000 0000000 00000002275 13662126234 025162 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002354 13662126234 025200 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002355 13662126234 025177 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/operators/AddFunction.java 0100644 0000000 0000000 00000002447 13662126234 025522 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002125 13662126234 025525 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002124 13662126234 026226 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002115 13662126234 026071 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002035 13662126234 026302 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002130 13662126234 026410 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002141 13662126234 027703 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002125 13662126234 025731 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002136 13662126234 027224 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002124 13662126234 026261 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002126 13662126234 026643 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002121 13662126234 026547 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002036 13662126234 025564 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002142 13662126234 027035 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002146 13662126234 027420 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002106 13662126234 026424 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002151 13662126234 030075 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002124 13662126234 025402 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002343 13662126234 026614 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/string/CrcFunction.java 0100644 0000000 0000000 00000003417 13662126234 025027 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002677 13662126234 025207 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002245 13662126234 025537 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003765 13662126234 025365 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/functions/structure/FirstFunction.java 0100644 0000000 0000000 00000002676 13662126234 026147 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002673 13662126234 025760 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003231 13662126234 025724 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002715 13662126234 026654 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/interpreter/ExpressionEvaluator.java 0100644 0000000 0000000 00000021613 13662126234 025657 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000007452 13662126234 025111 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000006256 13662126234 025624 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004751 13662126234 026751 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003513 13662126234 026235 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000001636 13662126234 025335 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004606 13662126234 027015 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003462 13662126234 024742 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000057255 13662126234 025647 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000007377 13662126234 024721 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/output/InstanceOutputBufferProvider.java 0100644 0000000 0000000 00000002114 13662126234 026457 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000002011 13662126234 024766 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003340 13662126234 027077 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateLoader.java 0100644 0000000 0000000 00000012141 13662126234 026675 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000012510 13662126234 030127 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004257 13662126234 026666 0 ustar 00 0000000 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_cs 0100644 0000000 0000000 00000005165 13662126234 023014 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/resourceloader/BaseResourceLoader.java 0100644 0000000 0000000 00000003300 13662126234 026012 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003226 13662126234 026671 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000004461 13662126234 027345 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000005110 13662126234 026206 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000007020 13662126234 027105 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000005177 13662126234 027242 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000003732 13662126234 026710 0 ustar 00 0000000 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.java 0100644 0000000 0000000 00000007403 13662126234 025227 0 ustar 00 0000000 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 5 ustar 00 0000000 0000000 src/com/google/clearsilver/jsilver/syntax/AutoEscaper.java 0100644 0000000 0000000 00000031423 13662126234 023033 0 ustar 00 0000000 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("