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();
  }
  /**
   * 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
  }