/* $$ Clover has instrumented this file $$ */// Copyright 2004 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.parse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.hivemind.impl.LocationImpl;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.MatchResult;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;
import org.apache.tapestry.util.IdAllocator;
/**
* 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.
* <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>".
* <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).
* <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).
* <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
* {@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.
* <p>
* 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.
* <p>
* 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.
* @author Howard Lewis Ship, Geoff Longman
*/
public class TemplateParser implements ITemplateParser
{public static com.cortexeb.tools.clover.d __CLOVER_227_0 = com.cortexeb.tools.clover.aq.getRecorder(new char[] {67,58,92,119,111,114,107,115,112,97,99,101,92,106,97,107,97,114,116,97,45,116,97,112,101,115,116,114,121,92,102,114,97,109,101,119,111,114,107,92,116,97,114,103,101,116,92,99,108,111,118,101,114,45,100,98},1096998272901L);
/**
* Attribute value prefix indicating that the attribute is an OGNL expression.
*
* @since 3.0
*/
public static final String OGNL_EXPRESSION_PREFIX = "ognl:";
/**
* Attribute value prefix indicating that the attribute is a localization key.
*
* @since 3.0
*/
public static final String LOCALIZATION_KEY_PREFIX = "message:";
/**
* A "magic" component id that causes the tag with the id and its entire body to be ignored
* during parsing.
*/
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.
*/
private static final String CONTENT_ID = "$content$";
/**
* The attribute, checked for in <span> tags, that signfies that the span is being used as
* an invisible localization.
*
* @since 2.0.4
*/
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).
*
* @since 2.3
*/
public static final String RAW_ATTRIBUTE_NAME = "raw";
/**
* Attribute used to identify components.
*
* @since 2.3
*/
public static final String JWCID_ATTRIBUTE_NAME = "jwcid";
private static final String PROPERTY_NAME_PATTERN = "_?[a-zA-Z]\\w*";
/**
* Pattern used to recognize ordinary components (defined in the specification).
*
* @since 3.0
*/
public static final String SIMPLE_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")$";
/**
* 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.
*
* @since 3.0
*/
public static final String IMPLICIT_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")?@(((" + PROPERTY_NAME_PATTERN
+ "):)?(" + PROPERTY_NAME_PATTERN + "))$";
private static final int IMPLICIT_ID_PATTERN_ID_GROUP = 1;
private static final int IMPLICIT_ID_PATTERN_TYPE_GROUP = 2;
private static final int IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP = 4;
private static final int IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP = 5;
private Pattern _simpleIdPattern;
private Pattern _implicitIdPattern;
private PatternMatcher _patternMatcher;
private IdAllocator _idAllocator = new IdAllocator();
private ITemplateParserDelegate _delegate;
/**
* Identifies the template being parsed; used with error messages.
*/
private Resource _resourceLocation;
/**
* Shared instance of {@link Location}used by all {@link TextToken}instances in the template.
*/
private Location _templateLocation;
/**
* Location with in the resource for the current line.
*/
private Location _currentLocation;
/**
* Local reference to the template data that is to be parsed.
*/
private char[] _templateData;
/**
* 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, the body of the tag is being ignored, and the
// ignore flag is cleared when the close tag is reached
boolean _ignoringBody;
// 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;
// The line on which the start tag exists
int _line;
// If true, then the parse ends when the closing tag is found.
boolean _content;
Tag(String tagName, int line)
{try { __CLOVER_227_0.M[1180]++;
__CLOVER_227_0.S[5555]++;_tagName = tagName;
__CLOVER_227_0.S[5556]++;_line = line;
} finally { }}
boolean match(String matchTagName)
{try { __CLOVER_227_0.M[1181]++;
__CLOVER_227_0.S[5557]++;return _tagName.equalsIgnoreCase(matchTagName);
} finally { }}
}
/**
* List of {@link TemplateToken}, this forms the ultimate response.
*/
private List _tokens = new ArrayList();
/**
* The location of the 'cursor' within the template data. The advance() method moves this
* forward.
*/
private int _cursor;
/**
* The start of the current block of static text, or -1 if no block is active.
*/
private int _blockStart;
/**
* The current line number; tracked by advance(). Starts at 1.
*/
private int _line;
/**
* 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 boolean _ignoring;
/**
* A {@link Map}of {@link String}s, used to store attributes collected while parsing a tag.
*/
private Map _attributes = new HashMap();
/**
* A factory used to create template tokens.
*/
private TemplateTokenFactory _factory;
public TemplateParser()
{try { __CLOVER_227_0.M[1182]++;
__CLOVER_227_0.S[5558]++;Perl5Compiler compiler = new Perl5Compiler();
__CLOVER_227_0.S[5559]++;try
{
__CLOVER_227_0.S[5560]++;_simpleIdPattern = compiler.compile(SIMPLE_ID_PATTERN);
__CLOVER_227_0.S[5561]++;_implicitIdPattern = compiler.compile(IMPLICIT_ID_PATTERN);
}
catch (MalformedPatternException ex)
{
__CLOVER_227_0.S[5562]++;throw new ApplicationRuntimeException(ex);
}
__CLOVER_227_0.S[5563]++;_patternMatcher = new Perl5Matcher();
} finally { }}
/**
* 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.
*/
public TemplateToken[] parse(char[] templateData, ITemplateParserDelegate delegate, Resource resourceLocation)
throws TemplateParseException
{try { __CLOVER_227_0.M[1183]++;
__CLOVER_227_0.S[5564]++;try
{
__CLOVER_227_0.S[5565]++;beforeParse(templateData, delegate, resourceLocation);
__CLOVER_227_0.S[5566]++;parse();
__CLOVER_227_0.S[5567]++;return (TemplateToken[]) _tokens.toArray(new TemplateToken[_tokens.size()]);
}
finally
{
__CLOVER_227_0.S[5568]++;afterParse();
}
} finally { }}
/**
* perform default initialization of the parser.
*/
protected void beforeParse(char[] templateData, ITemplateParserDelegate delegate, Resource resourceLocation)
{try { __CLOVER_227_0.M[1184]++;
__CLOVER_227_0.S[5569]++;_templateData = templateData;
__CLOVER_227_0.S[5570]++;_resourceLocation = resourceLocation;
__CLOVER_227_0.S[5571]++;_templateLocation = new LocationImpl(resourceLocation);
__CLOVER_227_0.S[5572]++;_delegate = delegate;
__CLOVER_227_0.S[5573]++;_ignoring = false;
__CLOVER_227_0.S[5574]++;_line = 1;
} finally { }}
/**
* Perform default cleanup after parsing completes.
*/
protected void afterParse()
{try { __CLOVER_227_0.M[1185]++;
__CLOVER_227_0.S[5575]++;_delegate = null;
__CLOVER_227_0.S[5576]++;_templateData = null;
__CLOVER_227_0.S[5577]++;_resourceLocation = null;
__CLOVER_227_0.S[5578]++;_templateLocation = null;
__CLOVER_227_0.S[5579]++;_currentLocation = null;
__CLOVER_227_0.S[5580]++;_stack.clear();
__CLOVER_227_0.S[5581]++;_tokens.clear();
__CLOVER_227_0.S[5582]++;_attributes.clear();
__CLOVER_227_0.S[5583]++;_idAllocator.clear();
} finally { }}
/**
* 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.
* <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 templateParseProblem(String message, Location location, int line, int cursor)
throws TemplateParseException
{try { __CLOVER_227_0.M[1186]++;
__CLOVER_227_0.S[5584]++;throw new TemplateParseException(message, location);
} finally { }}
/**
* 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.
* <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.
*/
protected void templateParseProblem(ApplicationRuntimeException exception, int line, int cursor)
throws ApplicationRuntimeException
{try { __CLOVER_227_0.M[1187]++;
__CLOVER_227_0.S[5585]++;throw exception;
} finally { }}
/**
* Give subclasses access to the parse results.
*/
protected List getTokens()
{try { __CLOVER_227_0.M[1188]++;
__CLOVER_227_0.S[5586]++;if ((((_tokens == null) && (++__CLOVER_227_0.CT[966] != 0)) || (++__CLOVER_227_0.CF[966] == 0))){
__CLOVER_227_0.S[5587]++;return Collections.EMPTY_LIST;}
__CLOVER_227_0.S[5588]++;return _tokens;
} finally { }}
/**
* Checks to see if the next few characters match a given pattern.
*/
private boolean lookahead(char[] match)
{try { __CLOVER_227_0.M[1189]++;
__CLOVER_227_0.S[5589]++;try
{
__CLOVER_227_0.S[5590]++;for (int i = 0; (((i < match.length) && (++__CLOVER_227_0.CT[967] != 0)) || (++__CLOVER_227_0.CF[967] == 0)); i++){
{
__CLOVER_227_0.S[5591]++;if ((((_templateData[_cursor + i] != match[i]) && (++__CLOVER_227_0.CT[968] != 0)) || (++__CLOVER_227_0.CF[968] == 0))){
__CLOVER_227_0.S[5592]++;return false;}
}}
// Every character matched.
__CLOVER_227_0.S[5593]++;return true;
}
catch (IndexOutOfBoundsException ex)
{
__CLOVER_227_0.S[5594]++;return false;
}
} finally { }}
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
{try { __CLOVER_227_0.M[1190]++;
__CLOVER_227_0.S[5595]++;_cursor = 0;
__CLOVER_227_0.S[5596]++;_blockStart = -1;
__CLOVER_227_0.S[5597]++;int length = _templateData.length;
__CLOVER_227_0.S[5598]++;while ((((_cursor < length) && (++__CLOVER_227_0.CT[969] != 0)) || (++__CLOVER_227_0.CF[969] == 0))){
{
__CLOVER_227_0.S[5599]++;if ((((_templateData[_cursor] != '<') && (++__CLOVER_227_0.CT[970] != 0)) || (++__CLOVER_227_0.CF[970] == 0))){
{
__CLOVER_227_0.S[5600]++;if ((((_blockStart < 0 && !_ignoring) && (++__CLOVER_227_0.CT[971] != 0)) || (++__CLOVER_227_0.CF[971] == 0))){
__CLOVER_227_0.S[5601]++;_blockStart = _cursor;}
__CLOVER_227_0.S[5602]++;advance();
__CLOVER_227_0.S[5603]++;continue;
}}
// OK, start of something.
__CLOVER_227_0.S[5604]++;if ((((lookahead(CLOSE_TAG)) && (++__CLOVER_227_0.CT[972] != 0)) || (++__CLOVER_227_0.CF[972] == 0))){
{
__CLOVER_227_0.S[5605]++;closeTag();
__CLOVER_227_0.S[5606]++;continue;
}}
__CLOVER_227_0.S[5607]++;if ((((lookahead(COMMENT_START)) && (++__CLOVER_227_0.CT[973] != 0)) || (++__CLOVER_227_0.CF[973] == 0))){
{
__CLOVER_227_0.S[5608]++;skipComment();
__CLOVER_227_0.S[5609]++;continue;
}}
// The start of some tag.
__CLOVER_227_0.S[5610]++;startTag();
}}
// 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.
__CLOVER_227_0.S[5611]++;addTextToken(_templateData.length - 1);
} finally { }}
/**
* 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
{try { __CLOVER_227_0.M[1191]++;
__CLOVER_227_0.S[5612]++;int length = _templateData.length;
__CLOVER_227_0.S[5613]++;int startLine = _line;
__CLOVER_227_0.S[5614]++;if ((((_blockStart < 0 && !_ignoring) && (++__CLOVER_227_0.CT[974] != 0)) || (++__CLOVER_227_0.CF[974] == 0))){
__CLOVER_227_0.S[5615]++;_blockStart = _cursor;}
__CLOVER_227_0.S[5616]++;while (true){
{
__CLOVER_227_0.S[5617]++;if ((((_cursor >= length) && (++__CLOVER_227_0.CT[975] != 0)) || (++__CLOVER_227_0.CF[975] == 0))){
__CLOVER_227_0.S[5618]++;templateParseProblem(ParseMessages.commentNotEnded(startLine), new LocationImpl(_resourceLocation,
startLine), startLine, _cursor);}
__CLOVER_227_0.S[5619]++;if ((((lookahead(COMMENT_END)) && (++__CLOVER_227_0.CT[976] != 0)) || (++__CLOVER_227_0.CF[976] == 0))){
__CLOVER_227_0.S[5620]++;break;}
// Not the end of the comment, advance over it.
__CLOVER_227_0.S[5621]++;advance();
}}
__CLOVER_227_0.S[5622]++;_cursor += COMMENT_END.length;
__CLOVER_227_0.S[5623]++;advanceOverWhitespace();
} finally { }}
private void addTextToken(int end)
{try { __CLOVER_227_0.M[1192]++;
// No active block to add to.
__CLOVER_227_0.S[5624]++;if ((((_blockStart < 0) && (++__CLOVER_227_0.CT[977] != 0)) || (++__CLOVER_227_0.CF[977] == 0))){
__CLOVER_227_0.S[5625]++;return;}
__CLOVER_227_0.S[5626]++;if ((((_blockStart <= end) && (++__CLOVER_227_0.CT[978] != 0)) || (++__CLOVER_227_0.CF[978] == 0))){
{
// This seems odd, shouldn't the location be the current location? I guess
// no errors are ever reported for a text token.
__CLOVER_227_0.S[5627]++;TemplateToken token = _factory.createTextToken(_templateData, _blockStart, end, _templateLocation);
__CLOVER_227_0.S[5628]++;_tokens.add(token);
}}
__CLOVER_227_0.S[5629]++;_blockStart = -1;
} finally { }}
private static final int WAIT_FOR_ATTRIBUTE_NAME = 0;
private static final int COLLECT_ATTRIBUTE_NAME = 1;
private static final int ADVANCE_PAST_EQUALS = 2;
private static final int WAIT_FOR_ATTRIBUTE_VALUE = 3;
private static final int COLLECT_QUOTED_VALUE = 4;
private static final int COLLECT_UNQUOTED_VALUE = 5;
private void startTag() throws TemplateParseException
{try { __CLOVER_227_0.M[1193]++;
__CLOVER_227_0.S[5630]++;int cursorStart = _cursor;
__CLOVER_227_0.S[5631]++;int length = _templateData.length;
__CLOVER_227_0.S[5632]++;String tagName = null;
__CLOVER_227_0.S[5633]++;boolean endOfTag = false;
__CLOVER_227_0.S[5634]++;boolean emptyTag = false;
__CLOVER_227_0.S[5635]++;int startLine = _line;
__CLOVER_227_0.S[5636]++;Location startLocation = new LocationImpl(_resourceLocation, startLine);
__CLOVER_227_0.S[5637]++;tagBeginEvent(startLine, _cursor);
__CLOVER_227_0.S[5638]++;advance();
// Collect the element type
__CLOVER_227_0.S[5639]++;while ((((_cursor < length) && (++__CLOVER_227_0.CT[979] != 0)) || (++__CLOVER_227_0.CF[979] == 0))){
{
__CLOVER_227_0.S[5640]++;char ch = _templateData[_cursor];
__CLOVER_227_0.S[5641]++;if ((((ch == '/' || ch == '>' || Character.isWhitespace(ch)) && (++__CLOVER_227_0.CT[980] != 0)) || (++__CLOVER_227_0.CF[980] == 0))){
{
__CLOVER_227_0.S[5642]++;tagName = new String(_templateData, cursorStart + 1, _cursor - cursorStart - 1);
__CLOVER_227_0.S[5643]++;break;
}}
__CLOVER_227_0.S[5644]++;advance();
}}
__CLOVER_227_0.S[5645]++;String attributeName = null;
__CLOVER_227_0.S[5646]++;int attributeNameStart = -1;
__CLOVER_227_0.S[5647]++;int attributeValueStart = -1;
__CLOVER_227_0.S[5648]++;int state = WAIT_FOR_ATTRIBUTE_NAME;
__CLOVER_227_0.S[5649]++;char quoteChar = 0;
__CLOVER_227_0.S[5650]++;_attributes.clear();
// Collect each attribute
__CLOVER_227_0.S[5651]++;while ((((!endOfTag) && (++__CLOVER_227_0.CT[981] != 0)) || (++__CLOVER_227_0.CF[981] == 0))){
{
__CLOVER_227_0.S[5652]++;if ((((_cursor >= length) && (++__CLOVER_227_0.CT[982] != 0)) || (++__CLOVER_227_0.CF[982] == 0))){
{
__CLOVER_227_0.S[5653]++;String message = ((((tagName == null) ) && (++__CLOVER_227_0.CT[983] != 0)) || (++__CLOVER_227_0.CF[983] == 0))? ParseMessages.unclosedUnknownTag(startLine) : ParseMessages
.unclosedTag(tagName, startLine);
__CLOVER_227_0.S[5654]++;templateParseProblem(message, startLocation, startLine, cursorStart);
}}
__CLOVER_227_0.S[5655]++;char ch = _templateData[_cursor];
__CLOVER_227_0.S[5656]++;switch (state)
{
case WAIT_FOR_ATTRIBUTE_NAME:
// Ignore whitespace before the next attribute name, while
// looking for the end of the current tag.
__CLOVER_227_0.S[5657]++;if ((((ch == '/') && (++__CLOVER_227_0.CT[984] != 0)) || (++__CLOVER_227_0.CF[984] == 0))){
{
__CLOVER_227_0.S[5658]++;emptyTag = true;
__CLOVER_227_0.S[5659]++;advance();
__CLOVER_227_0.S[5660]++;break;
}}
__CLOVER_227_0.S[5661]++;if ((((ch == '>') && (++__CLOVER_227_0.CT[985] != 0)) || (++__CLOVER_227_0.CF[985] == 0))){
{
__CLOVER_227_0.S[5662]++;endOfTag = true;
__CLOVER_227_0.S[5663]++;break;
}}
__CLOVER_227_0.S[5664]++;if ((((Character.isWhitespace(ch)) && (++__CLOVER_227_0.CT[986] != 0)) || (++__CLOVER_227_0.CF[986] == 0))){
{
__CLOVER_227_0.S[5665]++;advance();
__CLOVER_227_0.S[5666]++;break;
}}
// Found non-whitespace, assume its the attribute name.
// Note: could use a check here for non-alpha.
__CLOVER_227_0.S[5667]++;attributeNameStart = _cursor;
__CLOVER_227_0.S[5668]++;state = COLLECT_ATTRIBUTE_NAME;
__CLOVER_227_0.S[5669]++;advance();
__CLOVER_227_0.S[5670]++;break;
case COLLECT_ATTRIBUTE_NAME:
// Looking for end of attribute name.
__CLOVER_227_0.S[5671]++;if ((((ch == '=' || ch == '/' || ch == '>' || Character.isWhitespace(ch)) && (++__CLOVER_227_0.CT[987] != 0)) || (++__CLOVER_227_0.CF[987] == 0))){
{
__CLOVER_227_0.S[5672]++;attributeName = new String(_templateData, attributeNameStart, _cursor - attributeNameStart);
__CLOVER_227_0.S[5673]++;state = ADVANCE_PAST_EQUALS;
__CLOVER_227_0.S[5674]++;break;
}}
// Part of the attribute name
__CLOVER_227_0.S[5675]++;advance();
__CLOVER_227_0.S[5676]++;break;
case ADVANCE_PAST_EQUALS:
// Looking for the '=' sign. May hit the end of the tag, or (for bare attributes),
// the next attribute name.
__CLOVER_227_0.S[5677]++;if ((((ch == '/' || ch == '>') && (++__CLOVER_227_0.CT[988] != 0)) || (++__CLOVER_227_0.CF[988] == 0))){
{
// A bare attribute, which is not interesting to
// us.
__CLOVER_227_0.S[5678]++;state = WAIT_FOR_ATTRIBUTE_NAME;
__CLOVER_227_0.S[5679]++;break;
}}
__CLOVER_227_0.S[5680]++;if ((((Character.isWhitespace(ch)) && (++__CLOVER_227_0.CT[989] != 0)) || (++__CLOVER_227_0.CF[989] == 0))){
{
__CLOVER_227_0.S[5681]++;advance();
__CLOVER_227_0.S[5682]++;break;
}}
__CLOVER_227_0.S[5683]++;if ((((ch == '=') && (++__CLOVER_227_0.CT[990] != 0)) || (++__CLOVER_227_0.CF[990] == 0))){
{
__CLOVER_227_0.S[5684]++;state = WAIT_FOR_ATTRIBUTE_VALUE;
__CLOVER_227_0.S[5685]++;quoteChar = 0;
__CLOVER_227_0.S[5686]++;attributeValueStart = -1;
__CLOVER_227_0.S[5687]++;advance();
__CLOVER_227_0.S[5688]++;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).
__CLOVER_227_0.S[5689]++;state = WAIT_FOR_ATTRIBUTE_NAME;
__CLOVER_227_0.S[5690]++;break;
case WAIT_FOR_ATTRIBUTE_VALUE:
__CLOVER_227_0.S[5691]++;if ((((ch == '/' || ch == '>') && (++__CLOVER_227_0.CT[991] != 0)) || (++__CLOVER_227_0.CF[991] == 0))){
__CLOVER_227_0.S[5692]++;templateParseProblem(ParseMessages.missingAttributeValue(tagName, _line, attributeName),
getCurrentLocation(), _line, _cursor);}
// Ignore whitespace between '=' and the attribute value. Also, look
// for initial quote.
__CLOVER_227_0.S[5693]++;if ((((Character.isWhitespace(ch)) && (++__CLOVER_227_0.CT[992] != 0)) || (++__CLOVER_227_0.CF[992] == 0))){
{
__CLOVER_227_0.S[5694]++;advance();
__CLOVER_227_0.S[5695]++;break;
}}
__CLOVER_227_0.S[5696]++;if ((((ch == '\'' || ch == '"') && (++__CLOVER_227_0.CT[993] != 0)) || (++__CLOVER_227_0.CF[993] == 0))){
{
__CLOVER_227_0.S[5697]++;quoteChar = ch;
__CLOVER_227_0.S[5698]++;state = COLLECT_QUOTED_VALUE;
__CLOVER_227_0.S[5699]++;advance();
__CLOVER_227_0.S[5700]++;attributeValueStart = _cursor;
__CLOVER_227_0.S[5701]++;attributeBeginEvent(attributeName, _line, attributeValueStart);
__CLOVER_227_0.S[5702]++;break;
}}
// Not whitespace or quote, must be start of unquoted attribute.
__CLOVER_227_0.S[5703]++;state = COLLECT_UNQUOTED_VALUE;
__CLOVER_227_0.S[5704]++;attributeValueStart = _cursor;
__CLOVER_227_0.S[5705]++;attributeBeginEvent(attributeName, _line, attributeValueStart);
__CLOVER_227_0.S[5706]++;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.
__CLOVER_227_0.S[5707]++;if ((((ch == quoteChar) && (++__CLOVER_227_0.CT[994] != 0)) || (++__CLOVER_227_0.CF[994] == 0))){
{
__CLOVER_227_0.S[5708]++;String attributeValue = new String(_templateData, attributeValueStart, _cursor
- attributeValueStart);
__CLOVER_227_0.S[5709]++;_attributes.put(attributeName, attributeValue);
__CLOVER_227_0.S[5710]++;attributeEndEvent(_cursor);
// Advance over the quote.
__CLOVER_227_0.S[5711]++;advance();
__CLOVER_227_0.S[5712]++;state = WAIT_FOR_ATTRIBUTE_NAME;
__CLOVER_227_0.S[5713]++;break;
}}
__CLOVER_227_0.S[5714]++;advance();
__CLOVER_227_0.S[5715]++;break;
case COLLECT_UNQUOTED_VALUE:
// An unquoted attribute value ends with whitespace
// or the end of the enclosing tag.
__CLOVER_227_0.S[5716]++;if ((((ch == '/' || ch == '>' || Character.isWhitespace(ch)) && (++__CLOVER_227_0.CT[995] != 0)) || (++__CLOVER_227_0.CF[995] == 0))){
{
__CLOVER_227_0.S[5717]++;String attributeValue = new String(_templateData, attributeValueStart, _cursor
- attributeValueStart);
__CLOVER_227_0.S[5718]++;_attributes.put(attributeName, attributeValue);
__CLOVER_227_0.S[5719]++;attributeEndEvent(_cursor);
__CLOVER_227_0.S[5720]++;state = WAIT_FOR_ATTRIBUTE_NAME;
__CLOVER_227_0.S[5721]++;break;
}}
__CLOVER_227_0.S[5722]++;advance();
__CLOVER_227_0.S[5723]++;break;
}
}}
__CLOVER_227_0.S[5724]++;tagEndEvent(_cursor);
// Check for invisible localizations
__CLOVER_227_0.S[5725]++;String localizationKey = findValueCaselessly(LOCALIZATION_KEY_ATTRIBUTE_NAME, _attributes);
__CLOVER_227_0.S[5726]++;String jwcId = findValueCaselessly(JWCID_ATTRIBUTE_NAME, _attributes);
__CLOVER_227_0.S[5727]++;if ((((localizationKey != null && tagName.equalsIgnoreCase("span") && jwcId == null) && (++__CLOVER_227_0.CT[996] != 0)) || (++__CLOVER_227_0.CF[996] == 0))){
{
__CLOVER_227_0.S[5728]++;if ((((_ignoring) && (++__CLOVER_227_0.CT[997] != 0)) || (++__CLOVER_227_0.CF[997] == 0))){
__CLOVER_227_0.S[5729]++;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.
__CLOVER_227_0.S[5730]++;if ((((!emptyTag) && (++__CLOVER_227_0.CT[998] != 0)) || (++__CLOVER_227_0.CF[998] == 0))){
{
__CLOVER_227_0.S[5731]++;Tag tag = new Tag(tagName, startLine);
__CLOVER_227_0.S[5732]++;tag._component = false;
__CLOVER_227_0.S[5733]++;tag._removeTag = true;
__CLOVER_227_0.S[5734]++;tag._ignoringBody = true;
__CLOVER_227_0.S[5735]++;tag._mustBalance = true;
__CLOVER_227_0.S[5736]++;_stack.add(tag);
// Start ignoring content until the close tag.
__CLOVER_227_0.S[5737]++;_ignoring = true;
}}
else{
{
// Cursor is at the closing carat, advance over it and any whitespace.
__CLOVER_227_0.S[5738]++;advance();
__CLOVER_227_0.S[5739]++;advanceOverWhitespace();
}}
// End any open block.
__CLOVER_227_0.S[5740]++;addTextToken(cursorStart - 1);
__CLOVER_227_0.S[5741]++;boolean raw = checkBoolean(RAW_ATTRIBUTE_NAME, _attributes);
__CLOVER_227_0.S[5742]++;Map attributes = filter(_attributes, new String[]
{ LOCALIZATION_KEY_ATTRIBUTE_NAME, RAW_ATTRIBUTE_NAME });
__CLOVER_227_0.S[5743]++;TemplateToken token = _factory.createLocalizationToken(tagName, localizationKey, raw, attributes,
startLocation);
__CLOVER_227_0.S[5744]++;_tokens.add(token);
__CLOVER_227_0.S[5745]++;return;
}}
__CLOVER_227_0.S[5746]++;if ((((jwcId != null) && (++__CLOVER_227_0.CT[999] != 0)) || (++__CLOVER_227_0.CF[999] == 0))){
{
__CLOVER_227_0.S[5747]++;processComponentStart(tagName, jwcId, emptyTag, startLine, cursorStart, startLocation);
__CLOVER_227_0.S[5748]++;return;
}}
// A static tag (not a tag without a jwcid attribute).
// We need to record this so that we can match close tags later.
__CLOVER_227_0.S[5749]++;if ((((!emptyTag) && (++__CLOVER_227_0.CT[1000] != 0)) || (++__CLOVER_227_0.CF[1000] == 0))){
{
__CLOVER_227_0.S[5750]++;Tag tag = new Tag(tagName, startLine);
__CLOVER_227_0.S[5751]++;_stack.add(tag);
}}
// If there wasn't an active block, then start one.
__CLOVER_227_0.S[5752]++;if ((((_blockStart < 0 && !_ignoring) && (++__CLOVER_227_0.CT[1001] != 0)) || (++__CLOVER_227_0.CF[1001] == 0))){
__CLOVER_227_0.S[5753]++;_blockStart = cursorStart;}
__CLOVER_227_0.S[5754]++;advance();
} finally { }}
/**
* 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.
*/
protected void tagBeginEvent(int startLine, int cursorPosition)
{try { __CLOVER_227_0.M[1194]++;
} finally { }}
/**
* Notify that the end of the current tag has been detected.
* <p>
* Default implementation does nothing.
*/
protected void tagEndEvent(int cursorPosition)
{try { __CLOVER_227_0.M[1195]++;
} finally { }}
/**
* Notify that the beginning of an attribute value has been detected.
* <p>
* Default implementation does nothing.
*/
protected void attributeBeginEvent(String attributeName, int startLine, int cursorPosition)
{try { __CLOVER_227_0.M[1196]++;
} finally { }}
/**
* Notify that the end of the current attribute value has been detected.
* <p>
* Default implementation does nothing.
*/
protected void attributeEndEvent(int cursorPosition)
{try { __CLOVER_227_0.M[1197]++;
} finally { }}
private void processComponentStart(String tagName, String jwcId, boolean emptyTag, int startLine, int cursorStart,
Location startLocation) throws TemplateParseException
{try { __CLOVER_227_0.M[1198]++;
__CLOVER_227_0.S[5755]++;if ((((jwcId.equalsIgnoreCase(CONTENT_ID)) && (++__CLOVER_227_0.CT[1002] != 0)) || (++__CLOVER_227_0.CF[1002] == 0))){
{
__CLOVER_227_0.S[5756]++;processContentTag(tagName, startLine, cursorStart, emptyTag);
__CLOVER_227_0.S[5757]++;return;
}}
__CLOVER_227_0.S[5758]++;boolean isRemoveId = jwcId.equalsIgnoreCase(REMOVE_ID);
__CLOVER_227_0.S[5759]++;if ((((_ignoring && !isRemoveId) && (++__CLOVER_227_0.CT[1003] != 0)) || (++__CLOVER_227_0.CF[1003] == 0))){
__CLOVER_227_0.S[5760]++;templateParseProblem(ParseMessages.componentMayNotBeIgnored(tagName, startLine), startLocation, startLine,
cursorStart);}
__CLOVER_227_0.S[5761]++;String type = null;
__CLOVER_227_0.S[5762]++;boolean allowBody = false;
__CLOVER_227_0.S[5763]++;if ((((_patternMatcher.matches(jwcId, _implicitIdPattern)) && (++__CLOVER_227_0.CT[1004] != 0)) || (++__CLOVER_227_0.CF[1004] == 0))){
{
__CLOVER_227_0.S[5764]++;MatchResult match = _patternMatcher.getMatch();
__CLOVER_227_0.S[5765]++;jwcId = match.group(IMPLICIT_ID_PATTERN_ID_GROUP);
__CLOVER_227_0.S[5766]++;type = match.group(IMPLICIT_ID_PATTERN_TYPE_GROUP);
__CLOVER_227_0.S[5767]++;String libraryId = match.group(IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP);
__CLOVER_227_0.S[5768]++;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).
__CLOVER_227_0.S[5769]++;if ((((jwcId == null) && (++__CLOVER_227_0.CT[1005] != 0)) || (++__CLOVER_227_0.CF[1005] == 0))){
__CLOVER_227_0.S[5770]++;jwcId = _idAllocator.allocateId("$" + simpleType);}
__CLOVER_227_0.S[5771]++;try
{
__CLOVER_227_0.S[5772]++;allowBody = _delegate.getAllowBody(libraryId, simpleType, startLocation);
}
catch (ApplicationRuntimeException e)
{
// give subclasses a chance to handle and rethrow
__CLOVER_227_0.S[5773]++;templateParseProblem(e, startLine, cursorStart);
}
}}
else{
{
__CLOVER_227_0.S[5774]++;if ((((!isRemoveId) && (++__CLOVER_227_0.CT[1006] != 0)) || (++__CLOVER_227_0.CF[1006] == 0))){
{
__CLOVER_227_0.S[5775]++;if ((((!_patternMatcher.matches(jwcId, _simpleIdPattern)) && (++__CLOVER_227_0.CT[1007] != 0)) || (++__CLOVER_227_0.CF[1007] == 0))){
__CLOVER_227_0.S[5776]++;templateParseProblem(ParseMessages.componentIdInvalid(tagName, startLine, jwcId), startLocation,
startLine, cursorStart);}
__CLOVER_227_0.S[5777]++;if ((((!_delegate.getKnownComponent(jwcId)) && (++__CLOVER_227_0.CT[1008] != 0)) || (++__CLOVER_227_0.CF[1008] == 0))){
__CLOVER_227_0.S[5778]++;templateParseProblem(ParseMessages.unknownComponentId(tagName, startLine, jwcId), startLocation,
startLine, cursorStart);}
__CLOVER_227_0.S[5779]++;try
{
__CLOVER_227_0.S[5780]++;allowBody = _delegate.getAllowBody(jwcId, startLocation);
}
catch (ApplicationRuntimeException e)
{
// give subclasses a chance to handle and rethrow
__CLOVER_227_0.S[5781]++;templateParseProblem(e, startLine, cursorStart);
}
}}
}}
// Ignore the body if we're removing the entire tag,
// of if the corresponding component doesn't allow
// a body.
__CLOVER_227_0.S[5782]++;boolean ignoreBody = !emptyTag && (isRemoveId || !allowBody);
__CLOVER_227_0.S[5783]++;if ((((_ignoring && ignoreBody) && (++__CLOVER_227_0.CT[1009] != 0)) || (++__CLOVER_227_0.CF[1009] == 0))){
__CLOVER_227_0.S[5784]++;templateParseProblem(ParseMessages.nestedIgnore(tagName, startLine), new LocationImpl(_resourceLocation,
startLine), startLine, cursorStart);}
__CLOVER_227_0.S[5785]++;if ((((!emptyTag) && (++__CLOVER_227_0.CT[1010] != 0)) || (++__CLOVER_227_0.CF[1010] == 0))){
__CLOVER_227_0.S[5786]++;pushNewTag(tagName, startLine, isRemoveId, ignoreBody);}
// End any open block.
__CLOVER_227_0.S[5787]++;addTextToken(cursorStart - 1);
__CLOVER_227_0.S[5788]++;if ((((!isRemoveId) && (++__CLOVER_227_0.CT[1011] != 0)) || (++__CLOVER_227_0.CF[1011] == 0))){
{
__CLOVER_227_0.S[5789]++;addOpenToken(tagName, jwcId, type, startLocation);
__CLOVER_227_0.S[5790]++;if ((((emptyTag) && (++__CLOVER_227_0.CT[1012] != 0)) || (++__CLOVER_227_0.CF[1012] == 0))){
__CLOVER_227_0.S[5791]++;_tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));}
}}
__CLOVER_227_0.S[5792]++;advance();
} finally { }}
private void pushNewTag(String tagName, int startLine, boolean isRemoveId, boolean ignoreBody)
{try { __CLOVER_227_0.M[1199]++;
__CLOVER_227_0.S[5793]++;Tag tag = new Tag(tagName, startLine);
__CLOVER_227_0.S[5794]++;tag._component = !isRemoveId;
__CLOVER_227_0.S[5795]++;tag._removeTag = isRemoveId;
__CLOVER_227_0.S[5796]++;tag._ignoringBody = ignoreBody;
__CLOVER_227_0.S[5797]++;_ignoring = tag._ignoringBody;
__CLOVER_227_0.S[5798]++;tag._mustBalance = true;
__CLOVER_227_0.S[5799]++;_stack.add(tag);
} finally { }}
private void processContentTag(String tagName, int startLine, int cursorStart, boolean emptyTag)
throws TemplateParseException
{try { __CLOVER_227_0.M[1200]++;
__CLOVER_227_0.S[5800]++;if ((((_ignoring) && (++__CLOVER_227_0.CT[1013] != 0)) || (++__CLOVER_227_0.CF[1013] == 0))){
__CLOVER_227_0.S[5801]++;templateParseProblem(ParseMessages.contentBlockMayNotBeIgnored(tagName, startLine), new LocationImpl(
_resourceLocation, startLine), startLine, cursorStart);}
__CLOVER_227_0.S[5802]++;if ((((emptyTag) && (++__CLOVER_227_0.CT[1014] != 0)) || (++__CLOVER_227_0.CF[1014] == 0))){
__CLOVER_227_0.S[5803]++;templateParseProblem(ParseMessages.contentBlockMayNotBeEmpty(tagName, startLine), new LocationImpl(
_resourceLocation, startLine), startLine, cursorStart);}
__CLOVER_227_0.S[5804]++;_tokens.clear();
__CLOVER_227_0.S[5805]++;_blockStart = -1;
__CLOVER_227_0.S[5806]++;Tag tag = new Tag(tagName, startLine);
__CLOVER_227_0.S[5807]++;tag._mustBalance = true;
__CLOVER_227_0.S[5808]++;tag._content = true;
__CLOVER_227_0.S[5809]++;_stack.clear();
__CLOVER_227_0.S[5810]++;_stack.add(tag);
__CLOVER_227_0.S[5811]++;advance();
} finally { }}
private void addOpenToken(String tagName, String jwcId, String type, Location location)
{try { __CLOVER_227_0.M[1201]++;
__CLOVER_227_0.S[5812]++;OpenToken token = _factory.createOpenToken(tagName, jwcId, type, location);
__CLOVER_227_0.S[5813]++;_tokens.add(token);
__CLOVER_227_0.S[5814]++;if ((((_attributes.isEmpty()) && (++__CLOVER_227_0.CT[1015] != 0)) || (++__CLOVER_227_0.CF[1015] == 0))){
__CLOVER_227_0.S[5815]++;return;}
__CLOVER_227_0.S[5816]++;Iterator i = _attributes.entrySet().iterator();
__CLOVER_227_0.S[5817]++;while ((((i.hasNext()) && (++__CLOVER_227_0.CT[1016] != 0)) || (++__CLOVER_227_0.CF[1016] == 0))){
{
__CLOVER_227_0.S[5818]++;Map.Entry entry = (Map.Entry) i.next();
__CLOVER_227_0.S[5819]++;String key = (String) entry.getKey();
__CLOVER_227_0.S[5820]++;if ((((key.equalsIgnoreCase(JWCID_ATTRIBUTE_NAME)) && (++__CLOVER_227_0.CT[1017] != 0)) || (++__CLOVER_227_0.CF[1017] == 0))){
__CLOVER_227_0.S[5821]++;continue;}
__CLOVER_227_0.S[5822]++;String value = (String) entry.getValue();
__CLOVER_227_0.S[5823]++;addAttributeToToken(token, key, value);
}}
} finally { }}
/**
* 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)
{try { __CLOVER_227_0.M[1202]++;
__CLOVER_227_0.S[5824]++;token.addAttribute(name, convertEntitiesToPlain(attributeValue));
} finally { }}
/**
* 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
{try { __CLOVER_227_0.M[1203]++;
__CLOVER_227_0.S[5825]++;int cursorStart = _cursor;
__CLOVER_227_0.S[5826]++;int length = _templateData.length;
__CLOVER_227_0.S[5827]++;int startLine = _line;
__CLOVER_227_0.S[5828]++;Location startLocation = getCurrentLocation();
__CLOVER_227_0.S[5829]++;_cursor += CLOSE_TAG.length;
__CLOVER_227_0.S[5830]++;int tagStart = _cursor;
__CLOVER_227_0.S[5831]++;while (true){
{
__CLOVER_227_0.S[5832]++;if ((((_cursor >= length) && (++__CLOVER_227_0.CT[1018] != 0)) || (++__CLOVER_227_0.CF[1018] == 0))){
__CLOVER_227_0.S[5833]++;templateParseProblem(ParseMessages.incompleteCloseTag(startLine), startLocation, startLine, cursorStart);}
__CLOVER_227_0.S[5834]++;char ch = _templateData[_cursor];
__CLOVER_227_0.S[5835]++;if ((((ch == '>') && (++__CLOVER_227_0.CT[1019] != 0)) || (++__CLOVER_227_0.CF[1019] == 0))){
__CLOVER_227_0.S[5836]++;break;}
__CLOVER_227_0.S[5837]++;advance();
}}
__CLOVER_227_0.S[5838]++;String tagName = new String(_templateData, tagStart, _cursor - tagStart);
__CLOVER_227_0.S[5839]++;int stackPos = _stack.size() - 1;
__CLOVER_227_0.S[5840]++;Tag tag = null;
__CLOVER_227_0.S[5841]++;while ((((stackPos >= 0) && (++__CLOVER_227_0.CT[1020] != 0)) || (++__CLOVER_227_0.CF[1020] == 0))){
{
__CLOVER_227_0.S[5842]++;tag = (Tag) _stack.get(stackPos);
__CLOVER_227_0.S[5843]++;if ((((tag.match(tagName)) && (++__CLOVER_227_0.CT[1021] != 0)) || (++__CLOVER_227_0.CF[1021] == 0))){
__CLOVER_227_0.S[5844]++;break;}
__CLOVER_227_0.S[5845]++;if ((((tag._mustBalance) && (++__CLOVER_227_0.CT[1022] != 0)) || (++__CLOVER_227_0.CF[1022] == 0))){
__CLOVER_227_0.S[5846]++;templateParseProblem(ParseMessages
.improperlyNestedCloseTag(tagName, startLine, tag._tagName, tag._line), startLocation,
startLine, cursorStart);}
__CLOVER_227_0.S[5847]++;stackPos--;
}}
__CLOVER_227_0.S[5848]++;if ((((stackPos < 0) && (++__CLOVER_227_0.CT[1023] != 0)) || (++__CLOVER_227_0.CF[1023] == 0))){
__CLOVER_227_0.S[5849]++;templateParseProblem(ParseMessages.unmatchedCloseTag(tagName, startLine), startLocation, startLine,
cursorStart);}
// Special case for the content tag
__CLOVER_227_0.S[5850]++;if ((((tag._content) && (++__CLOVER_227_0.CT[1024] != 0)) || (++__CLOVER_227_0.CF[1024] == 0))){
{
__CLOVER_227_0.S[5851]++;addTextToken(cursorStart - 1);
// Advance the cursor right to the end.
__CLOVER_227_0.S[5852]++;_cursor = length;
__CLOVER_227_0.S[5853]++;_stack.clear();
__CLOVER_227_0.S[5854]++;return;
}}
// When a component closes, add a CLOSE tag.
__CLOVER_227_0.S[5855]++;if ((((tag._component) && (++__CLOVER_227_0.CT[1025] != 0)) || (++__CLOVER_227_0.CF[1025] == 0))){
{
__CLOVER_227_0.S[5856]++;addTextToken(cursorStart - 1);
__CLOVER_227_0.S[5857]++;_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.
__CLOVER_227_0.S[5858]++;if ((((_blockStart < 0 && !tag._removeTag && !_ignoring) && (++__CLOVER_227_0.CT[1026] != 0)) || (++__CLOVER_227_0.CF[1026] == 0))){
__CLOVER_227_0.S[5859]++;_blockStart = cursorStart;}
}}
// Remove all elements at stackPos or above.
__CLOVER_227_0.S[5860]++;for (int i = _stack.size() - 1; (((i >= stackPos) && (++__CLOVER_227_0.CT[1027] != 0)) || (++__CLOVER_227_0.CF[1027] == 0)); i--){
__CLOVER_227_0.S[5861]++;_stack.remove(i);}
// Advance cursor past '>'
__CLOVER_227_0.S[5862]++;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.
__CLOVER_227_0.S[5863]++;if ((((tag._removeTag) && (++__CLOVER_227_0.CT[1028] != 0)) || (++__CLOVER_227_0.CF[1028] == 0))){
__CLOVER_227_0.S[5864]++;advanceOverWhitespace();}
// If we were ignoring the body of the tag, then clear the ignoring
// flag, since we're out of the body.
__CLOVER_227_0.S[5865]++;if ((((tag._ignoringBody) && (++__CLOVER_227_0.CT[1029] != 0)) || (++__CLOVER_227_0.CF[1029] == 0))){
__CLOVER_227_0.S[5866]++;_ignoring = false;}
} finally { }}
/**
* Advances the cursor to the next character. If the end-of-line is reached, then increments the
* line counter.
*/
private void advance()
{try { __CLOVER_227_0.M[1204]++;
__CLOVER_227_0.S[5867]++;int length = _templateData.length;
__CLOVER_227_0.S[5868]++;if ((((_cursor >= length) && (++__CLOVER_227_0.CT[1030] != 0)) || (++__CLOVER_227_0.CF[1030] == 0))){
__CLOVER_227_0.S[5869]++;return;}
__CLOVER_227_0.S[5870]++;char ch = _templateData[_cursor];
__CLOVER_227_0.S[5871]++;_cursor++;
__CLOVER_227_0.S[5872]++;if ((((ch == '\n') && (++__CLOVER_227_0.CT[1031] != 0)) || (++__CLOVER_227_0.CF[1031] == 0))){
{
__CLOVER_227_0.S[5873]++;_line++;
__CLOVER_227_0.S[5874]++;_currentLocation = null;
__CLOVER_227_0.S[5875]++;return;
}}
// A \r, or a \r\n also counts as a new line.
__CLOVER_227_0.S[5876]++;if ((((ch == '\r') && (++__CLOVER_227_0.CT[1032] != 0)) || (++__CLOVER_227_0.CF[1032] == 0))){
{
__CLOVER_227_0.S[5877]++;_line++;
__CLOVER_227_0.S[5878]++;_currentLocation = null;
__CLOVER_227_0.S[5879]++;if ((((_cursor < length && _templateData[_cursor] == '\n') && (++__CLOVER_227_0.CT[1033] != 0)) || (++__CLOVER_227_0.CF[1033] == 0))){
__CLOVER_227_0.S[5880]++;_cursor++;}
__CLOVER_227_0.S[5881]++;return;
}}
// Not an end-of-line character.
} finally { }}
private void advanceOverWhitespace()
{try { __CLOVER_227_0.M[1205]++;
__CLOVER_227_0.S[5882]++;int length = _templateData.length;
__CLOVER_227_0.S[5883]++;while ((((_cursor < length) && (++__CLOVER_227_0.CT[1034] != 0)) || (++__CLOVER_227_0.CF[1034] == 0))){
{
__CLOVER_227_0.S[5884]++;char ch = _templateData[_cursor];
__CLOVER_227_0.S[5885]++;if ((((!Character.isWhitespace(ch)) && (++__CLOVER_227_0.CT[1035] != 0)) || (++__CLOVER_227_0.CF[1035] == 0))){
__CLOVER_227_0.S[5886]++;return;}
__CLOVER_227_0.S[5887]++;advance();
}}
} finally { }}
/**
* 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)
{try { __CLOVER_227_0.M[1206]++;
__CLOVER_227_0.S[5888]++;if ((((input == null || input.isEmpty()) && (++__CLOVER_227_0.CT[1036] != 0)) || (++__CLOVER_227_0.CF[1036] == 0))){
__CLOVER_227_0.S[5889]++;return null;}
__CLOVER_227_0.S[5890]++;Map result = null;
__CLOVER_227_0.S[5891]++;Iterator i = input.entrySet().iterator();
__CLOVER_227_0.S[5892]++;nextkey: while ((((i.hasNext()) && (++__CLOVER_227_0.CT[1037] != 0)) || (++__CLOVER_227_0.CF[1037] == 0))){
{
__CLOVER_227_0.S[5893]++;Map.Entry entry = (Map.Entry) i.next();
__CLOVER_227_0.S[5894]++;String key = (String) entry.getKey();
__CLOVER_227_0.S[5895]++;for (int j = 0; (((j < removeKeys.length) && (++__CLOVER_227_0.CT[1038] != 0)) || (++__CLOVER_227_0.CF[1038] == 0)); j++){
{
__CLOVER_227_0.S[5896]++;if ((((key.equalsIgnoreCase(removeKeys[j])) && (++__CLOVER_227_0.CT[1039] != 0)) || (++__CLOVER_227_0.CF[1039] == 0))){
__CLOVER_227_0.S[5897]++;continue nextkey;}
}}
__CLOVER_227_0.S[5898]++;if ((((result == null) && (++__CLOVER_227_0.CT[1040] != 0)) || (++__CLOVER_227_0.CF[1040] == 0))){
__CLOVER_227_0.S[5899]++;result = new HashMap(input.size());}
__CLOVER_227_0.S[5900]++;result.put(key, entry.getValue());
}}
__CLOVER_227_0.S[5901]++;return result;
} finally { }}
/**
* 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)
{try { __CLOVER_227_0.M[1207]++;
__CLOVER_227_0.S[5902]++;String result = (String) map.get(key);
__CLOVER_227_0.S[5903]++;if ((((result != null) && (++__CLOVER_227_0.CT[1041] != 0)) || (++__CLOVER_227_0.CF[1041] == 0))){
__CLOVER_227_0.S[5904]++;return result;}
__CLOVER_227_0.S[5905]++;Iterator i = map.entrySet().iterator();
__CLOVER_227_0.S[5906]++;while ((((i.hasNext()) && (++__CLOVER_227_0.CT[1042] != 0)) || (++__CLOVER_227_0.CF[1042] == 0))){
{
__CLOVER_227_0.S[5907]++;Map.Entry entry = (Map.Entry) i.next();
__CLOVER_227_0.S[5908]++;String entryKey = (String) entry.getKey();
__CLOVER_227_0.S[5909]++;if ((((entryKey.equalsIgnoreCase(key)) && (++__CLOVER_227_0.CT[1043] != 0)) || (++__CLOVER_227_0.CF[1043] == 0))){
__CLOVER_227_0.S[5910]++;return (String) entry.getValue();}
}}
__CLOVER_227_0.S[5911]++;return null;
} finally { }}
/**
* 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)
{try { __CLOVER_227_0.M[1208]++;
__CLOVER_227_0.S[5912]++;int inputLength = input.length();
__CLOVER_227_0.S[5913]++;StringBuffer buffer = new StringBuffer(inputLength);
__CLOVER_227_0.S[5914]++;int cursor = 0;
__CLOVER_227_0.S[5915]++;outer: while ((((cursor < inputLength) && (++__CLOVER_227_0.CT[1044] != 0)) || (++__CLOVER_227_0.CF[1044] == 0))){
{
__CLOVER_227_0.S[5916]++;for (int i = 0; (((i < CONVERSIONS.length) && (++__CLOVER_227_0.CT[1045] != 0)) || (++__CLOVER_227_0.CF[1045] == 0)); i += 2){
{
__CLOVER_227_0.S[5917]++;String entity = CONVERSIONS[i];
__CLOVER_227_0.S[5918]++;int entityLength = entity.length();
__CLOVER_227_0.S[5919]++;String value = CONVERSIONS[i + 1];
__CLOVER_227_0.S[5920]++;if ((((cursor + entityLength > inputLength) && (++__CLOVER_227_0.CT[1046] != 0)) || (++__CLOVER_227_0.CF[1046] == 0))){
__CLOVER_227_0.S[5921]++;continue;}
__CLOVER_227_0.S[5922]++;if ((((input.substring(cursor, cursor + entityLength).equals(entity)) && (++__CLOVER_227_0.CT[1047] != 0)) || (++__CLOVER_227_0.CF[1047] == 0))){
{
__CLOVER_227_0.S[5923]++;buffer.append(value);
__CLOVER_227_0.S[5924]++;cursor += entityLength;
__CLOVER_227_0.S[5925]++;continue outer;
}}
}}
__CLOVER_227_0.S[5926]++;buffer.append(input.charAt(cursor));
__CLOVER_227_0.S[5927]++;cursor++;
}}
__CLOVER_227_0.S[5928]++;return buffer.toString().trim();
} finally { }}
/**
* 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)
{try { __CLOVER_227_0.M[1209]++;
__CLOVER_227_0.S[5929]++;String value = findValueCaselessly(key, map);
__CLOVER_227_0.S[5930]++;if ((((value == null) && (++__CLOVER_227_0.CT[1048] != 0)) || (++__CLOVER_227_0.CF[1048] == 0))){
__CLOVER_227_0.S[5931]++;return false;}
__CLOVER_227_0.S[5932]++;return value.equalsIgnoreCase("true");
} finally { }}
/**
* 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()
{try { __CLOVER_227_0.M[1210]++;
__CLOVER_227_0.S[5933]++;if ((((_currentLocation == null) && (++__CLOVER_227_0.CT[1049] != 0)) || (++__CLOVER_227_0.CF[1049] == 0))){
__CLOVER_227_0.S[5934]++;_currentLocation = new LocationImpl(_resourceLocation, _line);}
__CLOVER_227_0.S[5935]++;return _currentLocation;
} finally { }}
public void setFactory(TemplateTokenFactory factory)
{try { __CLOVER_227_0.M[1211]++;
__CLOVER_227_0.S[5936]++;_factory = factory;
} finally { }}
}