function makeHtmlSanitizer(tagPolicy) {
    var stack;
    var ignoring;
    var emit = function (text, out) {
      if (!ignoring) { out.push(text); }
    };
    return makeSaxParser({
      startDoc: function(_) {
        stack = [];
        ignoring = false;
      },
      startTag: function(tagName, attribs, out) {
        if (ignoring) { return; }
        if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; }
        var eflags = html4.ELEMENTS[tagName];
        if (eflags & html4.eflags.FOLDABLE) {
          return;
        }
        attribs = tagPolicy(tagName, attribs);
        if (!attribs) {
          ignoring = !(eflags & html4.eflags.EMPTY);
          return;
        }
        // TODO(mikesamuel): relying on tagPolicy not to insert unsafe
        // attribute names.
        if (!(eflags & html4.eflags.EMPTY)) {
          stack.push(tagName);
        }

        out.push('<', tagName);
        for (var i = 0, n = attribs.length; i < n; i += 2) {
          var attribName = attribs[i],
              value = attribs[i + 1];
          if (value !== null && value !== void 0) {
            out.push(' ', attribName, '="', escapeAttrib(value), '"');
          }
        }
        out.push('>');
      },
      endTag: function(tagName, out) {
        if (ignoring) {
          ignoring = false;
          return;
        }
        if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; }
        var eflags = html4.ELEMENTS[tagName];
        if (!(eflags & (html4.eflags.EMPTY | html4.eflags.FOLDABLE))) {
          var index;
          if (eflags & html4.eflags.OPTIONAL_ENDTAG) {
            for (index = stack.length; --index >= 0;) {
              var stackEl = stack[index];
              if (stackEl === tagName) { break; }
              if (!(html4.ELEMENTS[stackEl] &
                    html4.eflags.OPTIONAL_ENDTAG)) {
                // Don't pop non optional end tags looking for a match.
                return;
              }
            }
          } else {
            for (index = stack.length; --index >= 0;) {
              if (stack[index] === tagName) { break; }
            }
          }
          if (index < 0) { return; }  // Not opened.
          for (var i = stack.length; --i > index;) {
            var stackEl = stack[i];
            if (!(html4.ELEMENTS[stackEl] &
                  html4.eflags.OPTIONAL_ENDTAG)) {
              out.push('<\/', stackEl, '>');
            }
          }
          stack.length = index;
          out.push('<\/', tagName, '>');
        }
      },
      pcdata: emit,
      rcdata: emit,
      cdata: emit,
      endDoc: function(out) {
        for (; stack.length; stack.length--) {
          out.push('<\/', stack[stack.length - 1], '>');
        }
      }
    });
  }
Returns a function that strips unsafe tags and attributes from html.
@param {function(string, Array.<string>): ?Array.<string>} tagPolicy
    A function that takes (tagName, attribs[]), where tagName is a key in
    html4.ELEMENTS and attribs is an array of alternating attribute names
    and values.  It should return a sanitized attribute array, or null to
    delete the tag.  It's okay for tagPolicy to modify the attribs array,
    but the same array is reused, so it should not be held between calls.
@return {function(string, Array)} A function that sanitizes a string of
    HTML and appends result strings to the second argument, an array.