You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by jk...@apache.org on 2006/03/11 21:55:35 UTC
svn commit: r385164 [20/32] - in /jakarta/tapestry/trunk: ./ .settings/
annotations/src/java/org/apache/tapestry/annotations/
annotations/src/test/org/apache/tapestry/annotations/ config/
contrib/src/documentation/content/xdocs/tapestry-contrib/Compone...
Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TemplateParser.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2004, 2005, 2006 The Apache Software Foundation
+// Copyright 2004, 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -35,93 +35,89 @@
/**
* Parses Tapestry templates, breaking them into a series of
- * {@link org.apache.tapestry.parse.TemplateToken tokens}. Although often
- * referred to as an "HTML template", there is no real requirement that the
- * template be HTML. This parser can handle any reasonable SGML derived markup
- * (including XML), but specifically works around the ambiguities of HTML
- * reasonably.
+ * {@link org.apache.tapestry.parse.TemplateToken tokens}. Although often referred to as an "HTML
+ * template", there is no real requirement that the template be HTML. This parser can handle any
+ * reasonable SGML derived markup (including XML), but specifically works around the ambiguities of
+ * HTML reasonably.
* <p>
- * Deployed as the tapestry.parse.TemplateParser service, using the threaded
- * model.
+ * Deployed as the tapestry.parse.TemplateParser service, using the threaded model.
* <p>
- * Dynamic markup in Tapestry attempts to be invisible. Components are arbitrary
- * tags containing a <code>jwcid</code> attribute. Such components must be
- * well balanced (have a matching close tag, or end the tag with "<code>/></code>".
+ * Dynamic markup in Tapestry attempts to be invisible. Components are arbitrary tags containing a
+ * <code>jwcid</code> attribute. Such components must be well balanced (have a matching close tag,
+ * or end the tag with "<code>/></code>".
* <p>
- * Generally, the id specified in the template is matched against an component
- * defined in the specification. However, implicit components are also possible.
- * The jwcid attribute uses the syntax "<code>@Type</code>" for implicit components. Type is the component type, and may
- * include a library id prefix. Such a component is anonymous (but is
- * given a unique id).
+ * Generally, the id specified in the template is matched against an component defined in the
+ * specification. However, implicit components are also possible. The jwcid attribute uses the
+ * syntax "<code>@Type</code>" for implicit components. Type is the component type, and may include a library id
+ * prefix. Such a component is anonymous (but is given a unique id).
* <p>
- * (The unique ids assigned start with a dollar sign, which is normally no
- * allowed for component ids ... this helps to make them stand out and
- * assures that they do not conflict with user-defined component ids.
- * These ids tend to propagate into URLs and become HTML element names and
- * even JavaScript variable names ... the dollar sign is acceptible in
- * these contexts as well).
+ * (The unique ids assigned start with a dollar sign, which is normally no allowed for
+ * component ids ... this helps to make them stand out and assures that they do not conflict
+ * with user-defined component ids. These ids tend to propagate into URLs and become HTML
+ * element names and even JavaScript variable names ... the dollar sign is acceptible in these
+ * contexts as well).
* <p>
* Implicit component may also be given a name using the syntax "
- * <code>componentId:@Type</code>". Such a component should <b>not </b>
- * be defined in the specification, but may still be accessed via
+ * <code>componentId:@Type</code>". Such a component should <b>not </b> be defined in the
+ * specification, but may still be accessed via
* {@link org.apache.tapestry.IComponent#getComponent(String)}.
* <p>
- * Both defined and implicit components may have additional attributes
- * defined, simply by including them in the template. They set formal or
- * informal parameters of the component to static strings.
- * {@link org.apache.tapestry.spec.IComponentSpecification#getAllowInformalParameters()},
- * if false, will cause such attributes to be simply ignored. For defined
- * components, conflicting values defined in the template are ignored.
+ * Both defined and implicit components may have additional attributes defined, simply by
+ * including them in the template. They set formal or informal parameters of the component to
+ * static strings.
+ * {@link org.apache.tapestry.spec.IComponentSpecification#getAllowInformalParameters()}, if
+ * false, will cause such attributes to be simply ignored. For defined components, conflicting
+ * values defined in the template are ignored.
* <p>
- * Attributes in component tags will become formal and informal parameters
- * of the corresponding component. Most attributes will be
+ * Attributes in component tags will become formal and informal parameters of the
+ * corresponding component. Most attributes will be
* <p>
- * The parser removes the body of some tags (when the corresponding
- * component doesn't
- * {@link org.apache.tapestry.spec.IComponentSpecification#getAllowBody() allow a body},
- * and allows portions of the template to be completely removed.
+ * The parser removes the body of some tags (when the corresponding component doesn't
+ * {@link org.apache.tapestry.spec.IComponentSpecification#getAllowBody() allow a body}, and
+ * allows portions of the template to be completely removed.
* <p>
- * The parser does a pretty thorough lexical analysis of the template, and
- * reports a great number of errors, including improper nesting of tags.
+ * The parser does a pretty thorough lexical analysis of the template, and reports a great
+ * number of errors, including improper nesting of tags.
* <p>
- * The parser supports <em>invisible localization</em>: The parser
- * recognizes HTML of the form:
- * <code><span key="<i>value</i>"> ... </span></code> and
- * converts them into a {@link TokenType#LOCALIZATION} token. You may also
- * specifify a <code>raw</code> attribute ... if the value is
- * <code>true</code>, then the localized value is sent to the client
- * without filtering, which is appropriate if the value has any markup
- * that should not be escaped.
+ * The parser supports <em>invisible localization</em>: The parser recognizes HTML of the
+ * form: <code><span key="<i>value</i>"> ... </span></code> and converts them
+ * into a {@link TokenType#LOCALIZATION} token. You may also specifify a <code>raw</code>
+ * attribute ... if the value is <code>true</code>, then the localized value is sent to the
+ * client without filtering, which is appropriate if the value has any markup that should not
+ * be escaped.
* @author Howard Lewis Ship, Geoff Longman
*/
public class TemplateParser implements ITemplateParser
{
-
/**
- * The attribute, checked for in <span> tags, that signfies that the
- * span is being used as an invisible localization.
- *
- * @since 2.0.4
+ * A "magic" component id that causes the tag with the id and its entire body to be ignored
+ * during parsing.
*/
- public static final String LOCALIZATION_KEY_ATTRIBUTE_NAME = "key";
+ private static final String REMOVE_ID = "$remove$";
+
+ /**
+ * A "magic" component id that causes the tag to represent the true content of the template. Any
+ * content prior to the tag is discarded, and any content after the tag is ignored. The tag
+ * itself is not included.
+ */
- public static final String PROPERTY_NAME_PATTERN = "_?[a-zA-Z]\\w*";
+ private static final String CONTENT_ID = "$content$";
/**
- * Pattern used to recognize ordinary components (defined in the
- * specification).
+ * The attribute, checked for in <span> tags, that signfies that the span is being used as
+ * an invisible localization.
*
- * @since 3.0
+ * @since 2.0.4
*/
- public static final String SIMPLE_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")$";
+ public static final String LOCALIZATION_KEY_ATTRIBUTE_NAME = "key";
/**
- * Used with {@link #LOCALIZATION_KEY_ATTRIBUTE_NAME} to indicate a string
- * that should be rendered "raw" (without escaping HTML). If not specified,
- * defaults to "false". The value must equal "true" (caselessly).
+ * Used with {@link #LOCALIZATION_KEY_ATTRIBUTE_NAME} to indicate a string that should be
+ * rendered "raw" (without escaping HTML). If not specified, defaults to "false". The value must
+ * equal "true" (caselessly).
*
* @since 2.3
*/
@@ -129,94 +125,108 @@
public static final String RAW_ATTRIBUTE_NAME = "raw";
/**
- * Pattern used to recognize implicit components (whose type is defined in
- * the template). Subgroup 1 is the id (which may be null) and subgroup 2 is
- * the type (which may be qualified with a library prefix). Subgroup 4 is
- * the library id, Subgroup 5 is the simple component type, which may (as of
- * 4.0) have slashes to delinate folders containing the component.
+ * Attribute name used to identify components.
*
- * @since 3.0
+ * @since 4.0
*/
- public static final String IMPLICIT_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")?@(((" + PROPERTY_NAME_PATTERN
- + "):)?((" + PROPERTY_NAME_PATTERN + "/)*" + PROPERTY_NAME_PATTERN + "))$";
+ private String _componentAttributeName;
- private static final int WAIT_FOR_ATTRIBUTE_NAME = 0;
+ private static final String PROPERTY_NAME_PATTERN = "_?[a-zA-Z]\\w*";
- private static final int WAIT_FOR_ATTRIBUTE_VALUE = 3;
+ /**
+ * Pattern used to recognize ordinary components (defined in the specification).
+ *
+ * @since 3.0
+ */
+
+ public static final String SIMPLE_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")$";
/**
- * A "magic" component id that causes the tag with the id and its entire
- * body to be ignored during parsing.
+ * Pattern used to recognize implicit components (whose type is defined in the template).
+ * Subgroup 1 is the id (which may be null) and subgroup 2 is the type (which may be qualified
+ * with a library prefix). Subgroup 4 is the library id, Subgroup 5 is the simple component
+ * type, which may (as of 4.0) have slashes to delinate folders containing the component.
+ *
+ * @since 3.0
*/
- private static final String REMOVE_ID = "$remove$";
- private static final int ADVANCE_PAST_EQUALS = 2;
+ public static final String IMPLICIT_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")?@((("
+ + PROPERTY_NAME_PATTERN + "):)?((" + PROPERTY_NAME_PATTERN + "/)*"
+ + PROPERTY_NAME_PATTERN + "))$";
- private static final char[] CLOSE_TAG = new char[] { '<', '/' };
+ private static final int IMPLICIT_ID_PATTERN_ID_GROUP = 1;
- private static final int COLLECT_ATTRIBUTE_NAME = 1;
+ private static final int IMPLICIT_ID_PATTERN_TYPE_GROUP = 2;
- private static final int COLLECT_QUOTED_VALUE = 4;
+ private static final int IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP = 4;
- private static final int COLLECT_UNQUOTED_VALUE = 5;
+ private static final int IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP = 5;
+
+ private Pattern _simpleIdPattern;
+
+ private Pattern _implicitIdPattern;
- private static final char[] COMMENT_END = new char[] { '-', '-', '>' };
+ private PatternMatcher _patternMatcher;
+
+ private IdAllocator _idAllocator = new IdAllocator();
- private static final char[] COMMENT_START = new char[] { '<', '!', '-', '-' };
+ private ITemplateParserDelegate _delegate;
/**
- * A "magic" component id that causes the tag to represent the true content
- * of the template. Any content prior to the tag is discarded, and any
- * content after the tag is ignored. The tag itself is not included.
+ * Identifies the template being parsed; used with error messages.
*/
- private static final String CONTENT_ID = "$content$";
+ private Resource _resourceLocation;
/**
- * Conversions needed by {@link #convertEntitiesToPlain(String)}.
+ * Shared instance of {@link Location} used by all {@link TextToken} instances in the template.
*/
- private static final String[] CONVERSIONS = { "<", "<", ">", ">", """, "\"", "&", "&" };
+ private Location _templateLocation;
- private static final int IMPLICIT_ID_PATTERN_ID_GROUP = 1;
+ /**
+ * Location with in the resource for the current line.
+ */
- private static final int IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP = 4;
+ private Location _currentLocation;
- private static final int IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP = 5;
+ /**
+ * Local reference to the template data that is to be parsed.
+ */
- private static final int IMPLICIT_ID_PATTERN_TYPE_GROUP = 2;
+ private char[] _templateData;
/**
- * Stores details about a parsed tag, used to match start and end tags and
- * control other parser processing.
+ * List of Tag
*/
+
+ private List _stack = new ArrayList();
+
private static class Tag
{
+ // The element, i.e., <jwc> or virtually any other element (via jwcid attribute)
+ String _tagName;
// If true, the tag is a placeholder for a dynamic element
boolean _component;
- // If true, then the parse ends when the closing tag is found.
- boolean _content;
-
// If true, the body of the tag is being ignored, and the
// ignore flag is cleared when the close tag is reached
boolean _ignoringBody;
- // The line on which the start tag exists
- int _line;
+ // If true, then the entire tag (and its body) is being ignored
+ boolean _removeTag;
// If true, then the tag must have a balanced closing tag.
// This is always true for components.
boolean _mustBalance;
- // If true, then the entire tag (and its body) is being ignored
- boolean _removeTag;
+ // The line on which the start tag exists
+ int _line;
- // The element, i.e., <jwc> or virtually any other element (via jwcid
- // attribute)
- String _tagName;
+ // If true, then the parse ends when the closing tag is found.
+ boolean _content;
Tag(String tagName, int line)
{
@@ -231,59 +241,23 @@
}
/**
- * A {@link Map}of {@link String}s, used to store attributes collected
- * while parsing a tag.
- */
-
- private Map _attributes = new HashMap();
-
- /**
- * The start of the current block of static text, or -1 if no block is
- * active.
- */
-
- private int _blockStart;
-
- /**
- * Attribute name used to identify components.
- *
- * @since 4.0
- */
-
- private String _componentAttributeName;
-
- /**
- * Location with in the resource for the current line.
+ * List of {@link TemplateToken}, this forms the ultimate response.
*/
- private Location _currentLocation;
+ private List _tokens = new ArrayList();
/**
- * The location of the 'cursor' within the template data. The advance()
- * method moves this forward.
+ * The location of the 'cursor' within the template data. The advance() method moves this
+ * forward.
*/
private int _cursor;
- private ITemplateParserDelegate _delegate;
-
- /**
- * A factory used to create template tokens.
- */
-
- private TemplateTokenFactory _factory;
-
- private IdAllocator _idAllocator = new IdAllocator();
-
/**
- * Set to true when the body of a tag is being ignored. This is typically
- * used to skip over the body of a tag when its corresponding component
- * doesn't allow a body, or whe the special jwcid of $remove$ is used.
+ * The start of the current block of static text, or -1 if no block is active.
*/
- private boolean _ignoring;
-
- private Pattern _implicitIdPattern;
+ private int _blockStart;
/**
* The current line number; tracked by advance(). Starts at 1.
@@ -291,40 +265,25 @@
private int _line;
- private PatternMatcher _patternMatcher;
-
- /**
- * Identifies the template being parsed; used with error messages.
- */
-
- private Resource _resourceLocation;
-
- private Pattern _simpleIdPattern;
-
- /**
- * List of Tag.
- */
-
- private List _stack = new ArrayList();
-
/**
- * Local reference to the template data that is to be parsed.
+ * Set to true when the body of a tag is being ignored. This is typically used to skip over the
+ * body of a tag when its corresponding component doesn't allow a body, or whe the special jwcid
+ * of $remove$ is used.
*/
- private char[] _templateData;
+ private boolean _ignoring;
/**
- * Shared instance of {@link Location} used by all {@link TextToken}
- * instances in the template.
+ * A {@link Map}of {@link String}s, used to store attributes collected while parsing a tag.
*/
- private Location _templateLocation;
+ private Map _attributes = new HashMap();
/**
- * List of {@link TemplateToken}, this forms the ultimate response.
+ * A factory used to create template tokens.
*/
- private List _tokens = new ArrayList();
+ private TemplateTokenFactory _factory;
public TemplateParser()
{
@@ -344,124 +303,50 @@
}
/**
- * @throws TemplateParseException
- * @since 4.0
- */
-
- private void addAttributeIfUnique(String tagName, String attributeName, String attributeValue)
- throws TemplateParseException
- {
-
- if (_attributes.containsKey(attributeName))
- templateParseProblem(ParseMessages.duplicateTagAttribute(tagName, _line, attributeName),
- getCurrentLocation(), _line, _cursor);
-
- _attributes.put(attributeName, attributeValue);
- }
-
- /**
- * Adds the attribute to the token (identifying prefixes and whatnot is now
- * done downstream).
+ * Parses the template data into an array of {@link TemplateToken}s.
+ * <p>
+ * The parser is <i>decidedly </i> not threadsafe, so care should be taken that only a single
+ * thread accesses it.
*
- * @since 3.0
+ * @param templateData
+ * the HTML template to parse. Some tokens will hold a reference to this array.
+ * @param delegate
+ * object that "knows" about defined components
+ * @param resourceLocation
+ * a description of where the template originated from, used with error messages.
*/
- private void addAttributeToToken(OpenToken token, String name, String attributeValue)
- {
- token.addAttribute(name, convertEntitiesToPlain(attributeValue));
- }
-
- private void addOpenToken(String tagName, String jwcId, String type, Location location)
+ public TemplateToken[] parse(char[] templateData, ITemplateParserDelegate delegate,
+ Resource resourceLocation) throws TemplateParseException
{
- OpenToken token = _factory.createOpenToken(tagName, jwcId, type, location);
- _tokens.add(token);
-
- if (_attributes.isEmpty()) return;
-
- Iterator i = _attributes.entrySet().iterator();
- while(i.hasNext())
+ try
{
- Map.Entry entry = (Map.Entry)i.next();
-
- String key = (String)entry.getKey();
-
- if (key.equalsIgnoreCase(_componentAttributeName)) continue;
+ beforeParse(templateData, delegate, resourceLocation);
- String value = (String)entry.getValue();
+ parse();
- addAttributeToToken(token, key, value);
+ return (TemplateToken[]) _tokens.toArray(new TemplateToken[_tokens.size()]);
}
- }
-
- private void addTextToken(int end)
- {
- // No active block to add to.
-
- if (_blockStart < 0) return;
-
- if (_blockStart <= end)
+ finally
{
- // This seems odd, shouldn't the location be the current location? I
- // guess
- // no errors are ever reported for a text token.
-
- TemplateToken token = _factory.createTextToken(_templateData, _blockStart, end, _templateLocation);
-
- _tokens.add(token);
+ afterParse();
}
-
- _blockStart = -1;
}
/**
- * Advances the cursor to the next character. If the end-of-line is reached,
- * then increments the line counter.
+ * perform default initialization of the parser.
*/
- private void advance()
- {
- int length = _templateData.length;
-
- if (_cursor >= length) return;
-
- char ch = _templateData[_cursor];
-
- _cursor++;
-
- if (ch == '\n')
- {
- _line++;
- _currentLocation = null;
- return;
- }
-
- // A \r, or a \r\n also counts as a new line.
-
- if (ch == '\r')
- {
- _line++;
- _currentLocation = null;
-
- if (_cursor < length && _templateData[_cursor] == '\n') _cursor++;
-
- return;
- }
-
- // Not an end-of-line character.
-
- }
-
- private void advanceOverWhitespace()
+ protected void beforeParse(char[] templateData, ITemplateParserDelegate delegate,
+ Resource resourceLocation)
{
- int length = _templateData.length;
-
- while(_cursor < length)
- {
- char ch = _templateData[_cursor];
- if (!Character.isWhitespace(ch)) return;
-
- advance();
- }
+ _templateData = templateData;
+ _resourceLocation = resourceLocation;
+ _templateLocation = new LocationImpl(resourceLocation);
+ _delegate = delegate;
+ _ignoring = false;
+ _line = 1;
+ _componentAttributeName = delegate.getComponentAttributeName();
}
/**
@@ -482,281 +367,51 @@
}
/**
- * Notify that the beginning of an attribute value has been detected.
+ * Used by the parser to report problems in the parse. Parsing <b>must </b> stop when a problem
+ * is reported.
* <p>
- * Default implementation does nothing.
+ * The default implementation simply throws an exception that contains the message and location
+ * parameters.
+ * <p>
+ * Subclasses may override but <b>must </b> ensure they throw the required exception.
+ *
+ * @param message
+ * @param location
+ * @param line
+ * ignored by the default impl
+ * @param cursor
+ * ignored by the default impl
+ * @throws TemplateParseException
+ * always thrown in order to terminate the parse.
*/
- protected void attributeBeginEvent(String attributeName, int startLine, int cursorPosition)
+
+ protected void templateParseProblem(String message, Location location, int line, int cursor)
+ throws TemplateParseException
{
+ throw new TemplateParseException(message, location);
}
/**
- * Notify that the end of the current attribute value has been detected.
+ * Used by the parser to report tapestry runtime specific problems in the parse. Parsing <b>must
+ * </b> stop when a problem is reported.
* <p>
- * Default implementation does nothing.
- */
- protected void attributeEndEvent(int cursorPosition)
- {
- }
-
- /**
- * perform default initialization of the parser.
- */
-
- protected void beforeParse(char[] templateData, ITemplateParserDelegate delegate, Resource resourceLocation)
- {
- _templateData = templateData;
- _resourceLocation = resourceLocation;
- _templateLocation = new LocationImpl(resourceLocation);
- _delegate = delegate;
- _ignoring = false;
- _line = 1;
- _componentAttributeName = delegate.getComponentAttributeName();
- }
-
- /**
- * Returns true if the map contains the given key (caseless search) and the
- * value is "true" (caseless comparison).
- */
-
- private boolean checkBoolean(String key, Map map)
- {
- String value = findValueCaselessly(key, map);
-
- if (value == null) return false;
-
- return value.equalsIgnoreCase("true");
- }
-
- /**
- * Invoked to handle a closing tag, i.e., </foo>. When a tag closes,
- * it will match against a tag on the open tag start. Preferably the top tag
- * on the stack (if everything is well balanced), but this is HTML, not XML,
- * so many tags won't balance.
- * <p>
- * Once the matching tag is located, the question is ... is the tag dynamic
- * or static? If static, then the current text block is extended to include
- * this close tag. If dynamic, then the current text block is ended (before
- * the '<' that starts the tag) and a close token is added.
+ * The default implementation simply rethrows the exception.
* <p>
- * In either case, the matching static element and anything above it is
- * removed, and the cursor is left on the character following the '>'.
- */
-
- private void closeTag()
- throws TemplateParseException
- {
- int cursorStart = _cursor;
- int length = _templateData.length;
- int startLine = _line;
-
- Location startLocation = getCurrentLocation();
-
- _cursor += CLOSE_TAG.length;
-
- int tagStart = _cursor;
-
- while(true)
- {
- if (_cursor >= length)
- templateParseProblem(ParseMessages.incompleteCloseTag(startLine), startLocation, startLine, cursorStart);
-
- char ch = _templateData[_cursor];
-
- if (ch == '>') break;
-
- advance();
- }
-
- String tagName = new String(_templateData, tagStart, _cursor - tagStart);
-
- int stackPos = _stack.size() - 1;
- Tag tag = null;
-
- while(stackPos >= 0)
- {
- tag = (Tag)_stack.get(stackPos);
-
- if (tag.match(tagName)) break;
-
- if (tag._mustBalance)
- templateParseProblem(ParseMessages
- .improperlyNestedCloseTag(tagName, startLine, tag._tagName, tag._line), startLocation,
- startLine, cursorStart);
-
- stackPos--;
- }
-
- if (stackPos < 0)
- templateParseProblem(ParseMessages.unmatchedCloseTag(tagName, startLine), startLocation, startLine,
- cursorStart);
-
- // Special case for the content tag
-
- if (tag._content)
- {
- addTextToken(cursorStart - 1);
-
- // Advance the cursor right to the end.
-
- _cursor = length;
- _stack.clear();
- return;
- }
-
- // When a component closes, add a CLOSE tag.
- if (tag._component)
- {
- addTextToken(cursorStart - 1);
-
- _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
- }
- else
- {
- // The close of a static tag. Unless removing the tag
- // entirely, make sure the block tag is part of a text block.
-
- if (_blockStart < 0 && !tag._removeTag && !_ignoring) _blockStart = cursorStart;
- }
-
- // Remove all elements at stackPos or above.
-
- for(int i = _stack.size() - 1; i >= stackPos; i--)
- _stack.remove(i);
-
- // Advance cursor past '>'
-
- advance();
-
- // If editting out the tag (i.e., $remove$) then kill any whitespace.
- // For components that simply don't contain a body, removeTag will
- // be false.
-
- if (tag._removeTag) advanceOverWhitespace();
-
- // If we were ignoring the body of the tag, then clear the ignoring
- // flag, since we're out of the body.
-
- if (tag._ignoringBody) _ignoring = false;
- }
-
- /**
- * Processes a tag that is the open tag for a component (but also handles
- * the $remove$ and $content$ tags).
- */
-
- /**
- * Provided a raw input string that has been recognized to be an expression,
- * this removes excess white space and converts &amp;;, &quot;;
- * &lt;; and &gt;; to their normal character values (otherwise its
- * impossible to specify those values in expressions in the template).
- */
-
- private String convertEntitiesToPlain(String input)
- {
- int inputLength = input.length();
-
- StringBuffer buffer = new StringBuffer(inputLength);
-
- int cursor = 0;
-
- outer: while(cursor < inputLength)
- {
- for(int i = 0; i < CONVERSIONS.length; i += 2)
- {
- String entity = CONVERSIONS[i];
- int entityLength = entity.length();
- String value = CONVERSIONS[i + 1];
-
- if (cursor + entityLength > inputLength) continue;
-
- if (input.substring(cursor, cursor + entityLength).equals(entity))
- {
- buffer.append(value);
- cursor += entityLength;
- continue outer;
- }
- }
-
- buffer.append(input.charAt(cursor));
- cursor++;
- }
-
- return buffer.toString().trim();
- }
-
- /**
- * Returns a new Map that is a copy of the input Map with some key/value
- * pairs removed. A list of keys is passed in and matching keys (caseless
- * comparison) from the input Map are excluded from the output map. May
- * return null (rather than return an empty Map).
- */
-
- private Map filter(Map input, String[] removeKeys)
- {
- if (input == null || input.isEmpty()) return null;
-
- Map result = null;
-
- Iterator i = input.entrySet().iterator();
-
- nextkey: while(i.hasNext())
- {
- Map.Entry entry = (Map.Entry)i.next();
-
- String key = (String)entry.getKey();
-
- for(int j = 0; j < removeKeys.length; j++)
- {
- if (key.equalsIgnoreCase(removeKeys[j])) continue nextkey;
- }
-
- if (result == null) result = new HashMap(input.size());
-
- result.put(key, entry.getValue());
- }
-
- return result;
- }
-
- /**
- * Searches a Map for given key, caselessly. The Map is expected to consist
- * of Strings for keys and values. Returns the value for the first key found
- * that matches (caselessly) the input key. Returns null if no value found.
- */
-
- protected String findValueCaselessly(String key, Map map)
- {
- String result = (String)map.get(key);
-
- if (result != null) return result;
-
- Iterator i = map.entrySet().iterator();
- while(i.hasNext())
- {
- Map.Entry entry = (Map.Entry)i.next();
-
- String entryKey = (String)entry.getKey();
-
- if (entryKey.equalsIgnoreCase(key)) return (String)entry.getValue();
- }
-
- return null;
- }
-
- /**
- * Gets the current location within the file. This allows the location to be
- * created only as needed, and multiple objects on the same line can share
- * the same Location instance.
+ * Subclasses may override but <b>must </b> ensure they rethrow the exception.
*
- * @since 3.0
+ * @param exception
+ * @param line
+ * ignored by the default impl
+ * @param cursor
+ * ignored by the default impl
+ * @throws ApplicationRuntimeException
+ * always rethrown in order to terminate the parse.
*/
- protected Location getCurrentLocation()
+ protected void templateParseProblem(ApplicationRuntimeException exception, int line, int cursor)
+ throws ApplicationRuntimeException
{
- if (_currentLocation == null) _currentLocation = new LocationImpl(_resourceLocation, _line);
-
- return _currentLocation;
+ throw exception;
}
/**
@@ -764,7 +419,8 @@
*/
protected List getTokens()
{
- if (_tokens == null) return Collections.EMPTY_LIST;
+ if (_tokens == null)
+ return Collections.EMPTY_LIST;
return _tokens;
}
@@ -777,9 +433,10 @@
{
try
{
- for(int i = 0; i < match.length; i++)
+ for (int i = 0; i < match.length; i++)
{
- if (_templateData[_cursor + i] != match[i]) return false;
+ if (_templateData[_cursor + i] != match[i])
+ return false;
}
// Every character matched.
@@ -792,18 +449,27 @@
}
}
- protected void parse()
- throws TemplateParseException
+ private static final char[] COMMENT_START = new char[]
+ { '<', '!', '-', '-' };
+
+ private static final char[] COMMENT_END = new char[]
+ { '-', '-', '>' };
+
+ private static final char[] CLOSE_TAG = new char[]
+ { '<', '/' };
+
+ protected void parse() throws TemplateParseException
{
_cursor = 0;
_blockStart = -1;
int length = _templateData.length;
- while(_cursor < length)
+ while (_cursor < length)
{
if (_templateData[_cursor] != '<')
{
- if (_blockStart < 0 && !_ignoring) _blockStart = _cursor;
+ if (_blockStart < 0 && !_ignoring)
+ _blockStart = _cursor;
advance();
continue;
@@ -828,8 +494,7 @@
startTag();
}
- // Usually there's some text at the end of the template (after the last
- // closing tag) that
+ // Usually there's some text at the end of the template (after the last closing tag) that
// should
// be added. Often the last few tags are static tags so we definately
// need to end the text block.
@@ -838,220 +503,73 @@
}
/**
- * Parses the template data into an array of {@link TemplateToken}s.
- * <p>
- * The parser is <i>decidedly </i> not threadsafe, so care should be taken
- * that only a single thread accesses it.
- *
- * @param templateData
- * the HTML template to parse. Some tokens will hold a reference
- * to this array.
- * @param delegate
- * object that "knows" about defined components
- * @param resourceLocation
- * a description of where the template originated from, used with
- * error messages.
+ * Advance forward in the document until the end of the comment is reached. In addition, skip
+ * any whitespace following the comment.
*/
- public TemplateToken[] parse(char[] templateData, ITemplateParserDelegate delegate, Resource resourceLocation)
- throws TemplateParseException
+ private void skipComment() throws TemplateParseException
{
- try
- {
- beforeParse(templateData, delegate, resourceLocation);
+ int length = _templateData.length;
+ int startLine = _line;
- parse();
+ if (_blockStart < 0 && !_ignoring)
+ _blockStart = _cursor;
- return (TemplateToken[])_tokens.toArray(new TemplateToken[_tokens.size()]);
- }
- finally
+ while (true)
{
- afterParse();
+ if (_cursor >= length)
+ templateParseProblem(ParseMessages.commentNotEnded(startLine), new LocationImpl(
+ _resourceLocation, startLine), startLine, _cursor);
+
+ if (lookahead(COMMENT_END))
+ break;
+
+ // Not the end of the comment, advance over it.
+
+ advance();
}
+
+ _cursor += COMMENT_END.length;
+ advanceOverWhitespace();
}
- private void processComponentStart(String tagName, String jwcId, boolean emptyTag, int startLine, int cursorStart,
- Location startLocation)
- throws TemplateParseException
+ private void addTextToken(int end)
{
- if (jwcId.equalsIgnoreCase(CONTENT_ID))
- {
- processContentTag(tagName, startLine, cursorStart, emptyTag);
+ // No active block to add to.
+ if (_blockStart < 0)
return;
- }
- boolean isRemoveId = jwcId.equalsIgnoreCase(REMOVE_ID);
+ if (_blockStart <= end)
+ {
+ // This seems odd, shouldn't the location be the current location? I guess
+ // no errors are ever reported for a text token.
- if (_ignoring && !isRemoveId)
- templateParseProblem(ParseMessages.componentMayNotBeIgnored(tagName, startLine), startLocation, startLine,
- cursorStart);
+ TemplateToken token = _factory.createTextToken(
+ _templateData,
+ _blockStart,
+ end,
+ _templateLocation);
- String type = null;
- boolean allowBody = false;
+ _tokens.add(token);
+ }
- if (_patternMatcher.matches(jwcId, _implicitIdPattern))
- {
- MatchResult match = _patternMatcher.getMatch();
+ _blockStart = -1;
+ }
- jwcId = match.group(IMPLICIT_ID_PATTERN_ID_GROUP);
- type = match.group(IMPLICIT_ID_PATTERN_TYPE_GROUP);
-
- String libraryId = match.group(IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP);
- String simpleType = match.group(IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP);
-
- // If (and this is typical) no actual component id was specified,
- // then generate one on the fly.
- // The allocated id for anonymous components is
- // based on the simple (unprefixed) type, but starts
- // with a leading dollar sign to ensure no conflicts
- // with user defined component ids (which don't allow dollar signs
- // in the id).
- // New for 4.0: the component type may included slashes ('/'), but
- // these
- // are not valid identifiers, so we convert them to '$'.
-
- if (jwcId == null) jwcId = _idAllocator.allocateId("$" + simpleType.replace('/', '$'));
-
- try
- {
- allowBody = _delegate.getAllowBody(libraryId, simpleType, startLocation);
- }
- catch (ApplicationRuntimeException e)
- {
- // give subclasses a chance to handle and rethrow
- templateParseProblem(e, startLine, cursorStart);
- }
-
- }
- else
- {
- if (!isRemoveId)
- {
- if (!_patternMatcher.matches(jwcId, _simpleIdPattern))
- templateParseProblem(ParseMessages.componentIdInvalid(tagName, startLine, jwcId), startLocation,
- startLine, cursorStart);
-
- if (!_delegate.getKnownComponent(jwcId))
- templateParseProblem(ParseMessages.unknownComponentId(tagName, startLine, jwcId), startLocation,
- startLine, cursorStart);
-
- try
- {
- allowBody = _delegate.getAllowBody(jwcId, startLocation);
- }
- catch (ApplicationRuntimeException e)
- {
- // give subclasses a chance to handle and rethrow
- templateParseProblem(e, startLine, cursorStart);
- }
- }
- }
-
- // Ignore the body if we're removing the entire tag,
- // of if the corresponding component doesn't allow
- // a body.
-
- boolean ignoreBody = !emptyTag && (isRemoveId || !allowBody);
-
- if (_ignoring && ignoreBody)
- templateParseProblem(ParseMessages.nestedIgnore(tagName, startLine), new LocationImpl(_resourceLocation,
- startLine), startLine, cursorStart);
-
- if (!emptyTag) pushNewTag(tagName, startLine, isRemoveId, ignoreBody);
-
- // End any open block.
-
- addTextToken(cursorStart - 1);
-
- if (!isRemoveId)
- {
- addOpenToken(tagName, jwcId, type, startLocation);
-
- if (emptyTag) _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
- }
-
- advance();
- }
-
- private void processContentTag(String tagName, int startLine, int cursorStart, boolean emptyTag)
- throws TemplateParseException
- {
- if (_ignoring)
- templateParseProblem(ParseMessages.contentBlockMayNotBeIgnored(tagName, startLine), new LocationImpl(
- _resourceLocation, startLine), startLine, cursorStart);
-
- if (emptyTag)
- templateParseProblem(ParseMessages.contentBlockMayNotBeEmpty(tagName, startLine), new LocationImpl(
- _resourceLocation, startLine), startLine, cursorStart);
-
- _tokens.clear();
- _blockStart = -1;
-
- Tag tag = new Tag(tagName, startLine);
-
- tag._mustBalance = true;
- tag._content = true;
-
- _stack.clear();
- _stack.add(tag);
-
- advance();
- }
-
- private void pushNewTag(String tagName, int startLine, boolean isRemoveId, boolean ignoreBody)
- {
- Tag tag = new Tag(tagName, startLine);
-
- tag._component = !isRemoveId;
- tag._removeTag = isRemoveId;
-
- tag._ignoringBody = ignoreBody;
-
- _ignoring = tag._ignoringBody;
-
- tag._mustBalance = true;
-
- _stack.add(tag);
- }
-
- public void setFactory(TemplateTokenFactory factory)
- {
- _factory = factory;
- }
-
- /**
- * Advance forward in the document until the end of the comment is reached.
- * In addition, skip any whitespace following the comment.
- */
-
- private void skipComment()
- throws TemplateParseException
- {
- int length = _templateData.length;
- int startLine = _line;
-
- if (_blockStart < 0 && !_ignoring) _blockStart = _cursor;
+ private static final int WAIT_FOR_ATTRIBUTE_NAME = 0;
- while(true)
- {
- if (_cursor >= length)
- templateParseProblem(ParseMessages.commentNotEnded(startLine), new LocationImpl(_resourceLocation,
- startLine), startLine, _cursor);
+ private static final int COLLECT_ATTRIBUTE_NAME = 1;
- if (lookahead(COMMENT_END)) break;
+ private static final int ADVANCE_PAST_EQUALS = 2;
- // Not the end of the comment, advance over it.
+ private static final int WAIT_FOR_ATTRIBUTE_VALUE = 3;
- advance();
- }
+ private static final int COLLECT_QUOTED_VALUE = 4;
- _cursor += COMMENT_END.length;
- advanceOverWhitespace();
- }
+ private static final int COLLECT_UNQUOTED_VALUE = 5;
- private void startTag()
- throws TemplateParseException
+ private void startTag() throws TemplateParseException
{
int cursorStart = _cursor;
int length = _templateData.length;
@@ -1067,7 +585,7 @@
// Collect the element type
- while(_cursor < length)
+ while (_cursor < length)
{
char ch = _templateData[_cursor];
@@ -1091,189 +609,185 @@
// Collect each attribute
- while(!endOfTag)
+ while (!endOfTag)
{
if (_cursor >= length)
{
- String message = (tagName == null) ? ParseMessages.unclosedUnknownTag(startLine) : ParseMessages
- .unclosedTag(tagName, startLine);
+ String message = (tagName == null) ? ParseMessages.unclosedUnknownTag(startLine)
+ : ParseMessages.unclosedTag(tagName, startLine);
templateParseProblem(message, startLocation, startLine, cursorStart);
}
char ch = _templateData[_cursor];
- switch(state)
+ switch (state)
{
- case WAIT_FOR_ATTRIBUTE_NAME:
+ case WAIT_FOR_ATTRIBUTE_NAME:
- // Ignore whitespace before the next attribute name, while
- // looking for the end of the current tag.
+ // Ignore whitespace before the next attribute name, while
+ // looking for the end of the current tag.
- if (ch == '/')
- {
- emptyTag = true;
- advance();
- break;
- }
+ if (ch == '/')
+ {
+ emptyTag = true;
+ advance();
+ break;
+ }
+
+ if (ch == '>')
+ {
+ endOfTag = true;
+ break;
+ }
+
+ if (Character.isWhitespace(ch))
+ {
+ advance();
+ break;
+ }
- if (ch == '>')
- {
- endOfTag = true;
- break;
- }
+ // Found non-whitespace, assume its the attribute name.
+ // Note: could use a check here for non-alpha.
- if (Character.isWhitespace(ch))
- {
+ attributeNameStart = _cursor;
+ state = COLLECT_ATTRIBUTE_NAME;
advance();
break;
- }
- // Found non-whitespace, assume its the attribute name.
- // Note: could use a check here for non-alpha.
+ case COLLECT_ATTRIBUTE_NAME:
- attributeNameStart = _cursor;
- state = COLLECT_ATTRIBUTE_NAME;
- advance();
- break;
+ // Looking for end of attribute name.
- case COLLECT_ATTRIBUTE_NAME:
+ if (ch == '=' || ch == '/' || ch == '>' || Character.isWhitespace(ch))
+ {
+ attributeName = new String(_templateData, attributeNameStart, _cursor
+ - attributeNameStart);
- // Looking for end of attribute name.
+ state = ADVANCE_PAST_EQUALS;
+ break;
+ }
- if (ch == '=' || ch == '/' || ch == '>' || Character.isWhitespace(ch))
- {
- attributeName = new String(_templateData, attributeNameStart, _cursor - attributeNameStart);
+ // Part of the attribute name
- state = ADVANCE_PAST_EQUALS;
+ advance();
break;
- }
-
- // Part of the attribute name
-
- advance();
- break;
- case ADVANCE_PAST_EQUALS:
+ case ADVANCE_PAST_EQUALS:
- // Looking for the '=' sign. May hit the end of the tag, or (for
- // bare
- // attributes),
- // the next attribute name.
-
- if (ch == '/' || ch == '>')
- {
- // A bare attribute, which is not interesting to
- // us.
+ // Looking for the '=' sign. May hit the end of the tag, or (for bare
+ // attributes),
+ // the next attribute name.
+
+ if (ch == '/' || ch == '>')
+ {
+ // A bare attribute, which is not interesting to
+ // us.
+
+ state = WAIT_FOR_ATTRIBUTE_NAME;
+ break;
+ }
+
+ if (Character.isWhitespace(ch))
+ {
+ advance();
+ break;
+ }
+
+ if (ch == '=')
+ {
+ state = WAIT_FOR_ATTRIBUTE_VALUE;
+ quoteChar = 0;
+ attributeValueStart = -1;
+ advance();
+ break;
+ }
+
+ // Otherwise, an HTML style "bare" attribute (such as <select multiple>).
+ // We aren't interested in those (we're just looking for the id or jwcid
+ // attribute).
state = WAIT_FOR_ATTRIBUTE_NAME;
break;
- }
-
- if (Character.isWhitespace(ch))
- {
- advance();
- break;
- }
-
- if (ch == '=')
- {
- state = WAIT_FOR_ATTRIBUTE_VALUE;
- quoteChar = 0;
- attributeValueStart = -1;
- advance();
- break;
- }
-
- // Otherwise, an HTML style "bare" attribute (such as <select
- // multiple>).
- // We aren't interested in those (we're just looking for the id
- // or jwcid
- // attribute).
-
- state = WAIT_FOR_ATTRIBUTE_NAME;
- break;
-
- case WAIT_FOR_ATTRIBUTE_VALUE:
-
- if (ch == '/' || ch == '>')
- templateParseProblem(ParseMessages.missingAttributeValue(tagName, _line, attributeName),
- getCurrentLocation(), _line, _cursor);
- // Ignore whitespace between '=' and the attribute value. Also,
- // look
- // for initial quote.
+ case WAIT_FOR_ATTRIBUTE_VALUE:
- if (Character.isWhitespace(ch))
- {
- advance();
- break;
- }
+ if (ch == '/' || ch == '>')
+ templateParseProblem(ParseMessages.missingAttributeValue(
+ tagName,
+ _line,
+ attributeName), getCurrentLocation(), _line, _cursor);
+
+ // Ignore whitespace between '=' and the attribute value. Also, look
+ // for initial quote.
+
+ if (Character.isWhitespace(ch))
+ {
+ advance();
+ break;
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ quoteChar = ch;
+
+ state = COLLECT_QUOTED_VALUE;
+ advance();
+ attributeValueStart = _cursor;
+ attributeBeginEvent(attributeName, _line, attributeValueStart);
+ break;
+ }
- if (ch == '\'' || ch == '"')
- {
- quoteChar = ch;
+ // Not whitespace or quote, must be start of unquoted attribute.
- state = COLLECT_QUOTED_VALUE;
- advance();
+ state = COLLECT_UNQUOTED_VALUE;
attributeValueStart = _cursor;
attributeBeginEvent(attributeName, _line, attributeValueStart);
break;
- }
-
- // Not whitespace or quote, must be start of unquoted attribute.
- state = COLLECT_UNQUOTED_VALUE;
- attributeValueStart = _cursor;
- attributeBeginEvent(attributeName, _line, attributeValueStart);
- break;
-
- case COLLECT_QUOTED_VALUE:
-
- // Start collecting the quoted attribute value. Stop at the
- // matching quote
- // character,
- // unless bare, in which case, stop at the next whitespace.
-
- if (ch == quoteChar)
- {
- String attributeValue = new String(_templateData, attributeValueStart, _cursor
- - attributeValueStart);
-
- attributeEndEvent(_cursor);
+ case COLLECT_QUOTED_VALUE:
- addAttributeIfUnique(tagName, attributeName, attributeValue);
+ // Start collecting the quoted attribute value. Stop at the matching quote
+ // character,
+ // unless bare, in which case, stop at the next whitespace.
+
+ if (ch == quoteChar)
+ {
+ String attributeValue = new String(_templateData, attributeValueStart,
+ _cursor - attributeValueStart);
+
+ attributeEndEvent(_cursor);
+
+ addAttributeIfUnique(tagName, attributeName, attributeValue);
+
+ // Advance over the quote.
+ advance();
+ state = WAIT_FOR_ATTRIBUTE_NAME;
+ break;
+ }
- // Advance over the quote.
advance();
- state = WAIT_FOR_ATTRIBUTE_NAME;
break;
- }
- advance();
- break;
-
- case COLLECT_UNQUOTED_VALUE:
-
- // An unquoted attribute value ends with whitespace
- // or the end of the enclosing tag.
+ case COLLECT_UNQUOTED_VALUE:
- if (ch == '/' || ch == '>' || Character.isWhitespace(ch))
- {
- String attributeValue = new String(_templateData, attributeValueStart, _cursor
- - attributeValueStart);
+ // An unquoted attribute value ends with whitespace
+ // or the end of the enclosing tag.
- attributeEndEvent(_cursor);
- addAttributeIfUnique(tagName, attributeName, attributeValue);
+ if (ch == '/' || ch == '>' || Character.isWhitespace(ch))
+ {
+ String attributeValue = new String(_templateData, attributeValueStart,
+ _cursor - attributeValueStart);
+
+ attributeEndEvent(_cursor);
+ addAttributeIfUnique(tagName, attributeName, attributeValue);
+
+ state = WAIT_FOR_ATTRIBUTE_NAME;
+ break;
+ }
- state = WAIT_FOR_ATTRIBUTE_NAME;
+ advance();
break;
- }
-
- advance();
-
- default:
- break;
}
}
@@ -1287,8 +801,11 @@
if (localizationKey != null && tagName.equalsIgnoreCase("span") && jwcId == null)
{
if (_ignoring)
- templateParseProblem(ParseMessages.componentMayNotBeIgnored(tagName, startLine), startLocation,
- startLine, cursorStart);
+ templateParseProblem(
+ ParseMessages.componentMayNotBeIgnored(tagName, startLine),
+ startLocation,
+ startLine,
+ cursorStart);
// If the tag isn't empty, then create a Tag instance to ignore the
// body of the tag.
@@ -1321,9 +838,14 @@
boolean raw = checkBoolean(RAW_ATTRIBUTE_NAME, _attributes);
- Map attributes = filter(_attributes, new String[] { LOCALIZATION_KEY_ATTRIBUTE_NAME, RAW_ATTRIBUTE_NAME });
+ Map attributes = filter(_attributes, new String[]
+ { LOCALIZATION_KEY_ATTRIBUTE_NAME, RAW_ATTRIBUTE_NAME });
- TemplateToken token = _factory.createLocalizationToken(tagName, localizationKey, raw, attributes,
+ TemplateToken token = _factory.createLocalizationToken(
+ tagName,
+ localizationKey,
+ raw,
+ attributes,
startLocation);
_tokens.add(token);
@@ -1348,12 +870,37 @@
// If there wasn't an active block, then start one.
- if (_blockStart < 0 && !_ignoring) _blockStart = cursorStart;
+ if (_blockStart < 0 && !_ignoring)
+ _blockStart = cursorStart;
advance();
}
/**
+ * @throws TemplateParseException
+ * @since 4.0
+ */
+
+ private void addAttributeIfUnique(String tagName, String attributeName, String attributeValue)
+ throws TemplateParseException
+ {
+
+ if (_attributes.containsKey(attributeName))
+ templateParseProblem(
+ ParseMessages.duplicateTagAttribute(tagName, _line, attributeName),
+ getCurrentLocation(),
+ _line,
+ _cursor);
+
+ _attributes.put(attributeName, attributeValue);
+ }
+
+ /**
+ * Processes a tag that is the open tag for a component (but also handles the $remove$ and
+ * $content$ tags).
+ */
+
+ /**
* Notify that the beginning of a tag has been detected.
* <p>
* Default implementation does nothing.
@@ -1372,52 +919,541 @@
}
/**
- * Used by the parser to report tapestry runtime specific problems in the
- * parse. Parsing <b>must </b> stop when a problem is reported.
- * <p>
- * The default implementation simply rethrows the exception.
+ * Notify that the beginning of an attribute value has been detected.
* <p>
- * Subclasses may override but <b>must </b> ensure they rethrow the
- * exception.
- *
- * @param exception
- * @param line
- * ignored by the default impl
- * @param cursor
- * ignored by the default impl
- * @throws ApplicationRuntimeException
- * always rethrown in order to terminate the parse.
+ * Default implementation does nothing.
*/
-
- protected void templateParseProblem(ApplicationRuntimeException exception, int line, int cursor)
+ protected void attributeBeginEvent(String attributeName, int startLine, int cursorPosition)
{
- throw exception;
}
/**
- * Used by the parser to report problems in the parse. Parsing <b>must </b>
- * stop when a problem is reported.
- * <p>
- * The default implementation simply throws an exception that contains the
- * message and location parameters.
+ * Notify that the end of the current attribute value has been detected.
* <p>
- * Subclasses may override but <b>must </b> ensure they throw the required
- * exception.
- *
- * @param message
- * @param location
- * @param line
- * ignored by the default impl
- * @param cursor
- * ignored by the default impl
- * @throws TemplateParseException
- * always thrown in order to terminate the parse.
+ * Default implementation does nothing.
*/
+ protected void attributeEndEvent(int cursorPosition)
+ {
+ }
- protected void templateParseProblem(String message, Location location, int line, int cursor)
- throws TemplateParseException
+ private void processComponentStart(String tagName, String jwcId, boolean emptyTag,
+ int startLine, int cursorStart, Location startLocation) throws TemplateParseException
{
- throw new TemplateParseException(message, location);
+ if (jwcId.equalsIgnoreCase(CONTENT_ID))
+ {
+ processContentTag(tagName, startLine, cursorStart, emptyTag);
+
+ return;
+ }
+
+ boolean isRemoveId = jwcId.equalsIgnoreCase(REMOVE_ID);
+
+ if (_ignoring && !isRemoveId)
+ templateParseProblem(
+ ParseMessages.componentMayNotBeIgnored(tagName, startLine),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ String type = null;
+ boolean allowBody = false;
+
+ if (_patternMatcher.matches(jwcId, _implicitIdPattern))
+ {
+ MatchResult match = _patternMatcher.getMatch();
+
+ jwcId = match.group(IMPLICIT_ID_PATTERN_ID_GROUP);
+ type = match.group(IMPLICIT_ID_PATTERN_TYPE_GROUP);
+
+ String libraryId = match.group(IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP);
+ String simpleType = match.group(IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP);
+
+ // If (and this is typical) no actual component id was specified,
+ // then generate one on the fly.
+ // The allocated id for anonymous components is
+ // based on the simple (unprefixed) type, but starts
+ // with a leading dollar sign to ensure no conflicts
+ // with user defined component ids (which don't allow dollar signs
+ // in the id).
+ // New for 4.0: the component type may included slashes ('/'), but these
+ // are not valid identifiers, so we convert them to '$'.
+
+ if (jwcId == null)
+ jwcId = _idAllocator.allocateId("$" + simpleType.replace('/', '$'));
+
+ try
+ {
+ allowBody = _delegate.getAllowBody(libraryId, simpleType, startLocation);
+ }
+ catch (ApplicationRuntimeException e)
+ {
+ // give subclasses a chance to handle and rethrow
+ templateParseProblem(e, startLine, cursorStart);
+ }
+
+ }
+ else
+ {
+ if (!isRemoveId)
+ {
+ if (!_patternMatcher.matches(jwcId, _simpleIdPattern))
+ templateParseProblem(
+ ParseMessages.componentIdInvalid(tagName, startLine, jwcId),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ if (!_delegate.getKnownComponent(jwcId))
+ templateParseProblem(
+ ParseMessages.unknownComponentId(tagName, startLine, jwcId),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ try
+ {
+ allowBody = _delegate.getAllowBody(jwcId, startLocation);
+ }
+ catch (ApplicationRuntimeException e)
+ {
+ // give subclasses a chance to handle and rethrow
+ templateParseProblem(e, startLine, cursorStart);
+ }
+ }
+ }
+
+ // Ignore the body if we're removing the entire tag,
+ // of if the corresponding component doesn't allow
+ // a body.
+
+ boolean ignoreBody = !emptyTag && (isRemoveId || !allowBody);
+
+ if (_ignoring && ignoreBody)
+ templateParseProblem(ParseMessages.nestedIgnore(tagName, startLine), new LocationImpl(
+ _resourceLocation, startLine), startLine, cursorStart);
+
+ if (!emptyTag)
+ pushNewTag(tagName, startLine, isRemoveId, ignoreBody);
+
+ // End any open block.
+
+ addTextToken(cursorStart - 1);
+
+ if (!isRemoveId)
+ {
+ addOpenToken(tagName, jwcId, type, startLocation);
+
+ if (emptyTag)
+ _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
+ }
+
+ advance();
+ }
+
+ private void pushNewTag(String tagName, int startLine, boolean isRemoveId, boolean ignoreBody)
+ {
+ Tag tag = new Tag(tagName, startLine);
+
+ tag._component = !isRemoveId;
+ tag._removeTag = isRemoveId;
+
+ tag._ignoringBody = ignoreBody;
+
+ _ignoring = tag._ignoringBody;
+
+ tag._mustBalance = true;
+
+ _stack.add(tag);
+ }
+
+ private void processContentTag(String tagName, int startLine, int cursorStart, boolean emptyTag)
+ throws TemplateParseException
+ {
+ if (_ignoring)
+ templateParseProblem(
+ ParseMessages.contentBlockMayNotBeIgnored(tagName, startLine),
+ new LocationImpl(_resourceLocation, startLine),
+ startLine,
+ cursorStart);
+
+ if (emptyTag)
+ templateParseProblem(
+ ParseMessages.contentBlockMayNotBeEmpty(tagName, startLine),
+ new LocationImpl(_resourceLocation, startLine),
+ startLine,
+ cursorStart);
+
+ _tokens.clear();
+ _blockStart = -1;
+
+ Tag tag = new Tag(tagName, startLine);
+
+ tag._mustBalance = true;
+ tag._content = true;
+
+ _stack.clear();
+ _stack.add(tag);
+
+ advance();
+ }
+
+ private void addOpenToken(String tagName, String jwcId, String type, Location location)
+ {
+ OpenToken token = _factory.createOpenToken(tagName, jwcId, type, location);
+ _tokens.add(token);
+
+ if (_attributes.isEmpty())
+ return;
+
+ Iterator i = _attributes.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String key = (String) entry.getKey();
+
+ if (key.equalsIgnoreCase(_componentAttributeName))
+ continue;
+
+ String value = (String) entry.getValue();
+
+ addAttributeToToken(token, key, value);
+ }
+ }
+
+ /**
+ * Adds the attribute to the token (identifying prefixes and whatnot is now done downstream).
+ *
+ * @since 3.0
+ */
+
+ private void addAttributeToToken(OpenToken token, String name, String attributeValue)
+ {
+ token.addAttribute(name, convertEntitiesToPlain(attributeValue));
+ }
+
+ /**
+ * Invoked to handle a closing tag, i.e., </foo>. When a tag closes, it will match against
+ * a tag on the open tag start. Preferably the top tag on the stack (if everything is well
+ * balanced), but this is HTML, not XML, so many tags won't balance.
+ * <p>
+ * Once the matching tag is located, the question is ... is the tag dynamic or static? If
+ * static, then the current text block is extended to include this close tag. If dynamic, then
+ * the current text block is ended (before the '<' that starts the tag) and a close token is
+ * added.
+ * <p>
+ * In either case, the matching static element and anything above it is removed, and the cursor
+ * is left on the character following the '>'.
+ */
+
+ private void closeTag() throws TemplateParseException
+ {
+ int cursorStart = _cursor;
+ int length = _templateData.length;
+ int startLine = _line;
+
+ Location startLocation = getCurrentLocation();
+
+ _cursor += CLOSE_TAG.length;
+
+ int tagStart = _cursor;
+
+ while (true)
+ {
+ if (_cursor >= length)
+ templateParseProblem(
+ ParseMessages.incompleteCloseTag(startLine),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ char ch = _templateData[_cursor];
+
+ if (ch == '>')
+ break;
+
+ advance();
+ }
+
+ String tagName = new String(_templateData, tagStart, _cursor - tagStart);
+
+ int stackPos = _stack.size() - 1;
+ Tag tag = null;
+
+ while (stackPos >= 0)
+ {
+ tag = (Tag) _stack.get(stackPos);
+
+ if (tag.match(tagName))
+ break;
+
+ if (tag._mustBalance)
+ templateParseProblem(ParseMessages.improperlyNestedCloseTag(
+ tagName,
+ startLine,
+ tag._tagName,
+ tag._line), startLocation, startLine, cursorStart);
+
+ stackPos--;
+ }
+
+ if (stackPos < 0)
+ templateParseProblem(
+ ParseMessages.unmatchedCloseTag(tagName, startLine),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ // Special case for the content tag
+
+ if (tag._content)
+ {
+ addTextToken(cursorStart - 1);
+
+ // Advance the cursor right to the end.
+
+ _cursor = length;
+ _stack.clear();
+ return;
+ }
+
+ // When a component closes, add a CLOSE tag.
+ if (tag._component)
+ {
+ addTextToken(cursorStart - 1);
+
+ _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
+ }
+ else
+ {
+ // The close of a static tag. Unless removing the tag
+ // entirely, make sure the block tag is part of a text block.
+
+ if (_blockStart < 0 && !tag._removeTag && !_ignoring)
+ _blockStart = cursorStart;
+ }
+
+ // Remove all elements at stackPos or above.
+
+ for (int i = _stack.size() - 1; i >= stackPos; i--)
+ _stack.remove(i);
+
+ // Advance cursor past '>'
+
+ advance();
+
+ // If editting out the tag (i.e., $remove$) then kill any whitespace.
+ // For components that simply don't contain a body, removeTag will
+ // be false.
+
+ if (tag._removeTag)
+ advanceOverWhitespace();
+
+ // If we were ignoring the body of the tag, then clear the ignoring
+ // flag, since we're out of the body.
+
+ if (tag._ignoringBody)
+ _ignoring = false;
+ }
+
+ /**
+ * Advances the cursor to the next character. If the end-of-line is reached, then increments the
+ * line counter.
+ */
+
+ private void advance()
+ {
+ int length = _templateData.length;
+
+ if (_cursor >= length)
+ return;
+
+ char ch = _templateData[_cursor];
+
+ _cursor++;
+
+ if (ch == '\n')
+ {
+ _line++;
+ _currentLocation = null;
+ return;
+ }
+
+ // A \r, or a \r\n also counts as a new line.
+
+ if (ch == '\r')
+ {
+ _line++;
+ _currentLocation = null;
+
+ if (_cursor < length && _templateData[_cursor] == '\n')
+ _cursor++;
+
+ return;
+ }
+
+ // Not an end-of-line character.
+
+ }
+
+ private void advanceOverWhitespace()
+ {
+ int length = _templateData.length;
+
+ while (_cursor < length)
+ {
+ char ch = _templateData[_cursor];
+ if (!Character.isWhitespace(ch))
+ return;
+
+ advance();
+ }
+ }
+
+ /**
+ * Returns a new Map that is a copy of the input Map with some key/value pairs removed. A list
+ * of keys is passed in and matching keys (caseless comparison) from the input Map are excluded
+ * from the output map. May return null (rather than return an empty Map).
+ */
+
+ private Map filter(Map input, String[] removeKeys)
+ {
+ if (input == null || input.isEmpty())
+ return null;
+
+ Map result = null;
+
+ Iterator i = input.entrySet().iterator();
+
+ nextkey: while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String key = (String) entry.getKey();
+
+ for (int j = 0; j < removeKeys.length; j++)
+ {
+ if (key.equalsIgnoreCase(removeKeys[j]))
+ continue nextkey;
+ }
+
+ if (result == null)
+ result = new HashMap(input.size());
+
+ result.put(key, entry.getValue());
+ }
+
+ return result;
+ }
+
+ /**
+ * Searches a Map for given key, caselessly. The Map is expected to consist of Strings for keys
+ * and values. Returns the value for the first key found that matches (caselessly) the input
+ * key. Returns null if no value found.
+ */
+
+ protected String findValueCaselessly(String key, Map map)
+ {
+ String result = (String) map.get(key);
+
+ if (result != null)
+ return result;
+
+ Iterator i = map.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String entryKey = (String) entry.getKey();
+
+ if (entryKey.equalsIgnoreCase(key))
+ return (String) entry.getValue();
+ }
+
+ return null;
+ }
+
+ /**
+ * Conversions needed by {@link #convertEntitiesToPlain(String)}
+ */
+
+ private static final String[] CONVERSIONS =
+ { "<", "<", ">", ">", """, "\"", "&", "&" };
+
+ /**
+ * Provided a raw input string that has been recognized to be an expression, this removes excess
+ * white space and converts &amp;;, &quot;; &lt;; and &gt;; to their normal
+ * character values (otherwise its impossible to specify those values in expressions in the
+ * template).
+ */
+
+ private String convertEntitiesToPlain(String input)
+ {
+ int inputLength = input.length();
+
+ StringBuffer buffer = new StringBuffer(inputLength);
+
+ int cursor = 0;
+
+ outer: while (cursor < inputLength)
+ {
+ for (int i = 0; i < CONVERSIONS.length; i += 2)
+ {
+ String entity = CONVERSIONS[i];
+ int entityLength = entity.length();
+ String value = CONVERSIONS[i + 1];
+
+ if (cursor + entityLength > inputLength)
+ continue;
+
+ if (input.substring(cursor, cursor + entityLength).equals(entity))
+ {
+ buffer.append(value);
+ cursor += entityLength;
+ continue outer;
+ }
+ }
+
+ buffer.append(input.charAt(cursor));
+ cursor++;
+ }
+
+ return buffer.toString().trim();
+ }
+
+ /**
+ * Returns true if the map contains the given key (caseless search) and the value is "true"
+ * (caseless comparison).
+ */
+
+ private boolean checkBoolean(String key, Map map)
+ {
+ String value = findValueCaselessly(key, map);
+
+ if (value == null)
+ return false;
+
+ return value.equalsIgnoreCase("true");
+ }
+
+ /**
+ * Gets the current location within the file. This allows the location to be created only as
+ * needed, and multiple objects on the same line can share the same Location instance.
+ *
+ * @since 3.0
+ */
+
+ protected Location getCurrentLocation()
+ {
+ if (_currentLocation == null)
+ _currentLocation = new LocationImpl(_resourceLocation, _line);
+
+ return _currentLocation;
+ }
+
+ public void setFactory(TemplateTokenFactory factory)
+ {
+ _factory = factory;
}
-}
+}
\ No newline at end of file
Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TextToken.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2004, 2005, 2006 The Apache Software Foundation
+// Copyright 2004, 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@
import org.apache.tapestry.IRequestCycle;
/**
- * Represents static text in the template that may be passed through to the
- * client unchanged (except, perhaps, for the removal of some whitespace).
+ * Represents static text in the template that may be passed through to the client unchanged
+ * (except, perhaps, for the removal of some whitespace).
*
* @see TokenType#TEXT
* @author Howard Lewis Ship
@@ -32,7 +32,6 @@
public class TextToken extends TemplateToken implements IRender
{
-
private char[] _templateData;
private int _offset;
@@ -43,9 +42,11 @@
{
super(TokenType.TEXT, location);
- if (startIndex < 0 || endIndex < 0 || startIndex > templateData.length || endIndex > templateData.length)
- throw new ApplicationRuntimeException(ParseMessages.rangeError(this, templateData.length), this,
- getLocation(), null);
+ if (startIndex < 0 || endIndex < 0 || startIndex > templateData.length
+ || endIndex > templateData.length)
+ throw new ApplicationRuntimeException(ParseMessages.rangeError(
+ this,
+ templateData.length), this, getLocation(), null);
_templateData = templateData;
@@ -55,14 +56,14 @@
public void render(IMarkupWriter writer, IRequestCycle cycle)
{
- if (_length == 0) return;
+ if (_length == 0)
+ return;
// At one time, we would check to see if the cycle was rewinding and
// only invoke printRaw() if it was. However, that slows down
// normal rendering (microscopically) and, with the new
// NullResponseWriter class, the "cost" of invoking cycle.isRewinding()
- // is approximately the same as the "cost" of invoking
- // writer.printRaw().
+ // is approximately the same as the "cost" of invoking writer.printRaw().
writer.printRaw(_templateData, _offset, _length);
}
@@ -87,4 +88,4 @@
{
return _offset;
}
-}
+}
\ No newline at end of file
Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/parse/TokenType.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2004, 2005, 2006 The Apache Software Foundation
+// Copyright 2004, 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -21,9 +21,8 @@
* @author Howard Lewis Ship
*/
-public final class TokenType
+public class TokenType
{
-
/**
* Raw HTML text.
*
@@ -72,4 +71,4 @@
{
return _name;
}
-}
+}
\ No newline at end of file
Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/PersistentPropertyDataEncoderImpl.java Sat Mar 11 12:54:27 2006
@@ -50,6 +50,8 @@
*/
public class PersistentPropertyDataEncoderImpl implements PersistentPropertyDataEncoder
{
+ private ClassResolver _classResolver;
+
/**
* Prefix on the MIME encoding that indicates that the encoded data is not encoded.
*/
@@ -62,8 +64,6 @@
public static final String GZIP_BYTESTREAM_PREFIX = "Z";
- private ClassResolver _classResolver;
-
public String encodePageChanges(List changes)
{
Defense.notNull(changes, "changes");
@@ -193,4 +193,4 @@
{
_classResolver = resolver;
}
-}
+}
\ No newline at end of file
Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java?rev=385164&r1=385163&r2=385164&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/record/RecordMessages.java Sat Mar 11 12:54:27 2006
@@ -1,4 +1,4 @@
-// Copyright 2005, 2006 The Apache Software Foundation
+// Copyright 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
package org.apache.tapestry.record;
-import org.apache.hivemind.Messages;
import org.apache.hivemind.impl.MessageFormatter;
import org.apache.tapestry.IComponent;
@@ -22,44 +21,38 @@
* @author Howard M. Lewis Ship
* @since 4.0
*/
-final class RecordMessages
+class RecordMessages
{
-
- private final static Messages MESSAGES = new MessageFormatter(RecordMessages.class);
-
- /** @since 4.1 */
- private RecordMessages()
- {
- }
+ private final static MessageFormatter _formatter = new MessageFormatter(RecordMessages.class);
static String unknownPersistenceStrategy(String name)
{
- return MESSAGES.format("unknown-persistence-strategy", name);
+ return _formatter.format("unknown-persistence-strategy", name);
}
static String missingPropertySpecification(String propertyName, IComponent component)
{
- return MESSAGES.format("missing-property-specification", propertyName, component.getExtendedId(), component
- .getSpecification().getSpecificationLocation());
+ return _formatter.format("missing-property-specification", propertyName, component
+ .getExtendedId(), component.getSpecification().getSpecificationLocation());
}
static String recorderLocked(String propertyName, IComponent component)
{
- return MESSAGES.format("recorder-locked", propertyName, component.getExtendedId());
+ return _formatter.format("recorder-locked", propertyName, component.getExtendedId());
}
static String decodeFailure(Throwable cause)
{
- return MESSAGES.format("decode-failure", cause);
+ return _formatter.format("decode-failure", cause);
}
static String encodeFailure(Throwable cause)
{
- return MESSAGES.format("encode-failure", cause);
+ return _formatter.format("encode-failure", cause);
}
static String unknownPrefix(String prefix)
{
- return MESSAGES.format("unknown-prefix", prefix);
+ return _formatter.format("unknown-prefix", prefix);
}
-}
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: tapestry-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tapestry-dev-help@jakarta.apache.org