getOuterHTML = function(node, parentNode, callback) {
      switch (node.nodeType) {
        case Node.ELEMENT_NODE: {
          var tagName = node.localName;
          var s = '<' + tagName;
          var attrs = node.attributes;
          for (var i = 0, attr; (attr = attrs[i]); i++) {
            s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
          }
          s += '>';
          if (voidElements[tagName]) {
            return s;
          }
          return s + getInnerHTML(node, callback) + '</' + tagName + '>';
        }
        case Node.TEXT_NODE: {
          var data = /** @type {Text} */ (node).data;
          if (parentNode && plaintextParents[parentNode.localName]) {
            return data;
          }
          return escapeData(data);
        }
        case Node.COMMENT_NODE: {
          return '<!--' + /** @type {Comment} */ (node).data + '-->';
        }
        default: {
          window.console.error(node);
          throw new Error('not implemented');
        }
      }
    }
@param {Node} node
@param {Node} parentNode
@param {Function=} callback