/**
   * 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 '&lt;' 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 '&gt;'.
   */
  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
  }