class Sanitize

def self.document(html, config = {})

{#fragment} instead.
error will be raised. If this is undesirable, you should probably use
When sanitizing a document, the `` element must be whitelisted or an

settings in _config_ if specified.
Returns a sanitized copy of the given full _html_ document, using the
def self.document(html, config = {})
  Sanitize.new(config).document(html)
end

def self.fragment(html, config = {})

_config_ if specified.
Returns a sanitized copy of the given _html_ fragment, using the settings in
def self.fragment(html, config = {})
  Sanitize.new(config).fragment(html)
end

def self.node!(node, config = {})

Sanitizes the given `Nokogiri::XML::Node` instance and all its children.
def self.node!(node, config = {})
  Sanitize.new(config).node!(node)
end

def document(html)

{#fragment} instead.
error will be raised. If this is undesirable, you should probably use
When sanitizing a document, the `` element must be whitelisted or an

Returns a sanitized copy of the given _html_ document.
def document(html)
  return '' unless html
  doc = Nokogiri::HTML5.parse(preprocess(html))
  node!(doc)
  to_html(doc)
end

def fragment(html)

Returns a sanitized copy of the given _html_ fragment.
def fragment(html)
  return '' unless html
  html = preprocess(html)
  doc  = Nokogiri::HTML5.parse("<html><body>#{html}")
  # Hack to allow fragments containing <body>. Borrowed from
  # Nokogiri::HTML::DocumentFragment.
  if html =~ /\A<body(?:\s|>)/i
    path = '/html/body'
  else
    path = '/html/body/node()'
  end
  frag = doc.fragment
  frag << doc.xpath(path)
  node!(frag)
  to_html(frag)
end

def initialize(config = {})

Returns a new Sanitize object initialized with the settings in _config_.
def initialize(config = {})
  @config = Config.merge(Config::DEFAULT, config)
  @transformers = Array(@config[:transformers]).dup
  # Default transformers always run at the end of the chain, after any custom
  # transformers.
  @transformers << Transformers::CleanComment unless @config[:allow_comments]
  if @config[:elements].include?('style')
    scss = Sanitize::CSS.new(config)
    @transformers << Transformers::CSS::CleanElement.new(scss)
  end
  if @config[:attributes].values.any? {|attr| attr.include?('style') }
    scss ||= Sanitize::CSS.new(config)
    @transformers << Transformers::CSS::CleanAttribute.new(scss)
  end
  @transformers <<
      Transformers::CleanDoctype <<
      Transformers::CleanCDATA <<
      Transformers::CleanElement.new(@config)
end

def node!(node)

whitelisted or an error will be raised.
If _node_ is a `Nokogiri::XML::Document`, the `` element must be

in place.
Sanitizes the given `Nokogiri::XML::Node` and all its children, modifying it
def node!(node)
  raise ArgumentError unless node.is_a?(Nokogiri::XML::Node)
  if node.is_a?(Nokogiri::XML::Document)
    unless @config[:elements].include?('html')
      raise Error, 'When sanitizing a document, "<html>" must be whitelisted.'
    end
  end
  node_whitelist = Set.new
  traverse(node) do |n|
    transform_node!(n, node_whitelist)
  end
  node
end

def preprocess(html)

Preprocesses HTML before parsing to remove undesirable Unicode chars.
def preprocess(html)
  html = html.to_s.dup
  unless html.encoding.name == 'UTF-8'
    html.encode!('UTF-8',
      :invalid => :replace,
      :undef   => :replace)
  end
  html.gsub!(REGEX_UNSUITABLE_CHARS, '')
  html
end

def to_html(node)

def to_html(node)
  replace_meta = false
  # Hacky workaround for a libxml2 bug that adds an undesired Content-Type
  # meta tag to all serialized HTML documents.
  #
  # https://github.com/sparklemotion/nokogiri/issues/1008
  if node.type == Nokogiri::XML::Node::DOCUMENT_NODE ||
      node.type == Nokogiri::XML::Node::HTML_DOCUMENT_NODE
    regex_meta   = %r|(<html[^>]*>\s*<head[^>]*>\s*)<meta http-equiv="Content-Type" content="text/html; charset=utf-8">|i
    # Only replace the content-type meta tag if <meta> isn't whitelisted or
    # the original document didn't actually include a content-type meta tag.
    replace_meta = !@config[:elements].include?('meta') ||
      node.xpath('/html/head/meta[@http-equiv]').none? do |meta|
        meta['http-equiv'].casecmp('content-type').zero?
      end
  end
  so = Nokogiri::XML::Node::SaveOptions
  # Serialize to HTML without any formatting to prevent Nokogiri from adding
  # newlines after certain tags.
  html = node.to_html(
    :encoding  => 'utf-8',
    :indent    => 0,
    :save_with => so::NO_DECLARATION | so::NO_EMPTY_TAGS | so::AS_HTML
  )
  html.gsub!(regex_meta, '\1') if replace_meta
  html
end

def transform_node!(node, node_whitelist)

def transform_node!(node, node_whitelist)
  node_name = node.name.downcase
  @transformers.each do |transformer|
    result = transformer.call(
      :config         => @config,
      :is_whitelisted => node_whitelist.include?(node),
      :node           => node,
      :node_name      => node_name,
      :node_whitelist => node_whitelist
    )
    if result.is_a?(Hash) && result[:node_whitelist].respond_to?(:each)
      node_whitelist.merge(result[:node_whitelist])
    end
  end
  node
end

def traverse(node, &block)

itself, then traversing each child (if any) in order.
Performs top-down traversal of the given node, operating first on the node
def traverse(node, &block)
  yield node
  child = node.child
  while child do
    prev = child.previous_sibling
    traverse(child, &block)
    if child.parent == node
      child = child.next_sibling
    else
      # The child was unlinked or reparented, so traverse the previous node's
      # next sibling, or the parent's first child if there is no previous
      # node.
      child = prev ? prev.next_sibling : node.child
    end
  end
end