docs: public static void sanitize(
        @Nullable String html, final Policy policy,
        HtmlStreamEventProcessor preprocessor) {
      String htmlContent = html != null ? html : "";
  
      HtmlStreamEventReceiver receiver = initializePolicy(policy, preprocessor);
  
      receiver.openDocument();
  
      HtmlLexer lexer = new HtmlLexer(htmlContent);
      // Use a linked list so that policies can use Iterator.remove() in an O(1)
      // way.
      LinkedList<String> attrs = Lists.newLinkedList();
      while (lexer.hasNext()) {
        HtmlToken token = lexer.next();
        switch (token.type) {
          case TEXT:
            receiver.text(
                Encoding.decodeHtml(htmlContent.substring(token.start, token.end)));
            break;
          case UNESCAPED:
            receiver.text(Encoding.stripBannedCodeunits(
                htmlContent.substring(token.start, token.end)));
            break;
          case TAGBEGIN:
            if (htmlContent.charAt(token.start + 1) == '/') {  // A close tag.
              receiver.closeTag(HtmlLexer.canonicalName(
                  htmlContent.substring(token.start + 2, token.end)));
              while (lexer.hasNext()
                     && lexer.next().type != HtmlTokenType.TAGEND) {
                // skip tokens until we see a ">"
              }
            } else {
              attrs.clear();
  
              boolean attrsReadyForName = true;
              tagBody:
              while (lexer.hasNext()) {
                HtmlToken tagBodyToken = lexer.next();
                switch (tagBodyToken.type) {
                  case ATTRNAME:
                    if (!attrsReadyForName) {
                      // Last attribute added was valueless.
                      attrs.add(attrs.getLast());
                    } else {
                      attrsReadyForName = false;
                    }
                    attrs.add(HtmlLexer.canonicalName(
                        htmlContent.substring(tagBodyToken.start, tagBodyToken.end)));
                    break;
                  case ATTRVALUE:
                    attrs.add(Encoding.decodeHtml(stripQuotes(
                        htmlContent.substring(tagBodyToken.start, tagBodyToken.end))));
                    attrsReadyForName = true;
                    break;
                  case TAGEND:
                    break tagBody;
                  default:
                    // Just drop anything not recognized
                }
              }
              if (!attrsReadyForName) {
                attrs.add(attrs.getLast());
              }
              receiver.openTag(
                  HtmlLexer.canonicalName(
                      htmlContent.substring(token.start + 1, token.end)),
                  attrs);
            }
            break;
          default:
            // Ignore comments, XML prologues, processing instructions, and other
            // stuff that shouldn't show up in the output.
            break;
        }
      }
  
      receiver.closeDocument();
    }