jsilver-1.0.0.dfsg.orig/0000755000175000017500000000000011767454573014074 5ustar chrischrisjsilver-1.0.0.dfsg.orig/build.xml0000644000175000017500000000316111427052602015672 0ustar chrischris jsilver-1.0.0.dfsg.orig/pom.xml0000644000175000017500000003434011470535760015402 0ustar chrischris 4.0.0 com.google.jsilver jsilver 1.0.0 jar JSilver Pure-Java implementation of Clearsilver templating engine. http://code.google.com/p/jsilver/ 2010 Google http://www.google.com/ The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo Google-Code Issue Management http://code.google.com/p/jsilver/issues/ scm:svn:http://jsilver.googlecode.com/svn/tags/jsilver-1.0.0 scm:svn:https://jsilver.googlecode.com/svn/tags/jsilver-1.0.0 scm:svn:https://jsilver.googlecode.com/svn/tags/jsilver-1.0.0 David Beaumont bjdodson Ben Dodson bjdodson@gmail.com sonatype-nexus-snapshots Sonatype Nexus Snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-staging Nexus Release Repository https://oss.sonatype.org/service/local/staging/deploy/maven2/ UTF-8 UTF-8 1.5 1.5 yyyy-MM-dd HH:mm:ssZ r${buildNumber}; ${maven.build.timestamp} build ${basedir}/src ${project.build.directory}/classes maven-antrun-plugin generate-sources run org.codehaus.mojo exec-maven-plugin 1.2 generate-sources exec java -jar sablecc/sablecc.jar src/com/google/clearsilver/jsilver/syntax/jsilver.sablecc -d ${basedir}/build/gen ${basedir}/build/gen maven-resources-plugin 2.4.3 copy-resources compile copy-resources ${basedir}/build/classes ${basedir}/build/gen **/*.dat org.apache.maven.plugins maven-compiler-plugin 2.3.2 ${javac.src.version} ${javac.target.version} ${project.build.sourceEncoding} org.apache.maven.plugins maven-surefire-plugin 2.6 org.apache.maven.plugins maven-jar-plugin 2.3.1 true true ${implementation.build} ${javac.src.version} ${javac.target.version} org.apache.maven.plugins maven-release-plugin 2.1 forked-path -Prelease false ${basedir} META-INF LICENSE NOTICE ${basedir}/res release org.codehaus.mojo buildnumber-maven-plugin 1.0-beta-4 validate create true true org.apache.maven.plugins maven-source-plugin 2.1.2 attach-sources jar-no-fork true true ${implementation.build} ${javac.src.version} ${javac.target.version} org.apache.maven.plugins maven-javadoc-plugin 2.7 attach-javadocs jar true true ${implementation.build} ${javac.src.version} ${javac.target.version} org.apache.maven.plugins maven-gpg-plugin 1.1 sign-artifacts verify sign com.google.guava guava r06 junit junit 4.8.2 test jsilver-1.0.0.dfsg.orig/src/0000755000175000017500000000000011767454572014662 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/0000755000175000017500000000000011767454572015440 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/0000755000175000017500000000000011767454572016714 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/0000755000175000017500000000000011767454573022312 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/HtmlParser.java0000644000175000017500000002542711427052602025224 0ustar chrischris/* * 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.streamhtmlparser; /** * Methods exposed for HTML parsing of text to facilitate implementation * of Automatic context-aware escaping. The HTML parser also embeds a * Javascript parser for processing Javascript fragments. In the future, * it will also embed other specific parsers and hence most likely remain * the main interface to callers of this package. * *

Note: These are the exact methods exposed in the original C++ Parser. The * names are simply modified to conform to Java. */ public interface HtmlParser extends Parser { /** * The Parser Mode requested for parsing a given template. * Currently we support: *

*/ public enum Mode { HTML, JS, CSS, HTML_IN_TAG } /** * Indicates the type of HTML attribute that the parser is currently in or * {@code NONE} if the parser is not currently in an attribute. * {@code URI} is for attributes taking a URI such as "href" and "src". * {@code JS} is for attributes taking javascript such as "onclick". * {@code STYLE} is for the "style" attribute. * All other attributes fall under {@code REGULAR}. * * Returned by {@link HtmlParser#getAttributeType()} */ public enum ATTR_TYPE { NONE, REGULAR, URI, JS, STYLE } /** * All the states in which the parser can be. These are external states. * The parser has many more internal states that are not exposed and which * are instead mapped to one of these external ones. * {@code STATE_TEXT} the parser is in HTML proper. * {@code STATE_TAG} the parser is inside an HTML tag name. * {@code STATE_COMMENT} the parser is inside an HTML comment. * {@code STATE_ATTR} the parser is inside an HTML attribute name. * {@code STATE_VALUE} the parser is inside an HTML attribute value. * {@code STATE_JS_FILE} the parser is inside javascript code. * {@code STATE_CSS_FILE} the parser is inside CSS code. * *

All these states map exactly to those exposed in the C++ (original) * version of the HtmlParser. */ public final static ExternalState STATE_TEXT = new ExternalState("STATE_TEXT"); public final static ExternalState STATE_TAG = new ExternalState("STATE_TAG"); public final static ExternalState STATE_COMMENT = new ExternalState("STATE_COMMENT"); public final static ExternalState STATE_ATTR = new ExternalState("STATE_ATTR"); public final static ExternalState STATE_VALUE = new ExternalState("STATE_VALUE"); public final static ExternalState STATE_JS_FILE = new ExternalState("STATE_JS_FILE"); public final static ExternalState STATE_CSS_FILE = new ExternalState("STATE_CSS_FILE"); /** * Returns {@code true} if the parser is currently processing Javascript. * Such is the case if and only if, the parser is processing an attribute * that takes Javascript, a Javascript script block or the parser * is (re)set with {@link Mode#JS}. * * @return {@code true} if the parser is processing Javascript, * {@code false} otherwise */ public boolean inJavascript(); /** * Returns {@code true} if the parser is currently processing * a Javascript litteral that is quoted. The caller will typically * invoke this method after determining that the parser is processing * Javascript. Knowing whether the element is quoted or not helps * determine which escaping to apply to it when needed. * * @return {@code true} if and only if the parser is inside a quoted * Javascript literal */ public boolean isJavascriptQuoted(); /** * Returns {@code true} if and only if the parser is currently within * an attribute, be it within the attribute name or the attribute value. * * @return {@code true} if and only if inside an attribute */ public boolean inAttribute(); /** * Returns {@code true} if and only if the parser is currently within * a CSS context. A CSS context is one of the below: *

* * @return {@code true} if and only if the parser is inside CSS */ public boolean inCss(); /** * Returns the type of the attribute that the parser is in * or {@code ATTR_TYPE.NONE} if we are not parsing an attribute. * The caller will typically invoke this method after determining * that the parser is processing an attribute. * *

This is useful to determine which escaping to apply based * on the type of value this attribute expects. * * @return type of the attribute * @see HtmlParser.ATTR_TYPE */ public ATTR_TYPE getAttributeType(); /** * Returns {@code true} if and only if the parser is currently within * an attribute value and that attribute value is quoted. * * @return {@code true} if and only if the attribute value is quoted */ public boolean isAttributeQuoted(); /** * Returns the name of the HTML tag if the parser is currently within one. * Note that the name may be incomplete if the parser is currently still * parsing the name. Returns an empty {@code String} if the parser is not * in a tag as determined by {@code getCurrentExternalState}. * * @return the name of the HTML tag or an empty {@code String} if we are * not within an HTML tag */ public String getTag(); /** * Returns the name of the HTML attribute the parser is currently processing. * If the parser is still parsing the name, then the returned name * may be incomplete. Returns an empty {@code String} if the parser is not * in an attribute as determined by {@code getCurrentExternalState}. * * @return the name of the HTML attribute or an empty {@code String} * if we are not within an HTML attribute */ public String getAttribute(); /** * Returns the value of an HTML attribute if the parser is currently * within one. If the parser is currently parsing the value, the returned * value may be incomplete. The caller will typically first determine * that the parser is processing a value by calling * {@code getCurrentExternalState}. * * @return the value, could be an empty {@code String} if the parser is not * in an HTML attribute value */ public String getValue(); /** * Returns the current position of the parser within the HTML attribute * value, zero being the position of the first character in the value. * The caller will typically first determine that the parser is * processing a value by calling {@link #getState()}. * * @return the index or zero if the parser is not processing a value */ public int getValueIndex(); /** * Returns {@code true} if and only if the current position of the parser is * at the start of a URL HTML attribute value. This is the case when the * following three conditions are all met: *

*

    *
  1. The parser is in an HTML attribute value. *
  2. The HTML attribute expects a URL, as determined by * {@link #getAttributeType()} returning {@code .ATTR_TYPE#URI}. *
  3. The parser has not yet seen any characters from that URL. *
* *

This method may be used by an Html Sanitizer or an Auto-Escape system * to determine whether to validate the URL for well-formedness and validate * the scheme of the URL (e.g. {@code HTTP}, {@code HTTPS}) is safe. * In particular, it is recommended to use this method instead of * checking that {@link #getValueIndex()} is {@code 0} to support attribute * types where the URL does not start at index zero, such as the * {@code content} attribute of the {@code meta} HTML tag. * * @return {@code true} if and only if the parser is at the start of the URL */ public boolean isUrlStart(); /** * Resets the state of the parser, allowing for reuse of the * {@code HtmlParser} object. * *

See the {@link HtmlParser.Mode} enum for information on all * the valid modes. * * @param mode is an enum representing the high-level state of the parser */ public void resetMode(HtmlParser.Mode mode); /** * A specialized directive to tell the parser there is some content * that will be inserted here but that it will not get to parse. Used * by the template system that may not be able to give some content * to the parser but wants it to know there typically will be content * inserted at that point. This is a hint used in corner cases within * parsing of HTML attribute names and values where content we do not * get to see could affect our parsing and alter our current state. * *

Returns {@code false} if and only if the parser encountered * a fatal error which prevents it from continuing further parsing. * *

Note: The return value is different from the C++ Parser which * always returns {@code true} but in my opinion makes more sense. * * @throws ParseException if an unrecoverable error occurred during parsing */ public void insertText() throws ParseException; /** * Returns the state the Javascript parser is in. * *

See {@link JavascriptParser} for more information on the valid * external states. The caller will typically first determine that the * parser is processing Javascript and then invoke this method to * obtain more fine-grained state information. * * @return external state of the javascript parser */ public ExternalState getJavascriptState(); } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/ExternalState.java0000644000175000017500000000502111427052602025712 0ustar chrischris/* * 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.streamhtmlparser; import com.google.common.base.Preconditions; /** * A representation of the parser state suitable for use by the caller * of the Parser. The meaning of each state and therefore which action * the caller should perform on that state is not self-evident. In particular, * it depends on which parser is used (currently {@link HtmlParser} and * {@link JavascriptParser}). For examples, you will have to look * at the Google Template System and ClearSilver * both of which support Auto-Escaping by interfacing with our parser * (using the parser written in C++). * *

The caller of the Parser will query for the current parser state at * points of interest during parsing of templates. Based on the parser's * current state as represented by this class, the caller can determine * the appropriate escaping to apply. * *

Note: Given this class is external-facing, I considered creating * an interface but it is not likely we'll ever need to add more flexibility * and the class is so simple, I figured it was not warranted. * * * @see HtmlParser * @see JavascriptParser */ public class ExternalState { private final String name; /** * Creates an {@code ExternalState} object. * * @param name the name to assign to that state * @see HtmlParser * @see JavascriptParser */ public ExternalState(String name) { Preconditions.checkNotNull(name); // Developer error if it happens. this.name = name; } /** * Returns the name of the object. The name is only needed * to provide human-readable information when debugging. * * @return the name of that object */ public String getName() { return name; } /** * Returns the string representation of this external state. * The details of this representation are subject to change. */ @Override public String toString() { return String.format("ExternalState: %s", name); } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/JavascriptParser.java0000644000175000017500000000273011427052602026416 0ustar chrischris/* * 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.streamhtmlparser; /** * Methods exposed for Javascript parsing of text to facilitate implementation * of Automatic context-aware escaping. This interface does not add * additional methods on top of {@code Parser} for the time being, * it simply exposes the states in which the Javascript parser may be in. * *

Note: These are the exact states exposed in the original C++ Parser. */ public interface JavascriptParser extends Parser { public static final ExternalState STATE_TEXT = new ExternalState("STATE_TEXT"); public static final ExternalState STATE_Q = new ExternalState("STATE_Q"); public static final ExternalState STATE_DQ = new ExternalState("STATE_DQ"); public static final ExternalState STATE_REGEXP = new ExternalState("STATE_REGEXP"); public static ExternalState STATE_COMMENT = new ExternalState("STATE_COMMENT"); } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/impl/0000755000175000017500000000000011767454572023252 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/impl/GenericParser.java0000644000175000017500000002223211427052602026624 0ustar chrischris/* * 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.streamhtmlparser.impl; import com.google.common.base.Preconditions; import com.google.streamhtmlparser.ExternalState; import com.google.streamhtmlparser.Parser; import com.google.streamhtmlparser.ParseException; import com.google.streamhtmlparser.util.HtmlUtils; import java.util.Map; /** * An implementation of the {@code Parser} interface that is common to both * {@code HtmlParser} and {@code JavascriptParser}. * *

Provides methods for parsing input and ensuring that all in-state, * entering-a-state and exiting-a-state callbacks are invoked as appropriate. * *

This class started as abstract but it was found better for testing to * make it instantiatable so that the parsing logic can be tested with dummy * state transitions. */ public class GenericParser implements Parser { protected final ParserStateTable parserStateTable; protected final Map intToExtStateTable; protected final InternalState initialState; protected InternalState currentState; protected int lineNumber; protected int columnNumber; protected GenericParser(ParserStateTable parserStateTable, Map intToExtStateTable, InternalState initialState) { this.parserStateTable = parserStateTable; this.intToExtStateTable = intToExtStateTable; this.initialState = initialState; this.currentState = initialState; this.lineNumber = 1; this.columnNumber = 1; } /** * Constructs a generic parser that is an exact copy of the * one given. Note that here too, data structures that do not * change are shallow-copied (parser state table and state mappings). * * @param aGenericParser the {@code GenericParser} to copy */ protected GenericParser(GenericParser aGenericParser) { parserStateTable = aGenericParser.parserStateTable; intToExtStateTable = aGenericParser.intToExtStateTable; initialState = aGenericParser.initialState; currentState = aGenericParser.currentState; lineNumber = aGenericParser.lineNumber; columnNumber = aGenericParser.columnNumber; } /** * Tell the parser to process the provided {@code String}. This is just a * convenience method that wraps over {@link Parser#parse(char)}. * @param input the {@code String} to parse * @throws ParseException if an unrecoverable error occurred during parsing */ @Override public void parse(String input) throws ParseException { for (int i = 0; i < input.length(); i++) parse(input.charAt(i)); } /** * Main loop for parsing of input. * *

Absent any callbacks defined, this function simply determines the * next state to switch to based on the ParserStateTable which is * derived from a state-machine configuration file in the original C++ parser. * *

However some states have specific callbacks defined which when * receiving specific characters may decide to overwrite the next state to * go to. Hence the next state is a function both of the main state table * in {@code ParserStateTable} as well as specific run-time information * from the callback functions. * *

Also note that the callbacks are called in a proper sequence, * first the exit-state one then the enter-state one and finally the * in-state one. Changing the order may result in a functional change. * * @param input the input character to parse (process) * @throws ParseException if an unrecoverable error occurred during parsing */ @Override public void parse(char input) throws ParseException { InternalState nextState = parserStateTable.getNextState(currentState, input); if (nextState == InternalState.INTERNAL_ERROR_STATE) { String errorMsg = String.format("Unexpected character '%s' in int_state '%s' " + "(ext_state '%s')", HtmlUtils.encodeCharForAscii(input), currentState.getName(), getState().getName()); currentState = InternalState.INTERNAL_ERROR_STATE; throw new ParseException(this, errorMsg); } if (currentState != nextState) { nextState = handleExitState(currentState, nextState, input); } if (currentState != nextState) { nextState = handleEnterState(nextState, nextState, input); } nextState = handleInState(nextState, input); currentState = nextState; record(input); columnNumber++; if (input == '\n') { lineNumber++; columnNumber = 1; } } /** * Return the current state of the parser. */ @Override public ExternalState getState() { if (!intToExtStateTable.containsKey(currentState)) { throw new NullPointerException("Did not find external state mapping " + "For internal state: " + currentState); } return intToExtStateTable.get(currentState); } /** * Reset the parser back to its initial default state. */ @Override public void reset() { currentState = initialState; lineNumber = 1; columnNumber = 1; } /** * Sets the current line number which is returned during error messages. */ @Override public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } /** * Returns the current line number. */ @Override public int getLineNumber() { return lineNumber; } /** * Sets the current column number which is returned during error messages. */ @Override public void setColumnNumber(int columnNumber) { this.columnNumber = columnNumber; } /** * Returns the current column number. */ @Override public int getColumnNumber() { return columnNumber; } InternalState getCurrentInternalState() { return currentState; } protected void setNextState(InternalState nextState) throws ParseException { Preconditions.checkNotNull(nextState); // Developer error if it triggers. /* We are not actually parsing hence providing * a null char to the event handlers. */ // TODO: Complicated logic to follow in C++ but clean it up. final char nullChar = '\0'; if (currentState != nextState) { nextState = handleExitState(currentState, nextState, nullChar); } if (currentState != nextState) { handleEnterState(nextState, nextState, nullChar); } currentState = nextState; } /** * Invoked when the parser enters a new state. * * @param currentState the current state of the parser * @param expectedNextState the next state according to the * state table definition * @param input the last character parsed * @return the state to change to, could be the same as the * {@code expectedNextState} provided * @throws ParseException if an unrecoverable error occurred during parsing */ protected InternalState handleEnterState(InternalState currentState, InternalState expectedNextState, char input) throws ParseException { return expectedNextState; } /** * Invoked when the parser exits a state. * * @param currentState the current state of the parser * @param expectedNextState the next state according to the * state table definition * @param input the last character parsed * @return the state to change to, could be the same as the * {@code expectedNextState} provided * @throws ParseException if an unrecoverable error occurred during parsing */ protected InternalState handleExitState(InternalState currentState, InternalState expectedNextState, char input) throws ParseException { return expectedNextState; } /** * Invoked for each character read when no state change occured. * * @param currentState the current state of the parser * @param input the last character parsed * @return the state to change to, could be the same as the * {@code expectedNextState} provided * @throws ParseException if an unrecoverable error occurred during parsing */ protected InternalState handleInState(InternalState currentState, char input) throws ParseException { return currentState; } /** * Perform some processing on the given character. Derived classes * may override this method in order to perform additional logic * on every processed character beyond the logic defined in * state transitions. * * @param input the input character to operate on */ protected void record(char input) { } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/impl/InternalState.java0000644000175000017500000000730311427052602026652 0ustar chrischris/* * 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.streamhtmlparser.impl; import com.google.common.base.Preconditions; import java.util.concurrent.atomic.AtomicInteger; /** * A very simple representation of the parser internal state. The state * contains a small integer identifier (from 1 to 255) to allow for * the implementation of a simple finite state machine. The name is * purely informational. * *

In order to eliminate the possibility that different states have * the same identifier, this class manages the idenitifiers themselves. * The HTML and Javascript parser states are managed elsewhere in different * "namespaces" hence will not clash and there is no current need for this * class to disambiguate them further. * *

The methods to create new InternalState instances are * package-scope only as they are only needed by HtmlParserImpl * and JavascriptParserImpl. */ class InternalState { // An InternalState to represent an error condition for all parsers. static final InternalState INTERNAL_ERROR_STATE = new InternalState(); // MAX_ID and FIRST_ID are only used for asserts against developer error. private static final int MAX_ID = 255; private static final int FIRST_ID = 1; private static AtomicInteger htmlStates = new AtomicInteger(FIRST_ID); private static AtomicInteger javascriptStates = new AtomicInteger(FIRST_ID); private final String name; private final int id; /** * @param name the {@code String} identifier for this state * @param id the integer identiifer for this state, guaranteed to be unique */ private InternalState(String name, int id) { Preconditions.checkNotNull(name); Preconditions.checkArgument(id >= FIRST_ID); Preconditions.checkArgument(id <= MAX_ID); this.name = name; this.id = id; } /** * Used only for the error state. Bypasses assert checks. */ private InternalState() { name = "InternalStateError"; id = 0; } /** * @return {@code String} name of that state. */ public String getName() { return name; } /** * @return {@code int} id of that state. */ public int getId() { return id; } /** * @return {@code String} representation of that object, the format * may change. */ @Override public String toString() { return String.format("InternalState: Name: %s; Id: %d", name, id); } /** * Obtain a new {@code InternalState} instance for the HTML parser. * * @param name a unique identifier for this state useful during debugging * @return a new {@code InternalState} object */ static InternalState getInstanceHtml(String name) { int htmlStateId = htmlStates.getAndIncrement(); return new InternalState(name, htmlStateId); } /** * Obtain a new InternalState instance for the Javascript parser. * * @param name A unique identifier for this state useful during debugging * @return a new {@code InternalState} object */ static InternalState getInstanceJavascript(String name) { int javascriptStateId = javascriptStates.getAndIncrement(); return new InternalState(name, javascriptStateId); } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/impl/HtmlParserImpl.java0000644000175000017500000007375111427052602027012 0ustar chrischris/* * 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.streamhtmlparser.impl; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.streamhtmlparser.ExternalState; import com.google.streamhtmlparser.HtmlParser; import com.google.streamhtmlparser.ParseException; import com.google.streamhtmlparser.util.CharacterRecorder; import com.google.streamhtmlparser.util.EntityResolver; import com.google.streamhtmlparser.util.HtmlUtils; import java.util.Map; /** * A custom specialized parser - ported from the main C++ version - used to * implement context-aware escaping of run-time data in web-application * templates. * *

This is the main class in the package. It implements the * {@code HtmlParser} interface. * *

This class is not thread-safe, in particular you cannot invoke any * state changing operations (such as {@code parse} from multiple threads * on the same object. * *

If you are looking at this class, chances are very high you are * implementing Auto-Escaping for a new template system. Please see the * landing page including a design document at * Auto-Escape Landing Page. */ public class HtmlParserImpl extends GenericParser implements HtmlParser { /* * Internal representation of the parser state, which is at a * finer-granularity than the external state as given to callers. * The relationship between InternalState and * ExternalState is a many-to-one relationship. */ private static final InternalState TEXT; private static final InternalState TAG_START; private static final InternalState TAG_NAME; private static final InternalState DECL_START; private static final InternalState DECL_BODY; private static final InternalState COM_OPEN; private static final InternalState COM_BODY; private static final InternalState COM_DASH; private static final InternalState COM_DASH_DASH; private static final InternalState PI; private static final InternalState PI_MAY_END; private static final InternalState TAG_SPACE; private static final InternalState TAG_CLOSE; private static final InternalState ATTR; private static final InternalState ATTR_SPACE; private static final InternalState VALUE; private static final InternalState VALUE_TEXT; private static final InternalState VALUE_Q_START; private static final InternalState VALUE_Q; private static final InternalState VALUE_DQ_START; private static final InternalState VALUE_DQ; private static final InternalState CDATA_COM_START; private static final InternalState CDATA_COM_START_DASH; private static final InternalState CDATA_COM_BODY; private static final InternalState CDATA_COM_DASH; private static final InternalState CDATA_COM_DASH_DASH; private static final InternalState CDATA_TEXT; private static final InternalState CDATA_LT; private static final InternalState CDATA_MAY_CLOSE; private static final InternalState JS_FILE; private static final InternalState CSS_FILE; static { TEXT = InternalState.getInstanceHtml("TEXT"); TAG_START = InternalState.getInstanceHtml("TAG_START"); TAG_NAME = InternalState.getInstanceHtml("TAG_NAME"); DECL_START = InternalState.getInstanceHtml("DECL_START"); DECL_BODY = InternalState.getInstanceHtml("DECL_BODY"); COM_OPEN = InternalState.getInstanceHtml("COM_OPEN"); COM_BODY = InternalState.getInstanceHtml("COM_BODY"); COM_DASH = InternalState.getInstanceHtml("COM_DASH"); COM_DASH_DASH = InternalState.getInstanceHtml("COM_DASH_DASH"); PI =InternalState.getInstanceHtml("PI"); PI_MAY_END = InternalState.getInstanceHtml("PI_MAY_END"); TAG_SPACE = InternalState.getInstanceHtml("TAG_SPACE"); TAG_CLOSE = InternalState.getInstanceHtml("TAG_CLOSE"); ATTR = InternalState.getInstanceHtml("ATTR"); ATTR_SPACE = InternalState.getInstanceHtml("ATTR_SPACE"); VALUE = InternalState.getInstanceHtml("VALUE"); VALUE_TEXT = InternalState.getInstanceHtml("VALUE_TEXT"); VALUE_Q_START = InternalState.getInstanceHtml("VALUE_Q_START"); VALUE_Q = InternalState.getInstanceHtml("VALUE_Q"); VALUE_DQ_START = InternalState.getInstanceHtml("VALUE_DQ_START"); VALUE_DQ = InternalState.getInstanceHtml("VALUE_DQ"); CDATA_COM_START = InternalState.getInstanceHtml("CDATA_COM_START"); CDATA_COM_START_DASH = InternalState.getInstanceHtml("CDATA_COM_START_DASH"); CDATA_COM_BODY = InternalState.getInstanceHtml("CDATA_COM_BODY"); CDATA_COM_DASH = InternalState.getInstanceHtml("CDATA_COM_DASH"); CDATA_COM_DASH_DASH = InternalState.getInstanceHtml("CDATA_COM_DASH_DASH"); CDATA_TEXT = InternalState.getInstanceHtml("CDATA_TEXT"); CDATA_LT = InternalState.getInstanceHtml("CDATA_LT"); CDATA_MAY_CLOSE = InternalState.getInstanceHtml("CDATA_MAY_CLOSE"); JS_FILE = InternalState.getInstanceHtml("JS_FILE"); CSS_FILE = InternalState.getInstanceHtml("CSS_FILE"); } private static final Map STATE_MAPPING = Maps.newHashMap(); static { initializeStateMapping(); } private static final ParserStateTable STATE_TABLE = new ParserStateTable(); static { initializeParserStateTable(); } private final CharacterRecorder tag; private final CharacterRecorder attr; private final CharacterRecorder value; private final CharacterRecorder cdataCloseTag; private final EntityResolver entityResolver; private final JavascriptParserImpl jsParser; private boolean insideJavascript; private int valueIndex; // True iff InsertText() was called at the start of a URL attribute value. private boolean textInsideUrlValue; /** * Creates an {@code HtmlParserImpl} object. * *

Both for performance reasons and to leverage code a state-flow machine * that is automatically generated from Python for multiple target * languages, this object uses a static {@code ParserStateTable} that * is read-only and obtained from the generated code in {@code HtmlParserFsm}. * That code also maintains the mapping from internal states * ({@code InternalState}) to external states ({@code ExternalState}). */ public HtmlParserImpl() { super(STATE_TABLE, STATE_MAPPING, TEXT); tag = new CharacterRecorder(); attr = new CharacterRecorder(); value = new CharacterRecorder(); cdataCloseTag = new CharacterRecorder(); entityResolver = new EntityResolver(); jsParser = new JavascriptParserImpl(); insideJavascript = false; valueIndex = 0; textInsideUrlValue = false; } /** * Creates an {@code HtmlParserImpl} that is a copy of the one provided. * * @param aHtmlParserImpl the {@code HtmlParserImpl} object to copy */ public HtmlParserImpl(HtmlParserImpl aHtmlParserImpl) { super(aHtmlParserImpl); tag = new CharacterRecorder(aHtmlParserImpl.tag); attr = new CharacterRecorder(aHtmlParserImpl.attr); value = new CharacterRecorder(aHtmlParserImpl.value); cdataCloseTag = new CharacterRecorder(aHtmlParserImpl.cdataCloseTag); entityResolver = new EntityResolver(aHtmlParserImpl.entityResolver); jsParser = new JavascriptParserImpl(aHtmlParserImpl.jsParser); insideJavascript = aHtmlParserImpl.insideJavascript; valueIndex = aHtmlParserImpl.valueIndex; textInsideUrlValue = aHtmlParserImpl.textInsideUrlValue; } @Override public boolean inJavascript() { return (insideJavascript && ( (getState() == STATE_VALUE) || (currentState == CDATA_TEXT) || (currentState == CDATA_COM_START) || (currentState == CDATA_COM_START_DASH) || (currentState == CDATA_COM_BODY) || (currentState == CDATA_COM_DASH) || (currentState == CDATA_COM_DASH_DASH) || (currentState == CDATA_LT) || (currentState == CDATA_MAY_CLOSE) || (currentState == JS_FILE) )); } @Override public boolean isJavascriptQuoted() { if (inJavascript()) { ExternalState jsParserState = jsParser.getState(); return (jsParserState == JavascriptParserImpl.STATE_Q || jsParserState == JavascriptParserImpl.STATE_DQ); } return false; } @Override public boolean inAttribute() { ExternalState extState = getState(); return (extState != null && (extState == STATE_ATTR || extState == STATE_VALUE)); } /** * Returns {@code true} if and only if the parser is currently within * a CSS context. A CSS context is one of the below: *

* * @return {@code true} if and only if the parser is inside CSS */ @Override public boolean inCss() { return (currentState == CSS_FILE || (getState() == STATE_VALUE && (getAttributeType() == ATTR_TYPE.STYLE)) || ("style".equals(getTag()))); } @Override public ATTR_TYPE getAttributeType() { String attribute = getAttribute(); if (!inAttribute()) { return ATTR_TYPE.NONE; } if (HtmlUtils.isAttributeJavascript(attribute)) { return ATTR_TYPE.JS; } if (HtmlUtils.isAttributeUri(attribute)) { return ATTR_TYPE.URI; } if (HtmlUtils.isAttributeStyle(attribute)) { return ATTR_TYPE.STYLE; } // Special logic to handle the "content" attribute of the "meta" tag. if ("meta".equals(getTag()) && "content".equals(getAttribute())) { HtmlUtils.META_REDIRECT_TYPE redirectType = HtmlUtils.parseContentAttributeForUrl(getValue()); if (redirectType == HtmlUtils.META_REDIRECT_TYPE.URL_START || redirectType == HtmlUtils.META_REDIRECT_TYPE.URL) return ATTR_TYPE.URI; } return ATTR_TYPE.REGULAR; } @Override public ExternalState getJavascriptState() { return jsParser.getState(); } @Override public boolean isAttributeQuoted() { return (currentState == VALUE_Q_START || currentState == VALUE_Q || currentState == VALUE_DQ_START || currentState == VALUE_DQ); } @Override public String getTag() { return tag.getContent().toLowerCase(); } @Override public String getAttribute() { return inAttribute() ? attr.getContent().toLowerCase() : ""; } @Override public String getValue() { return (getState() == STATE_VALUE) ? value.getContent() : ""; } @Override public int getValueIndex() { if (getState() != STATE_VALUE) { return 0; } return valueIndex; } @Override public boolean isUrlStart() { // False when not inside an HTML attribute value if (getState() != STATE_VALUE) { return false; } // Or when the HTML attribute is not of URI type. if (getAttributeType() != ATTR_TYPE.URI) { return false; } // Or when we received an InsertText() directive at the start of a URL. if (textInsideUrlValue) { return false; } if ("meta".equals(getTag())) { // At this point, we know we are in the "content" attribute // or we would not have the URI attribute type. return (HtmlUtils.parseContentAttributeForUrl(getValue()) == HtmlUtils.META_REDIRECT_TYPE.URL_START); } // For all other URI attributes, check if we are at index 0. return (getValueIndex() == 0); } /** * {@inheritDoc} * * Resets the state of the parser to a state consistent with the * {@code Mode} provided. This will reset finer-grained state * information back to a default value, hence use only when * you want to parse text from a very clean slate. * *

See the {@link HtmlParser.Mode} enum for information on all * the valid modes. * * @param mode is an enum representing the high-level state of the parser */ @Override public void resetMode(Mode mode) { insideJavascript = false; tag.reset(); attr.reset(); value.reset(); cdataCloseTag.reset(); valueIndex = 0; textInsideUrlValue = false; jsParser.reset(); switch (mode) { case HTML: currentState = TEXT; break; case JS: currentState = JS_FILE; insideJavascript = true; break; case CSS: currentState = CSS_FILE; break; case HTML_IN_TAG: currentState = TAG_SPACE; break; default: throw new IllegalArgumentException("Did not recognize Mode: " + mode.toString()); } } /** * Resets the state of the parser to the initial state of parsing HTML. */ public void reset() { super.reset(); resetMode(Mode.HTML); } /** * A specialized directive to tell the parser there is some content * that will be inserted here but that it will not get to parse. Used * by the template system that may not be able to give some content * to the parser but wants it to know there typically will be content * inserted at that point. This is a hint used in corner cases within * parsing of HTML attribute names and values where content we do not * get to see could affect our parsing and alter our current state. * *

The two cases where {@code #insertText()} affects our parsing are: *

* * @throws ParseException if an unrecoverable error occurred during parsing */ @Override public void insertText() throws ParseException { // Case: Inside URL attribute value. if (getState() == STATE_VALUE && getAttributeType() == ATTR_TYPE.URI && isUrlStart()) { textInsideUrlValue = true; } // Case: Before parsing any attribute value. if (currentState == VALUE) { setNextState(VALUE_TEXT); } } @Override protected InternalState handleEnterState(InternalState currentState, InternalState expectedNextState, char input) { InternalState nextState = expectedNextState; if (currentState == TAG_NAME) { enterTagName(); } else if (currentState == ATTR) { enterAttribute(); } else if (currentState == TAG_CLOSE) { nextState = tagClose(currentState); } else if (currentState == CDATA_MAY_CLOSE) { enterStateCdataMayClose(); } else if (currentState == VALUE) { enterValue(); } else if (currentState == VALUE_TEXT || currentState == VALUE_Q || currentState == VALUE_DQ) { enterValueContent(); } return nextState; } @Override protected InternalState handleExitState(InternalState currentState, InternalState expectedNextState, char input) { InternalState nextState = expectedNextState; if (currentState == TAG_NAME) { exitTagName(); } else if (currentState == ATTR) { exitAttribute(); } else if (currentState == CDATA_MAY_CLOSE) { nextState = exitStateCdataMayClose(nextState, input); } else if ((currentState == VALUE_TEXT) || (currentState == VALUE_Q) || (currentState == VALUE_DQ)) { exitValueContent(); } return nextState; } @Override protected InternalState handleInState(InternalState currentState, char input) throws ParseException { if ((currentState == CDATA_TEXT) || (currentState == CDATA_COM_START) || (currentState == CDATA_COM_START_DASH) || (currentState == CDATA_COM_BODY) || (currentState == CDATA_COM_DASH) || (currentState == CDATA_COM_DASH_DASH) || (currentState == CDATA_LT) || (currentState == CDATA_MAY_CLOSE) || (currentState == JS_FILE)) { inStateCdata(input); } else if ((currentState == VALUE_TEXT) || (currentState == VALUE_Q) || (currentState == VALUE_DQ)) { inStateValue(input); } return currentState; } /** * Invokes recording on all CharacterRecorder objects. Currently we do * not check that one and only one of them is recording. I did a fair * bit of testing on the C++ parser and was not convinced there is * such a guarantee. */ @Override protected void record(char input) { attr.maybeRecord(input); tag.maybeRecord(input); value.maybeRecord(input); cdataCloseTag.maybeRecord(input); } /** * Starts recording the name of the HTML tag. Called when the parser * enters a new tag. */ private void enterTagName() { tag.startRecording(); } private void exitTagName() { tag.stopRecording(); String tagString = tag.getContent(); if (!tagString.isEmpty() && tagString.charAt(0) == '/') { tag.reset(); } } /** * Starts recording the name of the HTML attribute. Called when the parser * enters a new HTML attribute. */ private void enterAttribute() { attr.startRecording(); } private void exitAttribute() { attr.stopRecording(); } /** * Tracks the index within the HTML attribute value and initializes * the javascript parser for attributes that take javascript. * * Called when the parser enters a new HTML attribute value. */ private void enterValue() { valueIndex = 0; textInsideUrlValue = false; if (HtmlUtils.isAttributeJavascript(getAttribute())) { entityResolver.reset(); jsParser.reset(); insideJavascript = true; } else { insideJavascript = false; } } /** * Starts recordning the contents of the attribute value. * * Called when entering an attribute value. */ private void enterValueContent() { value.startRecording(); } /** * Stops the recording of the attribute value and exits javascript * (in case we were inside it). */ private void exitValueContent() { value.stopRecording(); insideJavascript = false; } /** * Processes javascript after performing entity resolution and updates * the position within the attribute value. * If the status of the entity resolution is IN_PROGRESS, * we don't invoke the javascript parser. * *

Called for every character inside an attribute value. * * @param input character read * @throws ParseException if an unrecoverable error occurred during parsing */ private void inStateValue(char input) throws ParseException { valueIndex++; if (insideJavascript) { EntityResolver.Status status = entityResolver.processChar(input); if (status == EntityResolver.Status.COMPLETED) { jsParser.parse(entityResolver.getEntity()); entityResolver.reset(); } else if (status == EntityResolver.Status.NOT_STARTED) { jsParser.parse(input); } } } /** * Handles the tag it finished reading. * *

For a script tag, it initializes the javascript parser. For all * tags that are recognized to have CDATA values * (including the script tag), it switches the CDATA state to handle them * properly. For code simplification, CDATA and RCDATA sections are * treated the same. * *

Called when the parser leaves a tag definition. * * @param state current state * @return state next state, could be the same as current state */ private InternalState tagClose(InternalState state) { InternalState nextState = state; String tagName = getTag(); if ("script".equals(tagName)) { nextState = CDATA_TEXT; jsParser.reset(); insideJavascript = true; } else if ("style".equals(tagName) || "title".equals(tagName) || "textarea".equals(tagName)) { nextState = CDATA_TEXT; insideJavascript = false; } return nextState; } /** * Feeds the character to the javascript parser for processing. * *

Called inside CDATA blocks to parse javascript. * * @param input character read * @throws ParseException if an unrecoverable error occurred during parsing */ private void inStateCdata(char input) throws ParseException { if (insideJavascript) { jsParser.parse(input); } } /** * Starts recording. This is so we find the closing tag name in order to * know if the tag is going to be closed or not. * *

Called when encountering a '<' character in a CDATA section. */ private void enterStateCdataMayClose() { cdataCloseTag.startRecording(); } /** * Determines whether to close the tag element, It closes it if it finds * the corresponding end tag. Called when reading what could be a * closing CDATA tag. * * @param input the character read * @param expectedNextState the expected state to go to next * unless we want to change it here * @return the next state to go to */ private InternalState exitStateCdataMayClose( InternalState expectedNextState, char input) { InternalState nextState = expectedNextState; cdataCloseTag.stopRecording(); String cdataCloseTagString = cdataCloseTag.getContent(); Preconditions.checkState(!cdataCloseTagString.isEmpty() && cdataCloseTagString.charAt(0) == '/'); // Developer error. if (cdataCloseTagString.substring(1).equalsIgnoreCase(getTag()) && (input == '>' || HtmlUtils.isHtmlSpace(input))) { tag.clear(); insideJavascript = false; } else { nextState = CDATA_TEXT; } return nextState; } // ======================================================= // // SECTION BELOW WILL ALL BE AUTO-GENERATED IN FUTURE. // // ======================================================= // private static void registerMapping(InternalState internalState, ExternalState externalState) { STATE_MAPPING.put(internalState, externalState); } private static void initializeStateMapping() { // Each parser implementation must map the error state appropriately. registerMapping(InternalState.INTERNAL_ERROR_STATE, HtmlParser.STATE_ERROR); registerMapping(TEXT, HtmlParser.STATE_TEXT); registerMapping(TAG_START, HtmlParser.STATE_TAG); registerMapping(TAG_NAME, HtmlParser.STATE_TAG); registerMapping(DECL_START, HtmlParser.STATE_TEXT); registerMapping(DECL_BODY, HtmlParser.STATE_TEXT); registerMapping(COM_OPEN, HtmlParser.STATE_TEXT); registerMapping(COM_BODY, HtmlParser.STATE_COMMENT); registerMapping(COM_DASH, HtmlParser.STATE_COMMENT); registerMapping(COM_DASH_DASH, HtmlParser.STATE_COMMENT); registerMapping(PI, HtmlParser.STATE_TEXT); registerMapping(PI_MAY_END, HtmlParser.STATE_TEXT); registerMapping(TAG_SPACE, HtmlParser.STATE_TAG); registerMapping(TAG_CLOSE, HtmlParser.STATE_TEXT); registerMapping(ATTR, HtmlParser.STATE_ATTR); registerMapping(ATTR_SPACE, HtmlParser.STATE_ATTR); registerMapping(VALUE, HtmlParser.STATE_VALUE); registerMapping(VALUE_TEXT, HtmlParser.STATE_VALUE); registerMapping(VALUE_Q_START, HtmlParser.STATE_VALUE); registerMapping(VALUE_Q, HtmlParser.STATE_VALUE); registerMapping(VALUE_DQ_START, HtmlParser.STATE_VALUE); registerMapping(VALUE_DQ, HtmlParser.STATE_VALUE); registerMapping(CDATA_COM_START, HtmlParser.STATE_TEXT); registerMapping(CDATA_COM_START_DASH, HtmlParser.STATE_TEXT); registerMapping(CDATA_COM_BODY, HtmlParser.STATE_TEXT); registerMapping(CDATA_COM_DASH, HtmlParser.STATE_TEXT); registerMapping(CDATA_COM_DASH_DASH, HtmlParser.STATE_TEXT); registerMapping(CDATA_TEXT, HtmlParser.STATE_TEXT); registerMapping(CDATA_LT, HtmlParser.STATE_TEXT); registerMapping(CDATA_MAY_CLOSE, HtmlParser.STATE_TEXT); registerMapping(JS_FILE, HtmlParser.STATE_JS_FILE); registerMapping(CSS_FILE, HtmlParser.STATE_CSS_FILE); } private static void registerTransition(String expression, InternalState source, InternalState to) { // It seems to silly to go through a StateTableTransition here // but it adds extra data checking. StateTableTransition stt = new StateTableTransition(expression, source, to); STATE_TABLE.setExpression(stt.getExpression(), stt.getFrom(), stt.getTo()); } // NOTE: The "[:default:]" transition should be registered before any // other transitions for a given state or it will over-write them. private static void initializeParserStateTable() { registerTransition("[:default:]", CSS_FILE, CSS_FILE); registerTransition("[:default:]", JS_FILE, JS_FILE); registerTransition("[:default:]", CDATA_MAY_CLOSE, CDATA_TEXT); registerTransition(" \t\n\r", CDATA_MAY_CLOSE, TAG_SPACE); registerTransition(">", CDATA_MAY_CLOSE, TEXT); registerTransition("A-Za-z0-9/_:-", CDATA_MAY_CLOSE, CDATA_MAY_CLOSE); registerTransition("[:default:]", CDATA_LT, CDATA_TEXT); registerTransition("!", CDATA_LT, CDATA_COM_START); registerTransition("/", CDATA_LT, CDATA_MAY_CLOSE); registerTransition("[:default:]", CDATA_TEXT, CDATA_TEXT); registerTransition("<", CDATA_TEXT, CDATA_LT); registerTransition("[:default:]", CDATA_COM_DASH_DASH, CDATA_COM_BODY); registerTransition(">", CDATA_COM_DASH_DASH, CDATA_TEXT); registerTransition("-", CDATA_COM_DASH_DASH, CDATA_COM_DASH_DASH); registerTransition("[:default:]", CDATA_COM_DASH, CDATA_COM_BODY); registerTransition("-", CDATA_COM_DASH, CDATA_COM_DASH_DASH); registerTransition("[:default:]", CDATA_COM_BODY, CDATA_COM_BODY); registerTransition("-", CDATA_COM_BODY, CDATA_COM_DASH); registerTransition("[:default:]", CDATA_COM_START_DASH, CDATA_TEXT); registerTransition("-", CDATA_COM_START_DASH, CDATA_COM_BODY); registerTransition("[:default:]", CDATA_COM_START, CDATA_TEXT); registerTransition("-", CDATA_COM_START, CDATA_COM_START_DASH); registerTransition("[:default:]", VALUE_DQ, VALUE_DQ); registerTransition("\"", VALUE_DQ, TAG_SPACE); registerTransition("[:default:]", VALUE_DQ_START, VALUE_DQ); registerTransition("\"", VALUE_DQ_START, TAG_SPACE); registerTransition("[:default:]", VALUE_Q, VALUE_Q); registerTransition("\'", VALUE_Q, TAG_SPACE); registerTransition("[:default:]", VALUE_Q_START, VALUE_Q); registerTransition("\'", VALUE_Q_START, TAG_SPACE); registerTransition("[:default:]", VALUE_TEXT, VALUE_TEXT); registerTransition(" \t\n\r", VALUE_TEXT, TAG_SPACE); registerTransition(">", VALUE_TEXT, TAG_CLOSE); registerTransition("[:default:]", VALUE, VALUE_TEXT); registerTransition(">", VALUE, TAG_CLOSE); registerTransition(" \t\n\r", VALUE, VALUE); registerTransition("\"", VALUE, VALUE_DQ_START); registerTransition("\'", VALUE, VALUE_Q_START); registerTransition("=", ATTR_SPACE, VALUE); registerTransition("/", ATTR_SPACE, TAG_SPACE); registerTransition("A-Za-z0-9_:-", ATTR_SPACE, ATTR); registerTransition(" \t\n\r", ATTR_SPACE, ATTR_SPACE); registerTransition(">", ATTR_SPACE, TAG_CLOSE); registerTransition(" \t\n\r", ATTR, ATTR_SPACE); registerTransition("=", ATTR, VALUE); registerTransition("/", ATTR, TAG_SPACE); registerTransition(">", ATTR, TAG_CLOSE); registerTransition("A-Za-z0-9_:.-", ATTR, ATTR); registerTransition("[:default:]", TAG_CLOSE, TEXT); registerTransition("<", TAG_CLOSE, TAG_START); registerTransition("/", TAG_SPACE, TAG_SPACE); registerTransition("A-Za-z0-9_:-", TAG_SPACE, ATTR); registerTransition(" \t\n\r", TAG_SPACE, TAG_SPACE); registerTransition(">", TAG_SPACE, TAG_CLOSE); registerTransition("[:default:]", PI_MAY_END, PI); registerTransition(">", PI_MAY_END, TEXT); registerTransition("[:default:]", PI, PI); registerTransition("?", PI, PI_MAY_END); registerTransition("[:default:]", COM_DASH_DASH, COM_BODY); registerTransition(">", COM_DASH_DASH, TEXT); registerTransition("-", COM_DASH_DASH, COM_DASH_DASH); registerTransition("[:default:]", COM_DASH, COM_BODY); registerTransition("-", COM_DASH, COM_DASH_DASH); registerTransition("[:default:]", COM_BODY, COM_BODY); registerTransition("-", COM_BODY, COM_DASH); registerTransition("[:default:]", COM_OPEN, TEXT); registerTransition("-", COM_OPEN, COM_BODY); registerTransition("[:default:]", DECL_BODY, DECL_BODY); registerTransition(">", DECL_BODY, TEXT); registerTransition("[:default:]", DECL_START, DECL_BODY); registerTransition(">", DECL_START, TEXT); registerTransition("-", DECL_START, COM_OPEN); registerTransition(">", TAG_NAME, TAG_CLOSE); registerTransition(" \t\n\r", TAG_NAME, TAG_SPACE); registerTransition("A-Za-z0-9/_:-", TAG_NAME, TAG_NAME); // Manual change to remain in-sync with CL 10597850 in C HtmlParser. registerTransition("[:default:]", TAG_START, TEXT); registerTransition("<", TAG_START, TAG_START); // End of manual change. registerTransition("!", TAG_START, DECL_START); registerTransition("?", TAG_START, PI); registerTransition("A-Za-z0-9/_:-", TAG_START, TAG_NAME); registerTransition("[:default:]", TEXT, TEXT); registerTransition("<", TEXT, TAG_START); } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/impl/ParserStateTable.java0000644000175000017500000001545611427052602027312 0ustar chrischris/* * 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.streamhtmlparser.impl; import com.google.common.base.Preconditions; /** * Holds a state table which is defined as the set of all * recognized state transitions and the set of characters that * trigger them. * *

The logic of what character causes what state transition is derived from * a base definition written as a Python configuration file in the original * C++ parser. * *

This class provides methods to initially build the state table and then * methods at parsing time to determine the transitions to subsequent states. * *

Note on characters outside the extended ASCII range: Currently, all state * transitions in the Python configuration file trigger only on extended * ASCII characters, that is characters in the Unicode space of [U+0000 to * U+00FF]. We use that property to design a more efficient state transition * representation. When receiving characters outside that ASCII range, we * simply apply the DEFAULT transition for the given state - as we do for any * character that is not a hot character for that state. If no default * transition exists, we switch to the Internal Error state. * *

Technical note: In Java, a {@code char} is a code unit and in some cases * may not represent a complete Unicode code point. However, when that happens, * the code units that follow for that code point are all in the surrogate area * [U+D800 - U+DFFF] and hence outside of the ASCII range and will not trigger * any incorrect state transitions. * *

This class is storage-inefficient but it is static at least * and not generated for each Parser instance. */ class ParserStateTable { /** * A limit on how many different states we can have in one state table. * Can be increased should it no longer be sufficient. */ private static final int MAX_STATES = 256; /** * We only check transitions for (extended) ASCII characters, hence * characters in the range 0 to MAX_CHARS -1. */ private static final int MAX_CHARS = 256; /** * Records all state transitions except those identified as DEFAULT * transitions. It is two dimensional: Stores a target {@code InternalState} * given a source state (referenced by its numeric ID) and the current * character. */ private final InternalState[][] stateTable; /** * Records all DEFAULT state transitions. These are transitions provided * using the {@code "[:default:]"} syntax in the Python configuration file. * There can be only one such transition for any given source state, hence * the array is one dimensional. */ private final InternalState[] defaultStateTable; public ParserStateTable() { stateTable = new InternalState[MAX_STATES][MAX_CHARS]; defaultStateTable = new InternalState[MAX_STATES]; } /** * Returns the state to go to when receiving the current {@code char} * in the {@code from} state. * Returns {@code InternalState.INTERNAL_ERROR_STATE} if there is no * state transition for that character and no default state transition * for that state. * *

For ASCII characters, first look-up an explicit state transition for * the current character. If none is found, look-up a default transition. For * non-ASCII characters, look-up a default transition only. * * @param from the source state * @param currentChar the character received * @return the state to move to or {@code InternalState.INTERNAL_ERROR_STATE} */ InternalState getNextState(InternalState from, int currentChar) { // TODO: Consider throwing run-time error here. if (from == null || currentChar < 0) return InternalState.INTERNAL_ERROR_STATE; int id = from.getId(); if (id < 0 || id >= MAX_STATES) { return InternalState.INTERNAL_ERROR_STATE; } InternalState result = null; if (currentChar < MAX_CHARS) { result = stateTable[id][currentChar]; } if (result == null) { result = defaultStateTable[from.getId()]; } return result != null ? result : InternalState.INTERNAL_ERROR_STATE; } void setExpression(String expr, InternalState from, InternalState to) { if ((expr == null) || (from == null) || (to == null)) { return; } // This special string indicates a default state transition. if ("[:default:]".equals(expr)) { setDefaultDestination(from, to); return; } int i = 0; while (i < expr.length()) { // If next char is a '-' which is not the last character of the expr if (i < (expr.length() - 2) && expr.charAt(i + 1) == '-') { setRange(from, expr.charAt(i), expr.charAt(i + 2), to); i += 2; } else { setDestination(from, expr.charAt(i), to); i++; } } } private void fill(InternalState from, InternalState to) { char c; for (c = 0; c < MAX_CHARS; c++) { setDestination(from, c, to); } } private void setDefaultDestination(InternalState from, InternalState to) { Preconditions.checkNotNull(from); // Developer error if it triggers Preconditions.checkNotNull(to); // Developer error if it triggers int id = from.getId(); if ((id < 0) || (id >= MAX_STATES)) { return; } // TODO: Consider asserting if there was a state transition defined. defaultStateTable[from.getId()] = to; } private void setDestination(InternalState from, char chr, InternalState to) { Preconditions.checkNotNull(from); // Developer error if it triggers Preconditions.checkNotNull(to); // Developer error if it triggers Preconditions.checkArgument(chr >= 0 && chr < MAX_CHARS, "char must be in ASCII set: %c", chr); int id = from.getId(); if ((id < 0) || (id >= MAX_STATES)) { return; } stateTable[from.getId()][chr] = to; } private void setRange(InternalState from, char start, char end, InternalState to) { // Developer error if either trigger. Preconditions.checkArgument(start >= 0 && start < MAX_CHARS, "char must be in ASCII set: %c", start); Preconditions.checkArgument(end >= 0 && end < MAX_CHARS, "char must be in ASCII set: %c", end); char c; for (c = start; c <= end; c++) { setDestination(from, c, to); } } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/impl/StateTableTransition.java0000644000175000017500000000425111427052602030177 0ustar chrischris/* * 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.streamhtmlparser.impl; import com.google.common.base.Preconditions; /** * Holds one state transition as derived from a Python configuration * file. A state transition is a triplet as follows: *

* *

For example, the triplet ("a-z123", A, B) will cause the * state to go from A to B for any character that is either 1,2,3 or in * the range a-z inclusive. */ class StateTableTransition { private final String expression; private final InternalState from; private final InternalState to; /** * Returns the full state of the {@code StateTableTransition} in a * human readable form. The format of the returned {@code String} is not * specified and is subject to change. * * @return full state of the {@code StateTableTransition} */ @Override public String toString() { return String.format("Expression: %s; From: %s; To: %s", expression, from, to); } StateTableTransition(String expression, InternalState from, InternalState to) { // Developer error if any triggers. Preconditions.checkNotNull(expression); Preconditions.checkNotNull(from); Preconditions.checkNotNull(to); this.expression = expression; this.from = from; this.to = to; } String getExpression() { return expression; } InternalState getFrom() { return from; } InternalState getTo() { return to; } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/impl/JavascriptParserImpl.java0000644000175000017500000003233411427052602030204 0ustar chrischris/* * 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.streamhtmlparser.impl; import com.google.common.collect.Maps; import com.google.streamhtmlparser.ExternalState; import com.google.streamhtmlparser.JavascriptParser; import com.google.streamhtmlparser.util.HtmlUtils; import com.google.streamhtmlparser.util.JavascriptTokenBuffer; import java.util.Map; /** *

Many comments copied almost verbatim from the original C version. */ public class JavascriptParserImpl extends GenericParser implements JavascriptParser { final static InternalState JS_TEXT; final static InternalState JS_Q; final static InternalState JS_Q_E; final static InternalState JS_DQ; final static InternalState JS_DQ_E; final static InternalState JS_SLASH; final static InternalState JS_REGEXP_SLASH; final static InternalState JS_REGEXP; final static InternalState JS_REGEXP_BRK; final static InternalState JS_REGEXP_BRK_E; final static InternalState JS_REGEXP_E; final static InternalState JS_COM_LN; final static InternalState JS_COM_ML; final static InternalState JS_COM_ML_CLOSE; final static InternalState JS_COM_AFTER; static { JS_TEXT = InternalState.getInstanceJavascript("JS_TEXT"); JS_Q = InternalState.getInstanceJavascript("JS_Q"); JS_Q_E = InternalState.getInstanceJavascript("JS_Q_E"); JS_DQ = InternalState.getInstanceJavascript("JS_DQ"); JS_DQ_E = InternalState.getInstanceJavascript("JS_DQ_E"); JS_SLASH = InternalState.getInstanceJavascript("JS_SLASH"); JS_REGEXP = InternalState.getInstanceJavascript("JS_REGEXP"); JS_REGEXP_SLASH = InternalState.getInstanceJavascript("JS_REGEXP_SLASH"); JS_REGEXP_E = InternalState.getInstanceJavascript("JS_REGEXP_E"); JS_REGEXP_BRK = InternalState.getInstanceJavascript("JS_REGEXP_BRK"); JS_REGEXP_BRK_E = InternalState.getInstanceJavascript("JS_REGEXP_BRK_E"); JS_COM_LN = InternalState.getInstanceJavascript("COMMENT_LN"); JS_COM_ML = InternalState.getInstanceJavascript("COMMENT_ML"); JS_COM_ML_CLOSE = InternalState.getInstanceJavascript("COMMENT_ML_CLOSE"); JS_COM_AFTER = InternalState.getInstanceJavascript("COMMENT_AFTER"); } private static final Map STATE_MAPPING = Maps.newHashMap(); static { initializeStateMapping(); } private static final ParserStateTable STATE_TABLE = new ParserStateTable(); static { initializeParserStateTable(); } private final JavascriptTokenBuffer ccBuffer; /** * Creates a {@code JavascriptParserImpl} object. */ public JavascriptParserImpl() { super(STATE_TABLE, STATE_MAPPING, JS_TEXT); ccBuffer = new JavascriptTokenBuffer(); } /** * Creates a {@code JavascriptParserImpl} object that is a copy * of the one provided. * * @param aJavascriptParserImpl the {@code JavascriptParserImpl} to copy */ public JavascriptParserImpl(JavascriptParserImpl aJavascriptParserImpl) { super(aJavascriptParserImpl); ccBuffer = new JavascriptTokenBuffer(aJavascriptParserImpl.ccBuffer); } @Override public void reset() { super.reset(); currentState = JS_TEXT; } @Override protected InternalState handleEnterState(InternalState currentState, InternalState expectedNextState, char input) { InternalState nextState = expectedNextState; if (currentState == JS_SLASH) { nextState = enterStateJsSlash(currentState, input); } else if (currentState == JS_COM_AFTER) { enterStateJsCommentAfter(); } return nextState; } @Override protected InternalState handleExitState(InternalState currentState, InternalState expectedNextState, char input) { // Nothing to do - no handlers for exit states return expectedNextState; } @Override protected InternalState handleInState(InternalState currentState, char input) { if (currentState == JS_TEXT) { inStateJsText(input); } return currentState; } /** * Called every time we find a slash ('/') character in the javascript * text (except for slashes that close comments or regexp literals). * *

Comment copied verbatim from the corresponding C-version. * *

Implements the logic to figure out if this slash character is a * division operator or if it opens a regular expression literal. * This is heavily inspired by the syntactic resynchronization * for javascript 2.0: * *

When we receive a '/', we look at the previous non space character * to figure out if it's the ending of a punctuator that can precede a * regexp literal, in which case we assume the current '/' is part of a * regular expression literal (or the opening of a javascript comment, * but that part is dealt with in the state machine). The exceptions to * this are unary operators, so we look back a second character to rule * out '++' and '--'. * *

Although it is not straightforward to figure out if the binary * operator is a postfix of the previous expression or a prefix of the * regular expression, we rule out the later as it is an uncommon practice. * *

If we ruled out the previous token to be a valid regexp preceding * punctuator, we extract the last identifier in the buffer and match * against a list of keywords that are known to precede expressions in * the grammar. If we get a match on any of these keywords, then we are * opening a regular expression, if not, then we have a division operator. * *

Known cases that are accepted by the grammar but we handle * differently, although I (falmeida) don't believe there is a * legitimate usage for those: * Division of a regular expression: var result = /test/ / 5; * Prefix unary increment of a regular expression: var result = ++/test/; * Division of an object literal: { a: 1 } /x/.exec('x'); * * @param state being entered to * @param input character being processed * @return state next state to go to, may be the same as the one we * were called with * * http://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html> * Syntactic Resynchronization */ private InternalState enterStateJsSlash(InternalState state, char input) { InternalState nextState = state; int position = -1; // Consume the last whitespace if (HtmlUtils.isJavascriptWhitespace(ccBuffer.getChar(position))) { --position; } switch (ccBuffer.getChar(position)) { // Ignore unary increment case '+': if (ccBuffer.getChar(position - 1) != '+') { nextState = JS_REGEXP_SLASH; } break; case '-': // Ignore unary decrement if (ccBuffer.getChar(position - 1) != '-') { nextState = JS_REGEXP_SLASH; } break; // List of punctuator endings except ), ], }, + and - * case '=': case '<': case '>': case '&': case '|': case '!': case '%': case '*': case '/': case ',': case ';': case '?': case ':': case '^': case '~': case '{': case '(': case '[': case '}': case '\0': nextState = JS_REGEXP_SLASH; break; default: String lastIdentifier = ccBuffer.getLastIdentifier(); if (lastIdentifier != null && HtmlUtils .isJavascriptRegexpPrefix(lastIdentifier)) { nextState = JS_REGEXP_SLASH; } } ccBuffer.appendChar(input); return nextState; } /** * Called at the end of a javascript comment. * *

When we open a comment, the initial '/' was inserted into the ring * buffer, but it is not a token and should be considered whitespace * for parsing purposes. * *

When we first saw the '/' character, we didn't yet know if it was * the beginning of a comment, a division operator, or a regexp. * *

In this function we just replace the inital '/' with a whitespace * character, unless we had a preceding whitespace character, in which * case we just remove the '/'. This is needed to ensure all spaces in * the buffer are correctly folded. */ private void enterStateJsCommentAfter() { if (HtmlUtils.isJavascriptWhitespace(ccBuffer.getChar(-2))) { ccBuffer.popChar(); } else { ccBuffer.setChar(-1, ' '); } } private void inStateJsText(char input) { ccBuffer.appendChar(input); } // ======================================================= // // SECTION BELOW WILL ALL BE AUTO-GENERATED IN FUTURE. // // ======================================================= // private static void registerMapping(InternalState internalState, ExternalState externalState) { STATE_MAPPING.put(internalState, externalState); } private static void initializeStateMapping() { // Each parser implementation must map the error state appropriately. registerMapping(InternalState.INTERNAL_ERROR_STATE, JavascriptParser.STATE_ERROR); registerMapping(JS_TEXT, JavascriptParser.STATE_TEXT); registerMapping(JS_Q, JavascriptParser.STATE_Q); registerMapping(JS_Q_E, JavascriptParser.STATE_Q); registerMapping(JS_DQ, JavascriptParser.STATE_DQ); registerMapping(JS_DQ_E, JavascriptParser.STATE_DQ); registerMapping(JS_SLASH, JavascriptParser.STATE_TEXT); registerMapping(JS_REGEXP_SLASH, JavascriptParser.STATE_TEXT); registerMapping(JS_REGEXP, JavascriptParser.STATE_REGEXP); registerMapping(JS_REGEXP_BRK,JavascriptParser.STATE_REGEXP); registerMapping(JS_REGEXP_BRK_E, JavascriptParser.STATE_REGEXP); registerMapping(JS_REGEXP_E,JavascriptParser.STATE_REGEXP); registerMapping(JS_COM_LN, JavascriptParser.STATE_COMMENT); registerMapping(JS_COM_ML, JavascriptParser.STATE_COMMENT); registerMapping(JS_COM_ML_CLOSE, JavascriptParser.STATE_COMMENT); registerMapping(JS_COM_AFTER, JavascriptParser.STATE_TEXT); } private static void registerTransition(String expression, InternalState source, InternalState to) { // It seems to silly to go through a StateTableTransition here // but it adds extra data checking. StateTableTransition stt = new StateTableTransition(expression, source, to); STATE_TABLE.setExpression(stt.getExpression(), stt.getFrom(), stt.getTo()); } private static void initializeParserStateTable() { registerTransition("[:default:]", JS_COM_AFTER, JS_TEXT); registerTransition("/", JS_COM_AFTER, JS_SLASH); registerTransition("\"", JS_COM_AFTER, JS_DQ); registerTransition("\'", JS_COM_AFTER, JS_Q); registerTransition("[:default:]", JS_COM_ML_CLOSE, JS_COM_ML); registerTransition("/", JS_COM_ML_CLOSE,JS_COM_AFTER); registerTransition("[:default:]", JS_COM_ML, JS_COM_ML); registerTransition("*", JS_COM_ML, JS_COM_ML_CLOSE); registerTransition("[:default:]", JS_COM_LN,JS_COM_LN); registerTransition("\n", JS_COM_LN,JS_COM_AFTER); registerTransition("[:default:]", JS_REGEXP_E, JS_REGEXP); registerTransition("[:default:]", JS_REGEXP_BRK_E, JS_REGEXP_BRK); registerTransition("[:default:]", JS_REGEXP_BRK, JS_REGEXP_BRK); registerTransition("]", JS_REGEXP_BRK, JS_REGEXP); registerTransition("\\", JS_REGEXP_BRK, JS_REGEXP_BRK_E); registerTransition("[:default:]", JS_REGEXP, JS_REGEXP); registerTransition("/", JS_REGEXP, JS_TEXT); registerTransition("[", JS_REGEXP, JS_REGEXP_BRK); registerTransition("\\", JS_REGEXP, JS_REGEXP_E); registerTransition("[:default:]", JS_REGEXP_SLASH, JS_REGEXP); registerTransition("[", JS_REGEXP_SLASH, JS_REGEXP_BRK); registerTransition("\\", JS_REGEXP_SLASH, JS_REGEXP_E); registerTransition("*", JS_REGEXP_SLASH, JS_COM_ML); registerTransition("/", JS_REGEXP_SLASH, JS_COM_LN); registerTransition("[:default:]", JS_SLASH, JS_TEXT); registerTransition("*", JS_SLASH, JS_COM_ML); registerTransition("/", JS_SLASH, JS_COM_LN); registerTransition("[:default:]", JS_DQ_E,JS_DQ); registerTransition("[:default:]", JS_DQ,JS_DQ); registerTransition("\"", JS_DQ, JS_TEXT); registerTransition("\\", JS_DQ, JS_DQ_E); registerTransition("[:default:]", JS_Q_E,JS_Q); registerTransition("[:default:]", JS_Q,JS_Q); registerTransition("\'", JS_Q, JS_TEXT); registerTransition("\\", JS_Q, JS_Q_E); registerTransition("[:default:]", JS_TEXT, JS_TEXT); registerTransition("/", JS_TEXT, JS_SLASH); registerTransition("\"", JS_TEXT, JS_DQ); registerTransition("\'", JS_TEXT, JS_Q); } }jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/ParseException.java0000644000175000017500000000257211427052602026070 0ustar chrischris/* * 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.streamhtmlparser; /** * Checked exception thrown on an unrecoverable error during parsing. * * @see Parser#parse(String) */ public class ParseException extends Exception { /** * Constructs an {@code ParseException} with no detail message. */ public ParseException() {} /** * Constructs an {@code ParseException} with a detail message obtained * from the supplied message and the parser's line and column numbers. * @param parser the {@code Parser} that triggered the exception * @param msg the error message */ public ParseException(Parser parser, String msg) { super(String.format("At line: %d (col: %d); %s", parser.getLineNumber(), parser.getColumnNumber(), msg)); } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/JavascriptParserFactory.java0000644000175000017500000000256011427052602027747 0ustar chrischris/* * 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.streamhtmlparser; import com.google.streamhtmlparser.impl.JavascriptParserImpl; /** * A factory class to obtain instances of an JavascriptParser. * Currently each instance is a new object given these are fairly * light-weight. * *

Note that we do not typically expect a caller of this package to require * an instance of a JavascriptParser since one is already * embedded in the more general-purpose HtmlParser. We still * make it possible to require one as it may be useful for more * specialized needs. * */ public class JavascriptParserFactory { public static JavascriptParser getInstance() { return new JavascriptParserImpl(); } // Static class. private JavascriptParserFactory() { } // COV_NF_LINE }jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/util/0000755000175000017500000000000011767454573023267 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/util/JavascriptTokenBuffer.java0000644000175000017500000002025611427052602030354 0ustar chrischris/* * 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.streamhtmlparser.util; import com.google.common.base.Preconditions; import java.util.Arrays; /** * Implements a circular (ring) buffer of characters with specialized * application logic in order to determine the context of some * Javascript content that is being parsed. * * This is a specialized class - of no use to external code - * which aims to be 100% compatible with the corresponding logic * in the C-version of the HtmlParser, specifically * jsparser.c. In particular: *

*/ public class JavascriptTokenBuffer { /** * Size of the ring buffer used to lookup the last token in the javascript * stream. The size is somewhat arbitrary but must be larger than * the biggest token we want to lookup plus three: Two delimiters plus * an empty ring buffer slot. */ private static final int BUFFER_SIZE = 18; /** Storage implementing the circular buffer. */ private final char[] buffer; /** Index of the first item in our circular buffer. */ private int startIndex; /** Index of the last item in our circular buffer. */ private int endIndex; /** * Constructs an empty javascript token buffer. The size is fixed, * see {@link #BUFFER_SIZE}. */ public JavascriptTokenBuffer() { buffer = new char[BUFFER_SIZE]; startIndex = 0; endIndex = 0; } /** * Constructs a javascript token buffer that is identical to * the one given. In particular, it has the same size and contents. * * @param aJavascriptTokenBuffer the {@code JavascriptTokenBuffer} to copy */ public JavascriptTokenBuffer(JavascriptTokenBuffer aJavascriptTokenBuffer) { buffer = Arrays.copyOf(aJavascriptTokenBuffer.buffer, aJavascriptTokenBuffer.buffer.length); startIndex = aJavascriptTokenBuffer.startIndex; endIndex = aJavascriptTokenBuffer.endIndex; } /** * A simple wrapper over appendChar, it appends a string * to the buffer. Sequences of whitespace and newlines * are folded into one character to save space. Null strings are * not allowed. * * @param input the {@code String} to append, cannot be {@code null} */ // TODO: Move to testing since not used in code. public void appendString(String input) { if (input == null) { throw new NullPointerException("input == null is not allowed"); } for (int i = 0; i < input.length(); i++) { appendChar(input.charAt(i)); } } /** * Appends a character to the buffer. We fold sequences of whitespace and * newlines into one to save space. * * @param input the {@code char} to append */ public void appendChar(char input) { if (HtmlUtils.isJavascriptWhitespace(input) && HtmlUtils.isJavascriptWhitespace(getChar(-1))) { return; } buffer[endIndex] = input; endIndex = (endIndex + 1) % buffer.length; if (endIndex == startIndex) { startIndex = (endIndex + 1) % buffer.length; } } /** * Returns the last character in the buffer and removes it from the buffer * or the NUL character '\0' if the buffer is empty. * * @return last character in the buffer or '\0' if the buffer is empty */ public char popChar() { if (startIndex == endIndex) { return '\0'; } endIndex--; if (endIndex < 0) { endIndex += buffer.length; } return buffer[endIndex]; } /** * Returns the character at a given index in the buffer or nul ('\0') * if the index is outside the range of the buffer. Such could happen * if the buffer is not filled enough or the index is larger than the * size of the buffer. * *

Position must be negative where -1 is the index of the last * character in the buffer. * * @param position The index into the buffer * * @return character at the requested index */ public char getChar(int position) { assert(position < 0); // Developer error if it triggers. int absolutePosition = getAbsolutePosition(position); if (absolutePosition < 0) { return '\0'; } return buffer[absolutePosition]; } /** * Sets the given {@code input} at the given {@code position} of the buffer. * Returns {@code true} if we succeeded or {@code false} if we * failed (i.e. the write was beyond the buffer boundary). * *

Index positions are negative where -1 is the index of the * last character in the buffer. * * @param position The index at which to set the character * @param input The character to set in the buffer * @return {@code true} if we succeeded, {@code false} otherwise */ public boolean setChar(int position, char input) { assert(position < 0); // Developer error if it triggers. int absolutePosition = getAbsolutePosition(position); if (absolutePosition < 0) { return false; } buffer[absolutePosition] = input; return true; } /** * Returns the last javascript identifier/keyword in the buffer. * * @return the last identifier or {@code null} if none was found */ public String getLastIdentifier() { int end = -1; if (HtmlUtils.isJavascriptWhitespace(getChar(-1))) { end--; } int position; for (position = end; HtmlUtils.isJavascriptIdentifier(getChar(position)); position--) { } if ((position + 1) >= end) { return null; } return slice(position + 1, end); } /** * Returns a slice of the buffer delimited by the given indices. * * The start and end indexes represent the start and end of the * slice to copy. If the start argument extends beyond the beginning * of the buffer, the slice will only contain characters * starting from the beginning of the buffer. * * @param start The index of the first character the copy * @param end the index of the last character to copy * * @return {@code String} between the given indices */ public String slice(int start, int end) { // Developer error if any of the asserts below fail. Preconditions.checkArgument(start <= end); Preconditions.checkArgument(start < 0); Preconditions.checkArgument(end < 0); StringBuffer output = new StringBuffer(); for (int position = start; position <= end; position++) { char c = getChar(position); if (c != '\0') { output.append(c); } } return new String(output); } /** * Returns the position relative to the start of the buffer or -1 * if the position is past the size of the buffer. * * @param position the index to be translated * @return the position relative to the start of the buffer */ private int getAbsolutePosition(int position) { assert (position < 0); // Developer error if it triggers. if (position <= -buffer.length) { return -1; } int len = endIndex - startIndex; if (len < 0) { len += buffer.length; } if (position < -len) { return -1; } int absolutePosition = (position + endIndex) % buffer.length; if (absolutePosition < 0) { absolutePosition += buffer.length; } return absolutePosition; } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/util/CharacterRecorder.java0000644000175000017500000001211411427052602027467 0ustar chrischris/* * 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.streamhtmlparser.util; /** * Records (stores) characters supplied one at a time conditional on * whether recording is currently enabled. * *

When {@link #maybeRecord(char)} is called, it will add the * supplied character to the recording buffer but only if * recording is in progress. This is useful in our * {@link com.google.security.streamhtmlparser.HtmlParser} * as the caller logic to enable/disable recording is decoupled from the logic * of recording. * *

This is a specialized class - of no use to external code - * which aims to be 100% compatible with the corresponding logic * in the C-version of the HtmlParser, specifically in * statemachine.c. In particular: *

*/ public class CharacterRecorder { /** * How many characters can be recorded before stopping to accept new * ones. Set to one less than in the C-version as we do not need * to reserve a character for the terminating null. */ public static final int RECORDING_BUFFER_SIZE = 255; /** * This is where characters provided for recording are stored. Given * that the CharacterRecorder object is re-used, might as well * allocate the full size from the get-go. */ private final StringBuilder sb; /** Holds whether we are currently recording characters or not. */ private boolean recording; /** * Constructs an empty character recorder of fixed size currently * not recording. See {@link #RECORDING_BUFFER_SIZE} for the size. */ public CharacterRecorder() { sb = new StringBuilder(RECORDING_BUFFER_SIZE); recording = false; } /** * Constructs a character recorder of fixed size that is a copy * of the one provided. In particular it has the same recording * setting and the same contents. * * @param aCharacterRecorder the {@code CharacterRecorder} to copy */ public CharacterRecorder(CharacterRecorder aCharacterRecorder) { recording = aCharacterRecorder.recording; sb = new StringBuilder(RECORDING_BUFFER_SIZE); sb.append(aCharacterRecorder.getContent()); } /** * Enables recording for incoming characters. The recording buffer is cleared * of content it may have contained. */ public void startRecording() { // This is very fast, no memory (re-) allocation will take place. sb.setLength(0); recording = true; } /** * Disables recording further characters. */ public void stopRecording() { recording = false; } /** * Records the {@code input} if recording is currently on and we * have space available in the buffer. If recording is not * currently on, this method will not perform any action. * * @param input the character to record */ public void maybeRecord(char input) { if (recording && (sb.length() < RECORDING_BUFFER_SIZE)) { sb.append(input); } } /** * Empties the underlying storage but does not change the recording * state [i.e whether we are recording or not incoming characters]. */ public void clear() { sb.setLength(0); } /** * Empties the underlying storage and resets the recording indicator * to indicate we are not recording currently. */ public void reset() { clear(); recording = false; } /** * Returns the characters recorded in a {@code String} form. This * method has no side-effects, the characters remain stored as is. * * @return the contents in a {@code String} form */ public String getContent() { return sb.toString(); } /** * Returns whether or not we are currently recording incoming characters. * * @return {@code true} if we are recording, {@code false} otherwise */ public boolean isRecording() { return recording; } /** * Returns the full state of the object in a human readable form. The * format of the returned {@code String} is not specified and is * subject to change. * * @return the full state of this object */ @Override public String toString() { return String.format("In recording: %s; Value: %s", isRecording(), sb.toString()); } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/util/HtmlUtils.java0000644000175000017500000003422011427052602026034 0ustar chrischris/* * 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.streamhtmlparser.util; import com.google.common.collect.ImmutableSortedSet; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * Utility functions for HTML and Javascript that are most likely * not interesting to users outside this package. * *

The HtmlParser will be open-sourced hence we took the * decision to keep these utilities in this package as well as not to * leverage others that may exist in the google3 code base. * *

The functionality exposed is designed to be 100% compatible with * the corresponding logic in the C-version of the HtmlParser as such * we are particularly concerned with cross-language compatibility. * *

Note: The words {@code Javascript} and {@code ECMAScript} are used * interchangeably unless otherwise noted. */ public final class HtmlUtils { /** * static utility class */ private HtmlUtils() { } // COV_NF_LINE /** * Indicates the type of content contained in the {@code content} HTML * attribute of the {@code meta} HTML tag. Used by * {@link HtmlUtils#parseContentAttributeForUrl(String)}. *

The values are: *

*/ public enum META_REDIRECT_TYPE { NONE, URL_START, URL } /** * A regular expression matching the format of a {@code content} attribute * that contains a URL. Used by {@link #parseContentAttributeForUrl}. */ private static final String META_REDIRECT_REGEX = "^\\s*\\d*\\s*;\\s*URL\\s*=\\s*[\'\"]?"; // Safe for use by concurrent threads so we compile once. private static final Pattern META_REDIRECT_PATTERN = Pattern.compile(META_REDIRECT_REGEX, Pattern.CASE_INSENSITIVE); /** * Set of keywords that can precede a regular expression literal. Taken from: * * Language Syntax * *

The token {@code void} was added to the list. Several keywords are * defined in Ecmascript 4 not Ecmascript 3. However, to keep the logic * simple we do not differentiate on the version and bundle them all together. */ private static final Set REGEXP_TOKEN_PREFIXS = ImmutableSortedSet.of( "abstract", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "eval", "export", "extends", "field", "final", "finally", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "native", "new", "package", "private", "protected", "public", "return", "static", "switch", "synchronized", "throw", "throws", "transient", "try", "typeof", "var", "void", "volatile", "while", "with"); /** * Set of all HTML attributes which expect a URI (as the value). * Index of Attributes */ private static final Set ATTRIBUTE_EXPECTS_URI = ImmutableSortedSet.of( "action", "archive", "background", "cite", "classid", "codebase", "data", "dynsrc", "href", "longdesc", "src", "usemap"); /** * Set of {@code Character}s considered whitespace in Javascript. * See {@link #isJavascriptWhitespace(char)} */ private static final Set JAVASCRIPT_WHITESPACE = ImmutableSortedSet.of( '\u0009', /* Tab \t */ '\n', /* Line-Feed 0x0A */ '\u000B', /* Vertical Tab 0x0B */ '\u000C', /* Form Feed \f */ '\r', /* Carriage Return 0x0D */ ' ', /* Space 0x20 */ '\u00A0', /* Non-breaking space 0xA0 */ '\u2028', /* Line separator */ '\u2029'); /* Paragraph separator */ /** * Set of {@code Character}s considered whitespace in HTML. * See {@link #isHtmlSpace(char)} */ private static final Set HTML_WHITESPACE = ImmutableSortedSet.of( ' ', '\t', '\n', '\r', '\u200B'); /** * Determines if the HTML attribute specified expects javascript * for its value. Such is the case for example with the {@code onclick} * attribute. * *

Currently returns {@code true} for any attribute name that starts * with "on" which is not exactly correct but we trust a developer to * not use non-spec compliant attribute names (e.g. onbogus). * * @param attribute the name of an HTML attribute * @return {@code false} if the input is null or is not an attribute * that expects javascript code; {@code true} */ public static boolean isAttributeJavascript(String attribute) { return ((attribute != null) && attribute.startsWith("on")); } /** * Determines if the HTML attribute specified expects a {@code style} * for its value. Currently this is only true for the {@code style} * HTML attribute. * * @param attribute the name of an HTML attribute * @return {@code true} iff the attribute name is one that expects a * style for a value; otherwise {@code false} */ public static boolean isAttributeStyle(String attribute) { return "style".equals(attribute); } /** * Determines if the HTML attribute specified expects a {@code URI} * for its value. For example, both {@code href} and {@code src} * expect a {@code URI} but {@code style} does not. Returns * {@code false} if the attribute given was {@code null}. * * @param attribute the name of an HTML attribute * @return {@code true} if the attribute name is one that expects * a URI for a value; otherwise {@code null} * * @see #ATTRIBUTE_EXPECTS_URI */ public static boolean isAttributeUri(String attribute) { return ATTRIBUTE_EXPECTS_URI.contains(attribute); } /** * Determines if the specified character is an HTML whitespace character. * A character is an HTML whitespace character if and only if it is one * of the characters below. *

* * Note: The list includes the zero-width space (&#x200B;) * which is not included in the C version. * * @param chr the {@code char} to check * @return {@code true} if the character is an HTML whitespace character * * White space */ public static boolean isHtmlSpace(char chr) { return HTML_WHITESPACE.contains(chr); } /** * Determines if the specified character is an ECMAScript whitespace or line * terminator character. A character is a whitespace or line terminator if * and only if it is one of the characters below: * * *

Encompasses the characters in sections 7.2 and 7.3 of ECMAScript 3, in * particular, this list is quite different from that in * Character.isWhitespace. * * ECMAScript Language Specification * * @param chr the {@code char} to check * @return {@code true} or {@code false} * */ public static boolean isJavascriptWhitespace(char chr) { return JAVASCRIPT_WHITESPACE.contains(chr); } /** * Determines if the specified character is a valid character in an * ECMAScript identifier. This determination is currently not exact, * in particular: *

* * We are considering leveraging Character.isJavaIdentifierStart * and Character.isJavaIdentifierPart given that Java * and Javascript follow similar identifier naming rules but we lose * compatibility with the C-version. * * @param chr {@code char} to check * @return {@code true} if the {@code chr} is a Javascript whitespace * character; otherwise {@code false} */ public static boolean isJavascriptIdentifier(char chr) { return ((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_' || chr == '$'); } /** * Determines if the input token provided is a valid token prefix to a * javascript regular expression. The token argument is compared against * a {@code Set} of identifiers that can precede a regular expression in the * javascript grammar, and returns {@code true} if the provided * {@code String} is in that {@code Set}. * * @param input the {@code String} token to check * @return {@code true} iff the token is a valid prefix of a regexp */ public static boolean isJavascriptRegexpPrefix(String input) { return REGEXP_TOKEN_PREFIXS.contains(input); } /** * Encodes the specified character using Ascii for convenient insertion into * a single-quote enclosed {@code String}. Printable characters * are returned as-is. Carriage Return, Line Feed, Horizontal Tab, * back-slash and single quote are all backslash-escaped. All other characters * are returned hex-encoded. * * @param chr {@code char} to encode * @return an Ascii-friendly encoding of the given {@code char} */ public static String encodeCharForAscii(char chr) { if (chr == '\'') { return "\\'"; } else if (chr == '\\') { return "\\\\"; } else if (chr >= 32 && chr <= 126) { return String.format("%c", chr); } else if (chr == '\n') { return "\\n"; } else if (chr == '\r') { return "\\r"; } else if (chr == '\t') { return "\\t"; } else { // Cannot apply a precision specifier for integral types. Specifying // 0-padded hex-encoding with minimum width of two. return String.format("\\u%04x", (int)chr); } } /** * Parses the given {@code String} to determine if it contains a URL in the * format followed by the {@code content} attribute of the {@code meta} * HTML tag. * *

This function expects to receive the value of the {@code content} HTML * attribute. This attribute takes on different meanings depending on the * value of the {@code http-equiv} HTML attribute of the same {@code meta} * tag. Since we may not have access to the {@code http-equiv} attribute, * we instead rely on parsing the given value to determine if it contains * a URL. * * The specification of the {@code meta} HTML tag can be found in: * http://dev.w3.org/html5/spec/Overview.html#attr-meta-http-equiv-refresh * *

We return {@link HtmlUtils.META_REDIRECT_TYPE} indicating whether the * value contains a URL and whether we are at the start of the URL or past * the start. We are at the start of the URL if and only if one of the two * conditions below is true: *

* *

Examples: *

* * @param value {@code String} to parse * @return {@link HtmlUtils.META_REDIRECT_TYPE} indicating the presence * of a URL in the given value */ public static META_REDIRECT_TYPE parseContentAttributeForUrl(String value) { if (value == null) return META_REDIRECT_TYPE.NONE; Matcher matcher = META_REDIRECT_PATTERN.matcher(value); if (!matcher.find()) return META_REDIRECT_TYPE.NONE; // We have more content. if (value.length() > matcher.end()) return META_REDIRECT_TYPE.URL; return META_REDIRECT_TYPE.URL_START; } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/util/EntityResolver.java0000644000175000017500000002205511427052602027110 0ustar chrischris/* * 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.streamhtmlparser.util; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import java.util.Map; /** *

Decodes (unescapes) HTML entities with the complication that these * are received one character at a time hence must be stored temporarily. * Also, we may receive some "junk" characters before the actual * entity which we will discard. * *

This class is designed to be 100% compatible with the corresponding * logic in the C-version of the * {@link com.google.security.streamhtmlparser.HtmlParser}, found * in htmlparser.c. There are however a few intentional * differences outlines below: *

* *

Valid HTML entities have one of the following three forms: *

* *

A reset method is provided to facilitate object re-use. */ public class EntityResolver { /** * Returned in processChar method. *

*

*/ public enum Status { NOT_STARTED("Not Started"), IN_PROGRESS("In Progress"), COMPLETED("Completed"); private final String message; private Status(String message) { this.message = message; } /** * Returns a brief description of the {@code Status} for * debugging purposes. The format of the returned {@code String} * is not fully specified nor guaranteed to remain the same. */ @Override public String toString() { return message; } } /** * How many characters to store as we are processing an entity. Once we * reach that size, we know the entity is definitely invalid. The size * is higher than needed but keeping it as-is for compatibility with * the C-version. */ private static final int MAX_ENTITY_SIZE = 10; /** * Map containing the recognized HTML entities and their decoded values. * The trailing ';' is not included in the key but it is accounted for. */ private static final Map HTML_ENTITIES_MAP = new ImmutableMap.Builder() .put("<", "<") .put(">", ">") .put("&", "&") .put("&apos", "'") .build(); /** Storage for received until characters until an HTML entity is complete. */ private final StringBuilder sb; /** * Indicates the state we are in. see {@link EntityResolver.Status}. */ private Status status; private String entity; /** * Constructs an entity resolver that is initially empty and * with status {@code NOT_STARTED}, see {@link EntityResolver.Status}. * */ public EntityResolver() { sb = new StringBuilder(); status = Status.NOT_STARTED; entity = ""; } /** * Constructs an entity resolver that is an exact copy of * the one provided. In particular it has the same contents * and status. * * @param aEntityResolver the entity resolver to copy */ public EntityResolver(EntityResolver aEntityResolver) { sb = new StringBuilder(); sb.replace(0, sb.length(), aEntityResolver.sb.toString()); entity = aEntityResolver.entity; status = aEntityResolver.status; } /** * Returns the object to its original state for re-use, deleting any * stored characters that may be present. */ public void reset() { status = Status.NOT_STARTED; sb.setLength(0); entity = ""; } /** * Returns the full state of the StreamEntityResolver * in a human readable form. The format of the returned String * is not specified and is subject to change. * * @return full state of this object */ @Override public String toString() { return String.format("Status: %s; Contents (%d): %s", status.toString(), sb.length(), sb.toString()); } /** * Returns the decoded HTML Entity. Should only be called * after {@code processChar} returned status {@code COMPLETED}. * * @return the decoded HTML Entity or an empty {@code String} if * we were called with any status other than {@code COMPLETED} */ public String getEntity() { return entity; } /** * Processes a character from the input stream and decodes any html entities * from that processed input stream. * * @param input the {@code char} to process * @return the processed {@code String}. Typically returns an empty * {@code String} while awaiting for more characters to complete * processing of the entity. */ public Status processChar(char input) { // Developer error if the precondition fails. Preconditions.checkState(status != Status.NOT_STARTED || sb.length() == 0); if (status == Status.NOT_STARTED) { if (input == '&') { sb.append(input); status = Status.IN_PROGRESS; } } else if (status == Status.IN_PROGRESS) { if ((input == ';') || (HtmlUtils.isHtmlSpace(input))) { status = Status.COMPLETED; entity = convertEntity(input); } else { if (sb.length() < MAX_ENTITY_SIZE) { sb.append(input); } else { status = Status.COMPLETED; entity = uncovertedInput(input); } } } else { // Status.COMPLETED, ignore character, do nothing. } return status; } /** * Performs the decoding of a complete HTML entity and saves the * result back into the buffer. * * Numeric Character References * * @param terminator the last character read, unused on successful * conversions since it is the end delimiter of the entity * @return The decoded entity or the original input if we could not decode it. */ private String convertEntity(char terminator) { // Developer error if the buffer was empty or does not start with '&'. Preconditions.checkArgument(sb.length() > 0); Preconditions.checkArgument(sb.charAt(0) == '&'); if (sb.length() > 1) { if (sb.charAt(1) == '#') { if (sb.length() <= 2) { // Error => return content as-is. return uncovertedInput(terminator); } try { if ((sb.charAt(2) == 'x') || (sb.charAt(2) == 'X')) { // Hex NCR return new String(Character.toChars( Integer.parseInt(sb.substring(3), 16))); } else { // Decimal NCR return new String(Character.toChars( Integer.parseInt(sb.substring(2)))); } } catch (NumberFormatException e) { return uncovertedInput(terminator); } } // See if it matches any of the few recognized entities. String key = sb.toString(); if (HTML_ENTITIES_MAP.containsKey(key)) { return HTML_ENTITIES_MAP.get(key); } } // Covers the case of a lonely '&' given or valid/invalid unknown entities. return uncovertedInput(terminator); } private String uncovertedInput(char terminator) { return String.format("%s%c", sb.toString(), terminator); } } jsilver-1.0.0.dfsg.orig/src/com/google/streamhtmlparser/HtmlParserFactory.java0000644000175000017500000002724611427052602026555 0ustar chrischris/* * 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.streamhtmlparser; import com.google.streamhtmlparser.impl.HtmlParserImpl; import java.util.Set; import java.util.logging.Logger; /** * A factory class to obtain instances of an {@link HtmlParser}. * Currently each instance is a new object given these are fairly * light-weight. * *

In the unlikely case that this class fails to initialize properly * (a developer error), an error is emitted to the error console and the logs * and the specialized parser creation methods will throw * an {@link AssertionError} on all invokations. */ public class HtmlParserFactory { private static final Logger logger = Logger.getLogger(HtmlParserFactory.class.getName()); /** * To provide additional options when creating an {@code HtmlParser} using * {@link HtmlParserFactory#createParserInAttribute(HtmlParser.ATTR_TYPE, * boolean, Set)} */ public enum AttributeOptions { /** * Indicates that the attribute value is Javascript-quoted. Only takes * effect for Javascript-accepting attributes - as identified by * {@link HtmlParser.ATTR_TYPE#JS} - and only when the attribute is also * HTML quoted. */ JS_QUOTED, /** * Indicates the attribute value is only a part of a URL as opposed to a * full URL. In particular, the value is not at the start of a URL and * hence does not necessitate validation of the URL scheme. * Only valid for URI-accepting attributes - as identified by * {@link HtmlParser.ATTR_TYPE#URI}. */ URL_PARTIAL, } /** * To provide additional options when creating an {@code HtmlParser} using * {@link HtmlParserFactory#createParserInMode(HtmlParser.Mode, Set)} */ public enum ModeOptions { /** * Indicates that the parser is inside a quoted {@code String}. Only * valid in the {@link HtmlParser.Mode#JS} mode. */ JS_QUOTED } private static final HtmlParser parserInDefaultAttr = createParser(); private static final HtmlParser parserInDefaultAttrQ = createParser(); private static final HtmlParser parserInUriAttrComplete = createParser(); private static final HtmlParser parserInUriAttrQComplete = createParser(); private static final HtmlParser parserInUriAttrPartial = createParser(); private static final HtmlParser parserInUriAttrQPartial = createParser(); private static final HtmlParser parserInJsAttr = createParser(); private static final HtmlParser parserInJsAttrQ = createParser(); private static final HtmlParser parserInQJsAttr = createParser(); private static final HtmlParser parserInStyleAttr = createParser(); private static final HtmlParser parserInStyleAttrQ = createParser(); private static final HtmlParser parserInJsQ = createParser(); /** * Protects all the createParserXXX methods by throwing a run-time exception * if this class failed to initialize properly. */ private static boolean initSuccess = false; static { try { initializeParsers(); initSuccess = true; } catch (ParseException e) { // Log a severe error and print it to stderr along with a stack trace. String error = HtmlParserFactory.class.getName() + " Failed initialization: " + e.getMessage(); logger.severe(error); System.err.println(error); e.printStackTrace(); } } // Static class. private HtmlParserFactory() { } // COV_NF_LINE /** * Returns an {@code HtmlParser} object ready to parse HTML input. * * @return an {@code HtmlParser} in the provided mode */ public static HtmlParser createParser() { return new HtmlParserImpl(); } /** * Returns an {@code HtmlParser} object initialized with the * requested Mode. Provide non {@code null} options to provide * a more precise initialization with the desired Mode. * * @param mode the mode to reset the parser with * @param options additional options or {@code null} for none * @return an {@code HtmlParser} in the provided mode * @throws AssertionError when this class failed to initialize */ public static HtmlParser createParserInMode(HtmlParser.Mode mode, Set options) { requireInitialized(); if (options != null && options.contains(ModeOptions.JS_QUOTED)) return createParser(parserInJsQ); // With no options given, this method is just a convenience wrapper for // the two calls below. HtmlParser parser = new HtmlParserImpl(); parser.resetMode(mode); return parser; } /** * Returns an {@code HtmlParser} that is a copy of the one * supplied. It holds the same internal state and hence can * proceed with parsing in-lieu of the supplied parser. * * @param aHtmlParser a {@code HtmlParser} to copy from * @return an {@code HtmlParser} that is a copy of the provided one * @throws AssertionError when this class failed to initialize */ public static HtmlParser createParser(HtmlParser aHtmlParser) { requireInitialized(); // Should never get a ClassCastException since there is only one // implementation of the HtmlParser interface. return new HtmlParserImpl((HtmlParserImpl) aHtmlParser); } /** * A very specialized {@code HtmlParser} accessor that returns a parser * in a state where it expects to read the value of an attribute * of an HTML tag. This is only useful when the parser has not seen a * certain HTML tag and an attribute name and needs to continue parsing * from a state as though it has. * *

For example, to create a parser in a state akin to that * after the parser has parsed "<a href=\"", invoke: *

   *   createParserInAttribute(HtmlParser.ATTR_TYPE.URI, true)}
   * 
* *

You must provide the proper value of quoting or the parser * will go into an unexpected state. * As a special-case, when called with the {@code HtmlParser.ATTR_TYPE} * of {@code HtmlParser.ATTR_TYPE.NONE}, the parser is created in a state * inside an HTML tag where it expects an attribute name not an attribute * value. It becomes equivalent to a parser initialized in the * {@code HTML_IN_TAG} mode. * * @param attrtype the attribute type which the parser should be in * @param quoted whether the attribute value is enclosed in double quotes * @param options additional options or {@code null} for none * @return an {@code HtmlParser} initialized in the given attribute type * and quoting * @throws AssertionError when this class failed to initialize */ public static HtmlParser createParserInAttribute( HtmlParser.ATTR_TYPE attrtype, boolean quoted, Set options) { requireInitialized(); HtmlParser parser; switch (attrtype) { case REGULAR: parser = createParser( quoted ? parserInDefaultAttrQ : parserInDefaultAttr); break; case URI: if (options != null && options.contains(AttributeOptions.URL_PARTIAL)) parser = createParser( quoted ? parserInUriAttrQPartial : parserInUriAttrPartial); else parser = createParser( quoted ? parserInUriAttrQComplete : parserInUriAttrComplete); break; case JS: // Note: We currently do not support the case of the value being // inside a Javascript quoted string that is in an unquoted HTML // attribute, such as . // It would be simple to add but currently we assume Javascript // quoted attribute values are always HTML quoted. if (quoted) { if (options != null && options.contains(AttributeOptions.JS_QUOTED)) parser = createParser(parserInQJsAttr); else parser = createParser(parserInJsAttrQ); } else { parser = createParser(parserInJsAttr); } break; case STYLE: parser = createParser( quoted ? parserInStyleAttrQ : parserInStyleAttr); break; case NONE: parser = createParserInMode(HtmlParser.Mode.HTML_IN_TAG, null); break; default: throw new IllegalArgumentException( "Did not recognize ATTR_TYPE given: " + attrtype); } return parser; } /** * Initializes a set of static parsers to be subsequently used * by the various createParserXXX methods. * The parsers are set to their proper states by making them parse * an appropriate HTML input fragment. This approach is the most likely * to ensure all their internal state is consistent. * *

In the very unexpected case of the parsing failing (developer error), * this class will fail to initialize properly. * *

In addition: *

* * @throws ParseException if parsing failed. */ private static void initializeParsers() throws ParseException { parserInDefaultAttr.parse(") // and a fake query parameter. final String fakeUrlPrefix = "http://example.com/fakequeryparam="; parserInUriAttrPartial.parse(" newChildIterator() { return new DelegatedIterator(getDelegate().getChildren().iterator()); } /** * Single Iterable object for each node. All it does is return a DelegatedIterator when asked for * iterator. */ private final Iterable delegatedIterable = new Iterable() { public Iterator iterator() { return newChildIterator(); } }; @Override public Iterable getChildren() { return delegatedIterable; } @Override public Data getChild(String path) { return newInstance(getDelegate().getChild(path)); } @Override public Data createChild(String path) { return newInstance(getDelegate().createChild(path)); } @Override public void removeTree(String path) { getDelegate().removeTree(path); } @Override public void setSymlink(String sourcePath, String destinationPath) { getDelegate().setSymlink(sourcePath, destinationPath); } @Override public void setSymlink(String sourcePath, Data destination) { destination = unwrap(destination); getDelegate().setSymlink(sourcePath, destination); } @Override public void setSymlink(Data symLink) { symLink = unwrap(symLink); getDelegate().setSymlink(symLink); } @Override public Data getSymlink() { return newInstance(getDelegate().getSymlink()); } @Override public void copy(String toPath, Data from) { from = unwrap(from); getDelegate().copy(toPath, from); } @Override public void copy(Data from) { from = unwrap(from); getDelegate().copy(from); } @Override public String getValue(String path, String defaultValue) { return getDelegate().getValue(path, defaultValue); } @Override public int getIntValue(String path, int defaultValue) { return getDelegate().getIntValue(path, defaultValue); } @Override public String getValue(String path) { return getDelegate().getValue(path); } @Override public int getIntValue(String path) { return getDelegate().getIntValue(path); } @Override public boolean getBooleanValue(String path) { return getDelegate().getBooleanValue(path); } @Override public void setValue(String path, String value) { getDelegate().setValue(path, value); } @Override public String toString() { return getDelegate().toString(); } @Override public void toString(StringBuilder out, int indent) { getDelegate().toString(out, indent); } @Override public void write(Appendable out, int indent) throws IOException { getDelegate().write(out, indent); } @Override public void optimize() { getDelegate().optimize(); } @Override public void setEscapeMode(EscapeMode mode) { getDelegate().setEscapeMode(mode); } @Override public EscapeMode getEscapeMode() { return getDelegate().getEscapeMode(); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/NoOpStringInternStrategy.java0000644000175000017500000000153611427052602031410 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/DefaultHdfParser.java0000644000175000017500000001130111427052602027615 0ustar chrischris/* * 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(); } }; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/DefaultData.java0000644000175000017500000000157011427052602026617 0ustar chrischris/* * 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 { } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/HDFDataFactory.java0000644000175000017500000000523211427052602027163 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/NewHdfParser.java0000644000175000017500000005562111427052602026777 0ustar chrischris/* * 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); } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/AbstractData.java0000644000175000017500000001015311427052602026773 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/NativeStringInternStrategy.java0000644000175000017500000000163111427052602031757 0ustar chrischris/* * 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(); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/UniqueStack.java0000644000175000017500000001033611427052602026675 0ustar chrischris/* * 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()); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/ParserFactory.java0000644000175000017500000000147011427052602027224 0ustar chrischris/* * 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(); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/DefaultDataContext.java0000644000175000017500000002612711427052602030171 0ustar chrischris/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.io.IOException; import java.util.Map; /** * This is the basic implementation of the DataContext. It stores the root Data node and a stack of * Data objects that hold local variables. By definition, local variables are limited to single HDF * names, with no '.' allowed. We use this to limit our search in the stack for the first occurence * of the first chunk in the variable name. */ public class DefaultDataContext implements DataContext { /** * Root node of the Data structure used by the current context. */ private final Data rootData; /** * Head of the linked list of local variables, starting with the newest variable created. */ private LocalVariable head = null; /** * Indicates whether the renderer has pushed a new variable scope but no variable has been created * yet. */ private boolean newScope = false; public DefaultDataContext(Data data) { if (data == null) { throw new IllegalArgumentException("rootData is null"); } this.rootData = data; } @Override public Data getRootData() { return rootData; } /** * Starts a new variable scope. It is illegal to call this twice in a row without declaring a * local variable. */ @Override public void pushVariableScope() { if (newScope) { throw new IllegalStateException("PushVariableScope called twice with no " + "variables declared in between."); } newScope = true; } /** * Removes the current variable scope and references to the variables in it. It is illegal to call * this more times than {@link #pushVariableScope} has been called. */ @Override public void popVariableScope() { if (newScope) { // We pushed but created no local variables. newScope = false; } else { // Note, this will throw a NullPointerException if there is no scope to // pop. head = head.nextScope; } } @Override public void createLocalVariableByValue(String name, String value) { createLocalVariableByValue(name, value, EscapeMode.ESCAPE_NONE); } @Override public void createLocalVariableByValue(String name, String value, EscapeMode mode) { LocalVariable local = createLocalVariable(name); local.value = value; local.isPath = false; local.setEscapeMode(mode); } @Override public void createLocalVariableByValue(String name, String value, boolean isFirst, boolean isLast) { LocalVariable local = createLocalVariable(name); local.value = value; local.isPath = false; local.isFirst = isFirst; local.isLast = isLast; } @Override public void createLocalVariableByPath(String name, String path) { LocalVariable local = createLocalVariable(name); local.value = path; local.isPath = true; } private LocalVariable createLocalVariable(String name) { if (head == null && !newScope) { throw new IllegalStateException("Must call pushVariableScope before " + "creating local variable."); } // First look for a local variable with the same name in the current scope // and return that if it exists. If we don't do this then loops and each // can cause the list of local variables to explode. // // We only look at the first local variable (head) if it is part of the // current scope (we're not defining a new scope). Since each and loop // variables are always in their own scope, there would only be one variable // in the current scope if it was a reuse case. For macro calls (which are // the only other way createLocalVariable is called multiple times in a // single scope and thus head may not be the only local variable in the // current scope) it is illegal to use the same argument name more than // once. Therefore we don't need to worry about checking to see if the new // local variable name matches beyond the first local variable in the // current scope. if (!newScope && head != null && name.equals(head.name)) { // Clear out the fields that aren't set by the callers. head.isFirst = true; head.isLast = true; head.node = null; return head; } LocalVariable local = new LocalVariable(); local.name = name; local.next = head; if (newScope) { local.nextScope = head; newScope = false; } else if (head != null) { local.nextScope = head.nextScope; } else { local.nextScope = null; } head = local; return local; } @Override public Data findVariable(String name, boolean create) { return findVariable(name, create, head); } @Override public EscapeMode findVariableEscapeMode(String name) { Data var = findVariable(name, false); if (var == null) { return EscapeMode.ESCAPE_NONE; } else { return var.getEscapeMode(); } } private Data findVariable(String name, boolean create, LocalVariable start) { // When traversing the stack, we first look for the first chunk of the // name. This is so we respect scope. If we are searching for 'foo.bar' // and 'foo' is defined in many scopes, we should stop searching // after the first time we find 'foo', even if that local variable does // not have a child 'bar'. String firstChunk = name; int dot = name.indexOf('.'); if (dot != -1) { firstChunk = name.substring(0, dot); } LocalVariable curr = start; while (curr != null) { if (curr.name.equals(firstChunk)) { if (curr.isPath) { // The local variable references another Data node, dereference it. if (curr.node == null) { // We haven't resolved the path yet. Do it now and cache it if // not null. Note we begin looking for the dereferenced in the next // scope. curr.node = findVariable(curr.value, create, curr.nextScope); if (curr.node == null) { // Node does not exist. Any children won't either. return null; } } // We have a reference to the Data node directly. Use it. if (dot == -1) { // This is the node we're interested in. return curr.node; } else { if (create) { return curr.node.createChild(name.substring(dot + 1)); } else { return curr.node.getChild(name.substring(dot + 1)); } } } else { // This is a literal value local variable. It has no children, nor // can it. We want to throw an error on creation of children. if (dot == -1) { return curr; } if (create) { throw new IllegalStateException("Cannot create children of a " + "local literal variable"); } else { // No children. return null; } } } curr = curr.next; } if (create) { return rootData.createChild(name); } else { return rootData.getChild(name); } } /** * This class holds the name and value/path of a local variable. Objects of this type should only * be exposed outside of this class for value-based local variables. *

* Fields are not private to avoid the performance overhead of hidden access methods used for * outer classes to access private fields of inner classes. */ private static class LocalVariable extends AbstractData { // Pointer to next LocalVariable in the list. LocalVariable next; // Pointer to the first LocalVariable in the next scope down. LocalVariable nextScope; String name; String value; // True if value represents the path to another Data variable. boolean isPath; // Once the path resolves to a valid Data node, store it here to avoid // refetching. Data node = null; // Used only for loop local variables boolean isFirst = true; boolean isLast = true; public String getName() { return name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getFullPath() { return name; } public void setAttribute(String key, String value) { throw new UnsupportedOperationException("Not allowed on local variables."); } public String getAttribute(String key) { return null; } public boolean hasAttribute(String key) { return false; } public int getAttributeCount() { return 0; } public Iterable> getAttributes() { return null; } public Data getRoot() { return null; } public Data getParent() { return null; } public boolean isFirstSibling() { return isFirst; } public boolean isLastSibling() { return isLast; } public Data getNextSibling() { throw new UnsupportedOperationException("Not allowed on local variables."); } public int getChildCount() { return 0; } public Iterable getChildren() { return null; } public Data getChild(String path) { return null; } public Data createChild(String path) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void removeTree(String path) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void setSymlink(String sourcePath, String destinationPath) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void setSymlink(String sourcePath, Data destination) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void setSymlink(Data symLink) { throw new UnsupportedOperationException("Not allowed on local variables."); } public Data getSymlink() { return this; } public void copy(String toPath, Data from) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void copy(Data from) { throw new UnsupportedOperationException("Not allowed on local variables."); } public String getValue(String path, String defaultValue) { throw new UnsupportedOperationException("Not allowed on local variables."); } public void write(Appendable out, int indent) throws IOException { for (int i = 0; i < indent; i++) { out.append(" "); } out.append(getName()).append(" = ").append(getValue()); } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/ChainedData.java0000644000175000017500000001401011427052602026557 0ustar chrischris/* * 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(); } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/DataFactory.java0000644000175000017500000000274511427052602026647 0ustar chrischris/* * 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(); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/NestedMapData.java0000644000175000017500000004404711427052602027121 0ustar chrischris/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.data; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; /** * Represents a hierarchical data set of primitives. * * This is the JSilver equivalent to ClearSilver's HDF object. * * This class has no synchronization logic. Follow the same thread-safety semantics as you would for * a java.util.ArrayList (i.e. concurrent reads allowed, but ensure you have exclusive access when * modifying - should not read whilst another thread writes). */ public class NestedMapData extends AbstractData { /** * Number of children a node can have before we bother allocating a HashMap. We currently allocate * the HashMap on the 5th child. */ private static final int CHILD_MAP_THRESHOLD = 4; private String name; private NestedMapData parent; private final NestedMapData root; // Lazily intitialized after CHILD_MAP_THRESHOLD is hit. private Map children = null; // Number of children private int childCount = 0; // First child (first sibling of children) private NestedMapData firstChild = null; // Last child (last sibling of children) private NestedMapData lastChild = null; /** * Single object returned by getChildren(). Constructs ChildrenIterator objects. */ private Iterable iterableChildren = null; // Holds the attributes for this HDF node. private Map attributeList = null; private String value = null; private NestedMapData symLink = this; // Doubly linked list of siblings. private NestedMapData prevSibling = null; private NestedMapData nextSibling = null; public NestedMapData() { name = null; parent = null; root = this; } protected NestedMapData(String name, NestedMapData parent, NestedMapData root) { this.name = name; this.parent = parent; this.root = root; } // ******************* Node creation and removal ******************* // Must be kept in sync. /** * Creates a child of this node. * * @param chunk name to give the new child node. * @return the NestedMapData object corresponding to the new node. */ protected NestedMapData createChildNode(String chunk) { NestedMapData sym = followSymLinkToTheBitterEnd(); NestedMapData data = new NestedMapData(chunk, sym, sym.root); // If the parent node's child count is 5 or more and it does not have a // children Hashmap, initialize it now. if (sym.children == null && sym.childCount >= CHILD_MAP_THRESHOLD) { sym.children = new HashMap(); // Copy in existing children. NestedMapData curr = sym.firstChild; while (curr != null) { sym.children.put(curr.getName(), curr); curr = curr.nextSibling; } } // If the parent node has a children map, add the new child node to it. if (sym.children != null) { sym.children.put(chunk, data); } data.prevSibling = sym.lastChild; if (sym.lastChild != null) { // Update previous lastChild to point to new child. sym.lastChild.nextSibling = data; } else { // There were no previous children so this is the first. sym.firstChild = data; } sym.lastChild = data; sym.childCount++; return data; } private void severNode() { if (parent == null) { return; } if (parent.children != null) { parent.children.remove(name); } if (prevSibling != null) { prevSibling.nextSibling = nextSibling; } else { parent.firstChild = nextSibling; } if (nextSibling != null) { nextSibling.prevSibling = prevSibling; } else { parent.lastChild = prevSibling; } parent.childCount--; // Need to cleal the parent pointer or else if someone has a direct reference to this node // they will get very strange results. parent = null; } // ******************* Node data ******************* /** * Returns the name of this HDF node. The root node has no name, so calling this on the root node * will return null. */ public String getName() { return name; } /** * Recursive method that builds the full path to this node via its parent links into the given * StringBuilder. */ private void getPathName(StringBuilder sb) { if (parent != null && parent != root) { parent.getPathName(sb); sb.append("."); } String name = getName(); if (name != null) { sb.append(name); } } /** * Returns the full path to this node via its parent links. */ public String getFullPath() { StringBuilder sb = new StringBuilder(); getPathName(sb); return sb.toString(); } /** * Returns the value of this HDF node, or null if this node has no value. Every node in the tree * can have a value, a child, and a next peer. */ public String getValue() { return followSymLinkToTheBitterEnd().value; } /** * Set the value of this node. Any symlink that may have been set for this node will be replaced. */ public void setValue(String value) { // Clearsilver behaviour is to replace any symlink that may already exist // here with the new value, rather than following the symlink. this.symLink = this; this.value = value; } // ******************* Attributes ******************* // We don't expect attributes to be heavily used. They are not used for template parsing. public void setAttribute(String key, String value) { if (key == null) { throw new NullPointerException("Attribute name cannot be null."); } if (attributeList == null) { // Do we need to worry about synchronization? attributeList = new HashMap(); } if (value == null) { attributeList.remove(key); } else { attributeList.put(key, value); } } public String getAttribute(String key) { return attributeList == null ? null : attributeList.get(key); } public boolean hasAttribute(String key) { return attributeList != null && attributeList.containsKey(key); } public int getAttributeCount() { return attributeList == null ? 0 : attributeList.size(); } public Iterable> getAttributes() { if (attributeList == null) { return Collections.emptySet(); } return attributeList.entrySet(); } // ******************* Children ******************* /** * Return the root of the tree where the current node lies. If the current node is the root, * return this. */ public Data getRoot() { return root; } /** * Get the parent node. */ public Data getParent() { return parent; } /** * Is this the first of its siblings? */ @Override public boolean isFirstSibling() { return prevSibling == null; } /** * Is this the last of its siblings? */ @Override public boolean isLastSibling() { return nextSibling == null; } public Data getNextSibling() { return nextSibling; } /** * Returns number of child nodes. */ @Override public int getChildCount() { return followSymLinkToTheBitterEnd().childCount; } /** * Returns children of this node. */ @Override public Iterable getChildren() { if (iterableChildren == null) { iterableChildren = new IterableChildren(); } return iterableChildren; } /** * Retrieves the object that is the root of the subtree at hdfpath, returning null if the subtree * doesn't exist */ public NestedMapData getChild(String path) { NestedMapData current = this; for (int lastDot = 0, nextDot = 0; nextDot != -1 && current != null; lastDot = nextDot + 1) { nextDot = path.indexOf('.', lastDot); String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot); current = current.followSymLinkToTheBitterEnd().getChildNode(chunk); } return current; } /** * Retrieves the HDF object that is the root of the subtree at hdfpath, create the subtree if it * doesn't exist */ public NestedMapData createChild(String path) { NestedMapData current = this; for (int lastDot = 0, nextDot = 0; nextDot != -1; lastDot = nextDot + 1) { nextDot = path.indexOf('.', lastDot); String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot); NestedMapData currentSymLink = current.followSymLinkToTheBitterEnd(); current = currentSymLink.getChildNode(chunk); if (current == null) { current = currentSymLink.createChildNode(chunk); } } return current; } /** * Non-recursive method that only returns a Data node if this node has a child whose name matches * the specified name. * * @param name String containing the name of the child to look for. * @return a Data node that is the child of this node and named 'name', otherwise {@code null}. */ private NestedMapData getChildNode(String name) { NestedMapData sym = followSymLinkToTheBitterEnd(); if (sym.getChildCount() == 0) { // No children. Just return null. return null; } if (sym.children != null) { // children map exists. Look it up there. return sym.children.get(name); } // Iterate through linked list of children and look for a name match. NestedMapData curr = sym.firstChild; while (curr != null) { if (curr.getName().equals(name)) { return curr; } curr = curr.nextSibling; } return null; } /** * Remove the specified subtree. */ public void removeTree(String path) { NestedMapData removed = getChild(path); if (removed != null) { removed.severNode(); } } private NestedMapData followSymLinkToTheBitterEnd() { NestedMapData current; for (current = this; current.symLink != current; current = current.symLink); return current; } // ******************* Symbolic links ******************* /** * Set the source node to be a symbolic link to the destination. */ public void setSymlink(String sourcePath, String destinationPath) { setSymlink(sourcePath, createChild(destinationPath)); } /** * Set the source node to be a symbolic link to the destination. */ public void setSymlink(String sourcePath, Data destination) { createChild(sourcePath).setSymlink(destination); } /** * Set this node to be a symbolic link to another node. */ public void setSymlink(Data symLink) { if (symLink instanceof NestedMapData) { this.symLink = (NestedMapData) symLink; } else { String errorMessage = "Cannot set symlink of incompatible Data type: " + symLink.getClass().getName(); if (symLink instanceof ChainedData) { errorMessage += "\nOther type is ChainedData indicating there are " + "multiple valid Data nodes for the path: " + symLink.getFullPath(); } throw new IllegalArgumentException(errorMessage); } } /** * Retrieve the symbolic link this node points to. Will return reference to self if not a symlink. */ public Data getSymlink() { return symLink; } // ************************ Copy ************************* public void copy(String toPath, Data from) { if (toPath == null) { throw new NullPointerException("Invalid copy destination path"); } if (from == null) { // Is this a nop or should we throw an error? return; } Data to = createChild(toPath); to.copy(from); } public void copy(Data from) { if (from == null) { // Is this a nop or should we throw an error? return; } // Clear any existing symlink. this.symLink = this; // Clear any existing attributes and copy the ones from the source. if (this.attributeList != null) { this.attributeList.clear(); } for (Map.Entry attribute : from.getAttributes()) { setAttribute(attribute.getKey(), attribute.getValue()); } // If the source node was a symlink, just copy the link over and we're done. if (from.getSymlink() != from) { setSymlink(from.getSymlink()); return; } // Copy over the node's value. setValue(from.getValue()); // For each source child, create a new child with the same name and recurse. for (Data fromChild : from.getChildren()) { Data toChild = createChild(fromChild.getName()); toChild.copy(fromChild); } } /** * Write out the String representation of this HDF node. */ public void write(Appendable out, int indent) throws IOException { if (symLink != this) { indent(out, indent); writeNameAttrs(out); out.append(" : ").append(symLink.getFullPath()).append('\n'); return; } if (getValue() != null) { indent(out, indent); writeNameAttrs(out); if (getValue().contains("\n")) { writeMultiline(out); } else { out.append(" = ").append(getValue()).append('\n'); } } if (getChildCount() > 0) { int childIndent = indent; if (this != root) { indent(out, indent); writeNameAttrs(out); out.append(" {\n"); childIndent++; } for (Data child : getChildren()) { child.write(out, childIndent); } if (this != root) { indent(out, indent); out.append("}\n"); } } } /** * Here we optimize the structure for long-term use. We call intern() on all Strings to reduce the * copies of the same string that appear */ @Override public void optimize() { name = name == null ? null : name.intern(); value = value == null ? null : value.intern(); if (attributeList != null) { Map newList = new HashMap(attributeList.size()); for (Map.Entry entry : attributeList.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); key = key == null ? null : key.intern(); value = value == null ? null : value.intern(); newList.put(key, value); } attributeList = newList; } for (NestedMapData child = firstChild; child != null; child = child.nextSibling) { child.optimize(); } } private void writeMultiline(Appendable out) throws IOException { String marker = "EOM"; while (getValue().contains(marker)) { marker += System.nanoTime() % 10; } out.append(" << ").append(marker).append('\n').append(getValue()); if (!getValue().endsWith("\n")) { out.append('\n'); } out.append(marker).append('\n'); } private void indent(Appendable out, int indent) throws IOException { for (int i = 0; i < indent; i++) { out.append(" "); } } private void writeNameAttrs(Appendable out) throws IOException { // Output name out.append(getName()); if (attributeList != null && !attributeList.isEmpty()) { // Output parameters. out.append(" ["); boolean first = true; for (Map.Entry attr : attributeList.entrySet()) { if (first) { first = false; } else { out.append(", "); } out.append(attr.getKey()); if (attr.getValue().equals("1")) { continue; } out.append(" = \""); writeAttributeValue(out, attr.getValue()); out.append('"'); } out.append(']'); } } // Visible for testing static void writeAttributeValue(Appendable out, String value) throws IOException { for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); switch (c) { case '"': out.append("\\\""); break; case '\n': out.append("\\n"); break; case '\t': out.append("\\t"); break; case '\\': out.append("\\\\"); break; case '\r': out.append("\\r"); break; default: out.append(c); } } } /** * A single instance of this is created per NestedMapData object. Its single method returns an * iterator over the children of this node. *

* Note: This returns an iterator that starts with the first child at the time of iterator() being * called, not when this Iterable object was handed to the code. This might result in slightly * unexpected behavior if the children list is modified between when getChildren() is called and * iterator is called on the returned object but this should not be an issue in practice as * iterator is usually called immediately after getChildren(). * */ private class IterableChildren implements Iterable { public Iterator iterator() { return new ChildrenIterator(followSymLinkToTheBitterEnd().firstChild); } } /** * Iterator implementation for children. We do not check for concurrent modification like in other * Collection iterators. */ private static class ChildrenIterator implements Iterator { NestedMapData current; NestedMapData next; ChildrenIterator(NestedMapData first) { next = first; current = null; } public boolean hasNext() { return next != null; } public NestedMapData next() { if (next == null) { throw new NoSuchElementException(); } current = next; next = next.nextSibling; return current; } public void remove() { if (current == null) { throw new NoSuchElementException(); } current.severNode(); current = null; } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/DataContext.java0000644000175000017500000001160211427052602026654 0ustar chrischris/* * 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); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/TypeConverter.java0000644000175000017500000001203211427052602027245 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/UnmodifiableData.java0000644000175000017500000000700111427052602027624 0ustar chrischris/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.util.Iterator; /** * Data wrapper that prevents modifying the delegated Data node or its tree. */ public class UnmodifiableData extends DelegatedData { private static final String MODIFICATION_ERROR_MSG = "Cannot modify this Data object."; public UnmodifiableData(Data delegate) { super(delegate); } @Override protected DelegatedData newInstance(Data newDelegate) { return newDelegate == null ? null : new UnmodifiableData(newDelegate); } @Override public void copy(Data from) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void copy(String toPath, Data from) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } /** * {@link #createChild} calls {@link #getChild} and throws {@link UnsupportedOperationException} * if no object was found. */ @Override public Data createChild(String path) { // Check if child already exists Data child = getChild(path); if (child == null) { // If the child described by path does not exist we throw // an exception as we cannot create a new one. throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } return child; } protected class UnmodifiableIterator extends DelegatedIterator { UnmodifiableIterator(Iterator iterator) { super(iterator); } public void remove() { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } } /** * Override in order to not allow modifying children with remove(). */ @Override protected Iterator newChildIterator() { return new UnmodifiableIterator(getDelegate().getChildren().iterator()); } // Below we disable modification operations. @Override public void setSymlink(String sourcePath, Data destination) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setSymlink(String sourcePath, String destinationPath) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setSymlink(Data symLink) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setAttribute(String key, String value) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void removeTree(String path) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setValue(String path, String value) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setValue(String value) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } @Override public void setEscapeMode(EscapeMode mode) { throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/LocalAndGlobalData.java0000644000175000017500000000605311427052602030032 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/Parser.java0000644000175000017500000000441611427052602025677 0ustar chrischris/* * 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; } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/Data.java0000644000175000017500000002011711427052602025310 0ustar chrischris/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.data; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import java.io.IOException; import java.util.Map; /** * Represents a hierarchical data set of primitives. * * This is the JSilver equivalent to ClearSilver's HDF object. */ public interface Data { // ******************* Node data ******************* /** * Returns the name of this HDF node. The root node has no name, so calling this on the root node * will return null. */ String getName(); /** * Returns the value of this HDF node, or null if this node has no value. Every node in the tree * can have a value, a child, and a next peer. */ String getValue(); /** * Returns the integer value of this HDF node, or 0 if this node has no value. * * Note: The fact that this method returns a primitive type, rather than an Integer means that its * value cannot be used to determine whether the data node exists or not. Note also that, when * implementing a Data object that caches these values, care must be taken to ensure that a node * with an integer value of '0' is not mistaken for a non-existent node. */ int getIntValue(); /** * Returns the boolean value of this HDF node, or false if this node has no value. * * Note: The fact that this method returns a primitive type, rather than a Boolean means that its * value cannot be used to determine whether the data node exists or not. Note also that, when * implementing a Data object that caches these values, care must be taken to ensure that a node * with a boolean value of 'false' is not mistaken for a non-existent node. */ boolean getBooleanValue(); /** * Set the value of this node. Any symlink that may have been set for this node will be replaced. */ void setValue(String value); /** * Returns the full path to this node via its parent links. */ String getFullPath(); // ******************* Attributes ******************* /** * Sets an attribute key and value on the current node, replacing any existing value. * * @param key the name of the attribute to add/modify. * @param value the value to assign it. Value of {@code null} will clear the attribute. */ void setAttribute(String key, String value); /** * Returns the value of the node attribute with the given name, or {@code null} if there is no * value. */ String getAttribute(String key); /** * Returns {@code true} if the node contains an attribute with the given name, {@code false} * otherwise. */ boolean hasAttribute(String key); /** * Returns the number of attributes on this node. */ int getAttributeCount(); /** * Returns an iterable collection of attribute name/value pairs. * * @return an object that can be iterated over to get all the attribute name/value pairs. Should * return empty iterator if there are no attributes. */ Iterable> getAttributes(); // ******************* Children ******************* /** * Return the root of the tree where the current node lies. If the current node is the root, * return this. */ Data getRoot(); /** * Get the parent node. */ Data getParent(); /** * Is this the first of its siblings? */ boolean isFirstSibling(); /** * Is this the last of its siblings? */ boolean isLastSibling(); /** * Retrieves the node representing the next sibling of this Data node, if any. * * @return the next sibling Data object or {@code null} if this is the last sibling. */ Data getNextSibling(); /** * Returns number of child nodes. */ int getChildCount(); /** * Returns children of this node. */ Iterable getChildren(); /** * Retrieves the object that is the root of the subtree at hdfpath, returning null if the subtree * doesn't exist */ Data getChild(String path); /** * Retrieves the HDF object that is the root of the subtree at hdfpath, create the subtree if it * doesn't exist */ Data createChild(String path); /** * Remove the specified subtree. */ void removeTree(String path); // ******************* Symbolic links ******************* /** * Set the source node to be a symbolic link to the destination. */ void setSymlink(String sourcePath, String destinationPath); /** * Set the source node to be a symbolic link to the destination. */ void setSymlink(String sourcePath, Data destination); /** * Set this node to be a symbolic link to another node. */ void setSymlink(Data symLink); /** * Retrieve the symbolic link this node points to. Will return reference to self if not a symlink. */ Data getSymlink(); // **************************** Copy ************************** /** * Does a deep copy of the attributes and values from one node to another. * * @param toPath destination path for the deep copy. * @param from Data object that should be copied over. */ void copy(String toPath, Data from); /** * Does a deep copy the attributes and values from one node to another * * @param from Data object whose value should be copied over. */ void copy(Data from); // ******************* Convenience methods ******************* /** * Retrieves the value at the specified path in this HDF node's subtree. */ String getValue(String path, String defaultValue); /** * Retrieves the integer value at the specified path in this HDF node's subtree. If the value does * not exist, or cannot be converted to an integer, default_value will be returned. */ int getIntValue(String path, int defaultValue); /** * Retrieves the value at the specified path in this HDF node's subtree. If not found, returns * null. */ String getValue(String path); /** * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid, * returns 0. */ int getIntValue(String path); /** * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid, * returns false. */ boolean getBooleanValue(String path); /** * Sets the value at the specified path in this HDF node's subtree. */ void setValue(String path, String value); // ******************* String representation ******************* String toString(); void toString(StringBuilder out, int indent); /** * Write out the String representation of this HDF node. */ void write(Appendable out, int indent) throws IOException; /** * Optimizes the Data structure for performance. This is a somewhat expensive operation that * should improve CPU and/or memory usage for long-lived Data objects. For example, it may * internalize all Strings to reduce redundant copies. */ void optimize(); /** * Indicates the escaping, if any that was applied to this HDF node. * * @return EscapeMode that was applied to this node's value. {@code EscapeMode.ESCAPE_NONE} if the * value is not escaped. {@code EscapeMode.ESCAPE_IS_CONSTANT} if value is a string or * numeric literal. * * @see #setEscapeMode * @see EscapeMode */ EscapeMode getEscapeMode(); /** * Set the escaping that was applied to this HDF node. This method may be called by the template * renderer, for instance, when a "set" command sets the node to a constant string. It may also be * explicitly called if populating the HDF with pre-escaped or trusted values. * * @see #getEscapeMode */ void setEscapeMode(EscapeMode mode); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/data/StringInternStrategy.java0000644000175000017500000000264411427052602030615 0ustar chrischris/* * 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); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/0000755000175000017500000000000011767454572025250 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplate.java0000644000175000017500000000625611427052602032062 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/TemplateInterpreter.java0000644000175000017500000005725511427052602032105 0ustar chrischris/* * 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()); } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/LoadingTemplateFactory.java0000644000175000017500000000351311427052602032473 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/OptimizingTemplateFactory.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/OptimizingTemplateFactory.jav0000644000175000017500000000460611427052602033112 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/InterpretedMacro.java0000644000175000017500000000745211427052602031347 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/OptimizerProvider.java0000644000175000017500000000163611427052602031573 0ustar chrischris/* * 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(); } ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplateLoader.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplateLoader.jav0000644000175000017500000000475111427052602033046 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/ExpressionEvaluator.java0000644000175000017500000002161311427052602032115 0ustar chrischris/* * 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(); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/TemplateFactory.java0000644000175000017500000000346211427052602031200 0ustar chrischris/* * 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); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/interpreter/VariableLocator.java0000644000175000017500000000737711427052602031157 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/adaptor/0000755000175000017500000000000011767454572024337 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/adaptor/LoadPathToFileCache.java0000644000175000017500000001214611427052602030706 0ustar chrischris/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.adaptor; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * This class implements a cache of a list of loadpaths and a file name to the absolute file name * where the file is located on the filesystem. The purpose is to avoid filesystem calls for common * operations, like in which of these directories does this file exist? This class is threadsafe. * * Some of this code is copied from {@link com.google.clearsilver.base.CSFileCache}. */ public class LoadPathToFileCache { private final LRUCache cache; private final ReadWriteLock cacheLock = new ReentrantReadWriteLock(); public LoadPathToFileCache(int capacity) { cache = new LRUCache(capacity); } /** * Lookup in the cache to see if we have a mapping from the given loadpaths and filename to an * absolute file path. * * @param loadPaths the ordered list of directories to search for the file. * @param filename the name of the file. * @return the absolute filepath location of the file, or {@code null} if not in the cache. */ public String lookup(List loadPaths, String filename) { String filePathMapKey = makeCacheKey(loadPaths, filename); cacheLock.readLock().lock(); try { return cache.get(filePathMapKey); } finally { cacheLock.readLock().unlock(); } } /** * Add a new mapping to the cache. * * @param loadPaths the ordered list of directories to search for the file. * @param filename the name of the file. * @param filePath the absolute filepath location of the file */ public void add(List loadPaths, String filename, String filePath) { String filePathMapKey = makeCacheKey(loadPaths, filename); cacheLock.writeLock().lock(); try { cache.put(filePathMapKey, filePath); } finally { cacheLock.writeLock().unlock(); } } public static String makeCacheKey(List loadPaths, String filename) { if (loadPaths == null) { throw new NullPointerException("Loadpaths cannot be null"); } if (filename == null) { throw new NullPointerException("Filename cannot be null"); } String loadPathsHash = Long.toHexString(hashLoadPath(loadPaths)); StringBuilder sb = new StringBuilder(loadPathsHash); sb.append('|').append(filename); return sb.toString(); } /** * Generate a hashCode to represent the ordered list of loadpaths. Used as a key into the fileMap * since a concatenation of the loadpaths is likely to be huge (greater than 1K) but very * repetitive. Algorithm comes from Effective Java by Joshua Bloch. *

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

* Necessary to override because HashMap increases the capacity of the hashtable before * inserting the elements. However, here we have set the max capacity already and will instead * remove eldest elements instead of increasing capacity. */ @Override public void putAll(Map m) { for (Map.Entry e : m.entrySet()) { put(e.getKey(), e.getValue()); } } /** * This method is called by LinkedHashMap to check whether the eldest entry should be removed. * * @param eldest * @return true if element should be removed. */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > capacity; } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/adaptor/JCs.java0000644000175000017500000001277411427052602025651 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/adaptor/ResourceLoaderAdaptor.java0000644000175000017500000001475511427052602031424 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/adaptor/JSilverFactory.java0000644000175000017500000000647711427052602030103 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/adaptor/JHdf.java0000644000175000017500000001566711427052602026011 0ustar chrischris/* * 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(); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/0000755000175000017500000000000011767454572024715 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/0000755000175000017500000000000011767454572025661 5ustar chrischris././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/BaseUrlValidateFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/BaseUrlValidateFunction.ja0000644000175000017500000000656511427052602032703 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/HtmlUrlValidateFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/HtmlUrlValidateFunction.ja0000644000175000017500000000262411427052602032725 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/CssUrlValidateFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/CssUrlValidateFunction.jav0000644000175000017500000000460111427052602032734 0ustar chrischris/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.functions.html; import java.io.IOException; /** * Validates that input string is a valid URI. If it is not valid, the string {@code #} is returned. * If it is valid, the characters [\n\r\\'"()<>*] are URL encoded to ensure the string can be safely * inserted in a CSS URL context. In particular: *

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

* References: *

    *
  1. CSS 2.1 URLs: http://www.w3.org/TR/CSS21/syndata.html#url *
  2. CSS 1 URLs: http://www.w3.org/TR/REC-CSS1/#url *
* * @see BaseUrlValidateFunction */ public class CssUrlValidateFunction extends BaseUrlValidateFunction { protected void applyEscaping(String in, Appendable out) throws IOException { for (int i = 0; i < in.length(); i++) { char ch = in.charAt(i); switch (ch) { case '\n': out.append("%0A"); break; case '\r': out.append("%0D"); break; case '"': out.append("%22"); break; case '\'': out.append("%27"); break; case '(': out.append("%28"); break; case ')': out.append("%29"); break; case '*': out.append("%2A"); break; case '<': out.append("%3C"); break; case '>': out.append("%3E"); break; case '\\': out.append("%5C"); break; default: out.append(ch); } } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/HtmlStripFunction.java0000644000175000017500000001541011427052602032136 0ustar chrischris/* * 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. } } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/html/TextHtmlFunction.java0000644000175000017500000002045111427052602031762 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/NonEscapingFunction.java0000644000175000017500000000142511427052602031451 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/Function.java0000644000175000017500000000171411427052602027325 0ustar chrischris/* * 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(); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/string/0000755000175000017500000000000011767454572026223 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/string/SliceFunction.java0000644000175000017500000000376511427052602031623 0ustar chrischris/* * 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()); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/string/CrcFunction.java0000644000175000017500000000341711427052602031265 0ustar chrischris/* * 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]); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/string/LengthFunction.java0000644000175000017500000000224511427052602031775 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/string/FindFunction.java0000644000175000017500000000267711427052602031445 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/0000755000175000017500000000000011767454572026733 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NotFunction.java0000644000175000017500000000203611427052602032022 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/GreaterOrEqualFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/GreaterOrEqualFunctio0000644000175000017500000000214111427052602033043 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/LessOrEqualFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/LessOrEqualFunction.j0000644000175000017500000000213611427052602032772 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NumericAddFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NumericAddFunction.ja0000644000175000017500000000214211427052602032744 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/ModuloFunction.java0000644000175000017500000000212411427052602032517 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/OrFunction.java0000644000175000017500000000212411427052602031640 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014500000000000011565 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/SubtractFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/SubtractFunction.java0000644000175000017500000000234311427052602033052 0ustar chrischris/* * 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); } } } ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NumericEqualFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NumericEqualFunction.0000644000175000017500000000214611427052602033014 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/AddFunction.java0000644000175000017500000000244711427052602031760 0ustar chrischris/* * 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()); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/AndFunction.java0000644000175000017500000000212511427052602031763 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/GreaterFunction.java0000644000175000017500000000213011427052602032646 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/ExistsFunction.java0000644000175000017500000000203511427052602032540 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014500000000000011565 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NotEqualFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NotEqualFunction.java0000644000175000017500000000212111427052602033005 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014500000000000011565 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/MultiplyFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/MultiplyFunction.java0000644000175000017500000000212611427052602033101 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/DivideFunction.java0000644000175000017500000000212411427052602032464 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/EqualFunction.java0000644000175000017500000000211511427052602032327 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NumericNotEqualFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NumericNotEqualFuncti0000644000175000017500000000215111427052602033056 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/LessFunction.java0000644000175000017500000000212511427052602032167 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/operators/NumericFunction.java0000644000175000017500000000210611427052602032662 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/0000755000175000017500000000000011767454572026155 5ustar chrischris././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/JsValidateUnquotedLiteral.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/JsValidateUnquotedLitera0000644000175000017500000000443611427052602033000 0ustar chrischris/* * 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"); } } } ././@LongLink0000000000000000000000000000014500000000000011565 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/StyleEscapeFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/StyleEscapeFunction.java0000644000175000017500000000561611427052602032734 0ustar chrischris/* * 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]); } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/NullEscapeFunction.java0000644000175000017500000000212111427052602032532 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/JsEscapeFunction.java0000644000175000017500000000454511427052602032210 0ustar chrischris/* * 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]; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/UrlEscapeFunction.java0000644000175000017500000000356311427052602032375 0ustar chrischris/* * 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); } } } ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/SimpleEscapingFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/SimpleEscapingFunction.j0000644000175000017500000000740311427052602032722 0ustar chrischris/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.functions.escape; import com.google.clearsilver.jsilver.functions.TextFilter; import java.io.IOException; /** * Base class to make writing fast, simple escaping functions easy. A simple escaping function is * one where each character in the input is treated independently and there is no runtime state. The * only decision you make is whether the current character should be escaped into some different * string or not. * * The only serious limitation on using this class it that only low valued characters can be * escaped. This is because (for speed) we use an array of escaped strings, indexed by character * value. In future this limitation may be lifted if there's a call for it. */ public abstract class SimpleEscapingFunction implements TextFilter { // The limit for how many strings we can store here (max) private static final int CHAR_INDEX_LIMIT = 256; // Our fast lookup array of escaped strings. This array is indexed by char // value so it's important not to have it grow too large. For now we have // an artificial limit on it. private String[] ESCAPE_STRINGS; /** * Creates an instance to escape the given set of characters. */ protected SimpleEscapingFunction(char[] ESCAPE_CHARS) { setEscapeChars(ESCAPE_CHARS); } protected SimpleEscapingFunction() { ESCAPE_STRINGS = new String[0]; } protected void setEscapeChars(char[] ESCAPE_CHARS) throws AssertionError { int highestChar = -1; for (char c : ESCAPE_CHARS) { if (c > highestChar) { highestChar = c; } } if (highestChar >= CHAR_INDEX_LIMIT) { throw new AssertionError("Cannot escape characters with values above " + CHAR_INDEX_LIMIT); } ESCAPE_STRINGS = new String[highestChar + 1]; for (char c : ESCAPE_CHARS) { ESCAPE_STRINGS[c] = getEscapeString(c); } } /** * Given one of the escape characters supplied to this instance's constructor, return the escape * string for it. This method does not need to be efficient. */ protected abstract String getEscapeString(char c); /** * Algorithm is as follows: *
    *
  1. Scan block for contiguous unescaped sequences *
  2. Append unescaped sequences to output *
  3. Append escaped string to output (if found) *
  4. Rinse & Repeat *
*/ @Override public void filter(String in, Appendable out) throws IOException { final int len = in.length(); int pos = 0; int start = pos; while (pos < len) { // We really hope that the hotspot compiler inlines this call properly // (without optimization it accounts for > 50% of the time in this call) final char chr = in.charAt(pos); final String escapeString; if (chr < ESCAPE_STRINGS.length && (escapeString = ESCAPE_STRINGS[chr]) != null) { // We really hope our appendable handles sub-strings nicely // (we know that StringBuilder / StringBuffer does). if (pos > start) { out.append(in, start, pos); } out.append(escapeString); pos += 1; start = pos; continue; } pos += 1; } if (pos > start) { out.append(in, start, pos); } } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/escape/HtmlEscapeFunction.java0000644000175000017500000000640611427052602032536 0ustar chrischris/* * 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 + "]"); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/EscapingFunction.java0000644000175000017500000000142111427052602030772 0ustar chrischris/* * 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; } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/numeric/0000755000175000017500000000000011767454572026357 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/numeric/MinFunction.java0000644000175000017500000000235511427052602031435 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/numeric/MaxFunction.java0000644000175000017500000000235411427052602031436 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/numeric/AbsFunction.java0000644000175000017500000000227511427052602031420 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/structure/0000755000175000017500000000000011767454572026755 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/structure/NameFunction.java0000644000175000017500000000323111427052602032162 0ustar chrischris/* * 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); } } ././@LongLink0000000000000000000000000000014500000000000011565 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/structure/SubcountFunction.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/structure/SubcountFunction.java0000644000175000017500000000271511427052602033112 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/structure/FirstFunction.java0000644000175000017500000000267611427052602032405 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/structure/LastFunction.java0000644000175000017500000000267311427052602032216 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/bundles/0000755000175000017500000000000011767454572026351 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/bundles/CoreOperators.java0000644000175000017500000000713711427052602031770 0ustar chrischris/* * 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()); } } ././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/bundles/ClearSilverCompatibleFunctions.javajsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/bundles/ClearSilverCompatibleFu0000644000175000017500000001054711427052602032766 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/FunctionRegistry.java0000644000175000017500000001112011427052602031046 0ustar chrischris/* * 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); } } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/TextFilter.java0000644000175000017500000000144111427052602027627 0ustar chrischris/* * 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; } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/functions/FunctionExecutor.java0000644000175000017500000000255211427052602031045 0ustar chrischris/* * 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); } jsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/template/0000755000175000017500000000000011767454572024520 5ustar chrischrisjsilver-1.0.0.dfsg.orig/src/com/google/clearsilver/jsilver/template/DefaultRenderingContext.java0000644000175000017500000002330411427052602032131 0ustar chrischris/* * 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.template; import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.data.DataContext; import com.google.clearsilver.jsilver.data.UniqueStack; import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException; 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.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.values.Value; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; /** * Default implementation of RenderingContext. */ public class DefaultRenderingContext implements RenderingContext, FunctionExecutor { public static final Logger logger = Logger.getLogger(DefaultRenderingContext.class.getName()); private final DataContext dataContext; private final ResourceLoader resourceLoader; private final Appendable out; private final FunctionExecutor globalFunctionExecutor; private final AutoEscapeOptions autoEscapeOptions; private final UniqueStack includeStack; private List escaperStack = new ArrayList(8); // seems like a reasonable initial // capacity. private String currentEscaper; // optimization to reduce List lookup. private List