/** * 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 }