private void visit(
      HtmlMatcherTagNode tagNode,
      Map<Equivalence.Wrapper<ExprNode>, Boolean> exprValueMap,
      HtmlStack stack) {
    HtmlTagNode tag = (HtmlTagNode) tagNode.getSoyNode().get();
    TagName openTagName = tag.getTagName();
    HtmlStack prev = stack;
    switch (tagNode.getTagKind()) {
      case VOID_TAG:
        HtmlOpenTagNode voidTag = (HtmlOpenTagNode) tag;
        // Report errors for non-void tags that are self-closing.
        // For void tags, we don't care if they are self-closing or not. But when we visit
        // a HtmlCloseTagNode we will throw an error if it is a void tag.
        // Ignore this check if we are currently in a foreign content (svg).
        if (!stack.inForeignContent
            && !openTagName.isDefinitelyVoid()
            && voidTag.isSelfClosing()
            && openTagName.isStatic()) {
          errorReporter.report(
              voidTag.getSourceLocation(),
              INVALID_SELF_CLOSING_TAG,
              openTagName.getStaticTagName());
        }
        break;
      case OPEN_TAG:
        HtmlOpenTagNode openTag = (HtmlOpenTagNode) tag;
        if (openTagName.isForeignContent() && stack.inForeignContent) {
          errorReporter.report(openTag.getSourceLocation(), makeSoyErrorKind(NESTED_SVG));
        }
        // In a case where an open tag can close another open tag (ie <p><p> or <li><li>),
        // check if this is possible by peeking the stack and inject a tag before the open tag.
        if (!prev.isEmpty()) {
          HtmlOpenTagNode optionalTag = stack.tagNode;
          if (optionalTag.getTagName().isDefinitelyOptional()) {
            if (TagName.checkOpenTagClosesOptional(
                openTag.getTagName(), optionalTag.getTagName())) {
              injectCloseTag(optionalTag, openTag, idGenerator);
              prev = prev.pop();
            }
          }
        }
        prev =
            prev.push(openTag, stack.inForeignContent || openTag.getTagName().isForeignContent());
        break;
      case CLOSE_TAG:
        HtmlCloseTagNode closeTag = (HtmlCloseTagNode) tag;
        // Report an error if this node is a void tag. Void tag should never be closed.
        if (closeTag.getTagName().isDefinitelyVoid()) {
          errorReporter.report(
              closeTag.getTagName().getTagLocation(),
              INVALID_CLOSE_TAG,
              closeTag.getTagName().getStaticTagName());
          break;
        }
        // This is for cases similar to {block}</p>{/block}
        if (stack.isEmpty()) {
          errorReporter.report(
              closeTag.getSourceLocation(), makeSoyErrorKind(UNEXPECTED_CLOSE_TAG));
          break;
        }
        prev = stack;
        while (!prev.isEmpty()) {
          HtmlOpenTagNode nextOpenTag = prev.tagNode;
          if (nextOpenTag.getTagName().equals(closeTag.getTagName())) {
            annotationMap.put(nextOpenTag, Optional.of(closeTag));
            annotationMap.put(closeTag, Optional.of(nextOpenTag));
            prev = prev.pop();
            break;
          } else if (nextOpenTag.getTagName().isDefinitelyOptional()
              && TagName.checkCloseTagClosesOptional(
                  closeTag.getTagName(), nextOpenTag.getTagName())) {
            // Close tag closes an optional open tag (e.g. <li> ... </ul>). Inject a synthetic
            // close tag that matches `openTag`.
            injectCloseTag(nextOpenTag, closeTag, idGenerator);
            prev = prev.pop();
          } else {
            annotationMap.put(nextOpenTag, INVALID_NODE);
            errorReporter.report(
                closeTag.getSourceLocation(),
                makeSoyErrorKind(UNEXPECTED_CLOSE_TAG_KNOWN),
                nextOpenTag.getTagName(),
                nextOpenTag.getSourceLocation());
            prev = prev.pop();
          }
        }
        break;
    }
    Optional<HtmlMatcherGraphNode> nextNode = tagNode.getNodeForEdgeKind(EdgeKind.TRUE_EDGE);
    if (nextNode.isPresent()) {
      visit(nextNode.get(), exprValueMap, prev);
    } else {
      checkUnusedTags(prev);
    }
  }
private void visit ( HtmlMatcherTagNode tagNode , Map < Equivalence . Wrapper < ExprNode > , Boolean > exprValueMap , HtmlStack stack ) { HtmlTagNode tag = ( HtmlTagNode ) tagNode . getSoyNode ( ) . get ( ) ; TagName openTagName = tag . getTagName ( ) ; HtmlStack prev = stack ; switch ( tagNode . getTagKind ( ) ) { case VOID_TAG : HtmlOpenTagNode voidTag = ( HtmlOpenTagNode ) tag ; if ( ! stack . inForeignContent && ! openTagName . isDefinitelyVoid ( ) && voidTag . isSelfClosing ( ) && openTagName . isStatic ( ) ) { errorReporter . report ( voidTag . getSourceLocation ( ) , INVALID_SELF_CLOSING_TAG , openTagName . getStaticTagName ( ) ) ; } break ; case OPEN_TAG : HtmlOpenTagNode openTag = ( HtmlOpenTagNode ) tag ; if ( openTagName . isForeignContent ( ) && stack . inForeignContent ) { errorReporter . report ( openTag . getSourceLocation ( ) , makeSoyErrorKind ( NESTED_SVG ) ) ; } if ( ! prev . isEmpty ( ) ) { HtmlOpenTagNode optionalTag = stack . tagNode ; if ( optionalTag . getTagName ( ) . isDefinitelyOptional ( ) ) { if ( TagName . checkOpenTagClosesOptional ( openTag . getTagName ( ) , optionalTag . getTagName ( ) ) ) { injectCloseTag ( optionalTag , openTag , idGenerator ) ; prev = prev . pop ( ) ; } } } prev = prev . push ( openTag , stack . inForeignContent || openTag . getTagName ( ) . isForeignContent ( ) ) ; break ; case CLOSE_TAG : HtmlCloseTagNode closeTag = ( HtmlCloseTagNode ) tag ; if ( closeTag . getTagName ( ) . isDefinitelyVoid ( ) ) { errorReporter . report ( closeTag . getTagName ( ) . getTagLocation ( ) , INVALID_CLOSE_TAG , closeTag . getTagName ( ) . getStaticTagName ( ) ) ; break ; } if ( stack . isEmpty ( ) ) { errorReporter . report ( closeTag . getSourceLocation ( ) , makeSoyErrorKind ( UNEXPECTED_CLOSE_TAG ) ) ; break ; } prev = stack ; while ( ! prev . isEmpty ( ) ) { HtmlOpenTagNode nextOpenTag = prev . tagNode ; if ( nextOpenTag . getTagName ( ) . equals ( closeTag . getTagName ( ) ) ) { annotationMap . put ( nextOpenTag , Optional . of ( closeTag ) ) ; annotationMap . put ( closeTag , Optional . of ( nextOpenTag ) ) ; prev = prev . pop ( ) ; break ; } else if ( nextOpenTag . getTagName ( ) . isDefinitelyOptional ( ) && TagName . checkCloseTagClosesOptional ( closeTag . getTagName ( ) , nextOpenTag . getTagName ( ) ) ) { injectCloseTag ( nextOpenTag , closeTag , idGenerator ) ; prev = prev . pop ( ) ; } else { annotationMap . put ( nextOpenTag , INVALID_NODE ) ; errorReporter . report ( closeTag . getSourceLocation ( ) , makeSoyErrorKind ( UNEXPECTED_CLOSE_TAG_KNOWN ) , nextOpenTag . getTagName ( ) , nextOpenTag . getSourceLocation ( ) ) ; prev = prev . pop ( ) ; } } break ; } Optional < HtmlMatcherGraphNode > nextNode = tagNode . getNodeForEdgeKind ( EdgeKind . TRUE_EDGE ) ; if ( nextNode . isPresent ( ) ) { visit ( nextNode . get ( ) , exprValueMap , prev ) ; } else { checkUnusedTags ( prev ) ; } }
Perform tag matching/error reporting for invalid HTML.