private void addTextToken(int end) { // No active block to add to. if (_blockStart < 0) return; if (_blockStart <= end) { TemplateToken token = _factory.createTextToken(_templateData, _blockStart, end, _templateLocation); _tokens.add(token); } _blockStart = -1; }
private void addOpenToken(String tagName, String jwcId, String type, ILocation 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(JWCID_ATTRIBUTE_NAME)) continue; String value = (String) entry.getValue(); addAttributeToToken(token, key, value); } }
private void processComponentStart( String tagName, String jwcId, boolean emptyTag, int startLine, int cursorStart, ILocation startLocation) throws TemplateParseException { if (jwcId.equalsIgnoreCase(CONTENT_ID)) { processContentTag(tagName, startLine, cursorStart, emptyTag); return; } boolean isRemoveId = jwcId.equalsIgnoreCase(REMOVE_ID); if (_ignoring && !isRemoveId) templateParseProblem( Tapestry.format( "TemplateParser.component-may-not-be-ignored", tagName, Integer.toString(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). if (jwcId == null) jwcId = _idAllocator.allocateId("$" + simpleType); 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( Tapestry.format( "TemplateParser.component-id-invalid", tagName, Integer.toString(startLine), jwcId), startLocation, startLine, cursorStart); if (!_delegate.getKnownComponent(jwcId)) templateParseProblem( Tapestry.format( "TemplateParser.unknown-component-id", tagName, Integer.toString(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( Tapestry.format("TemplateParser.nested-ignore", tagName, Integer.toString(startLine)), new Location(_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 startTag() throws TemplateParseException { int cursorStart = _cursor; int length = _templateData.length; String tagName = null; boolean endOfTag = false; boolean emptyTag = false; int startLine = _line; ILocation startLocation = new Location(_resourceLocation, startLine); tagBeginEvent(startLine, _cursor); advance(); // Collect the element type while (_cursor < length) { char ch = _templateData[_cursor]; if (ch == '/' || ch == '>' || Character.isWhitespace(ch)) { tagName = new String(_templateData, cursorStart + 1, _cursor - cursorStart - 1); break; } advance(); } String attributeName = null; int attributeNameStart = -1; int attributeValueStart = -1; int state = WAIT_FOR_ATTRIBUTE_NAME; char quoteChar = 0; _attributes.clear(); // Collect each attribute while (!endOfTag) { if (_cursor >= length) { String key = (tagName == null) ? "TemplateParser.unclosed-unknown-tag" : "TemplateParser.unclosed-tag"; templateParseProblem( Tapestry.format(key, tagName, Integer.toString(startLine)), startLocation, startLine, cursorStart); } char ch = _templateData[_cursor]; switch (state) { case WAIT_FOR_ATTRIBUTE_NAME: // Ignore whitespace before the next attribute name, while // looking for the end of the current tag. if (ch == '/') { emptyTag = true; advance(); break; } if (ch == '>') { endOfTag = true; break; } if (Character.isWhitespace(ch)) { advance(); break; } // Found non-whitespace, assume its the attribute name. // Note: could use a check here for non-alpha. attributeNameStart = _cursor; state = COLLECT_ATTRIBUTE_NAME; advance(); break; case COLLECT_ATTRIBUTE_NAME: // Looking for end of attribute name. if (ch == '=' || ch == '/' || ch == '>' || Character.isWhitespace(ch)) { attributeName = new String(_templateData, attributeNameStart, _cursor - attributeNameStart); state = ADVANCE_PAST_EQUALS; break; } // Part of the attribute name advance(); break; 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. 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( Tapestry.format( "TemplateParser.missing-attribute-value", tagName, Integer.toString(_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; } // 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); if (_attributes.containsKey(attributeName)) templateParseProblem( Tapestry.format( "TemplateParser.duplicate-tag-attribute", tagName, Integer.toString(_line), attributeName), getCurrentLocation(), _line, _cursor); _attributes.put(attributeName, attributeValue); // 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. if (ch == '/' || ch == '>' || Character.isWhitespace(ch)) { String attributeValue = new String(_templateData, attributeValueStart, _cursor - attributeValueStart); attributeEndEvent(_cursor); if (_attributes.containsKey(attributeName)) templateParseProblem( Tapestry.format( "TemplateParser.duplicate-tag-attribute", tagName, Integer.toString(_line), attributeName), getCurrentLocation(), _line, _cursor); _attributes.put(attributeName, attributeValue); state = WAIT_FOR_ATTRIBUTE_NAME; break; } advance(); break; } } tagEndEvent(_cursor); // Check for invisible localizations String localizationKey = findValueCaselessly(LOCALIZATION_KEY_ATTRIBUTE_NAME, _attributes); String jwcId = findValueCaselessly(JWCID_ATTRIBUTE_NAME, _attributes); if (localizationKey != null && tagName.equalsIgnoreCase("span") && jwcId == null) { if (_ignoring) templateParseProblem( Tapestry.format( "TemplateParser.component-may-not-be-ignored", tagName, Integer.toString(startLine)), startLocation, startLine, cursorStart); // If the tag isn't empty, then create a Tag instance to ignore the // body of the tag. if (!emptyTag) { Tag tag = new Tag(tagName, startLine); tag._component = false; tag._removeTag = true; tag._ignoringBody = true; tag._mustBalance = true; _stack.add(tag); // Start ignoring content until the close tag. _ignoring = true; } else { // Cursor is at the closing carat, advance over it and any whitespace. advance(); advanceOverWhitespace(); } // End any open block. addTextToken(cursorStart - 1); boolean raw = checkBoolean(RAW_ATTRIBUTE_NAME, _attributes); Map attributes = filter(_attributes, new String[] {LOCALIZATION_KEY_ATTRIBUTE_NAME, RAW_ATTRIBUTE_NAME}); TemplateToken token = _factory.createLocalizationToken( tagName, localizationKey, raw, attributes, startLocation); _tokens.add(token); return; } if (jwcId != null) { processComponentStart(tagName, jwcId, emptyTag, startLine, cursorStart, startLocation); return; } // A static tag (not a tag without a jwcid attribute). // We need to record this so that we can match close tags later. if (!emptyTag) { Tag tag = new Tag(tagName, startLine); _stack.add(tag); } // If there wasn't an active block, then start one. if (_blockStart < 0 && !_ignoring) _blockStart = cursorStart; advance(); }
/** * 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; ILocation startLocation = getCurrentLocation(); _cursor += CLOSE_TAG.length; int tagStart = _cursor; while (true) { if (_cursor >= length) templateParseProblem( Tapestry.format("TemplateParser.incomplete-close-tag", Integer.toString(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( Tapestry.format( "TemplateParser.improperly-nested-close-tag", new Object[] { tagName, Integer.toString(startLine), tag._tagName, Integer.toString(tag._line) }), startLocation, startLine, cursorStart); stackPos--; } // test begin add is page condiction if (stackPos < 0) { if (_delegate.getComponent().getSpecification().isPageSpecification()) { templateParseProblem( Tapestry.format( "TemplateParser.unmatched-close-tag", tagName, Integer.toString(startLine)), startLocation, startLine, cursorStart); } } // test end // Special case for the content tag // test begin if (tag != null) { 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. // test begin add && i >= 0 for (int i = _stack.size() - 1; i >= stackPos && i >= 0; i--) _stack.remove(i); // test end // 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; } // test end }