<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -2,7 +2,9 @@ Sanitize History
 ================================================================================
 
 Version 1.2.0.dev (git)
-  * ?
+  * Added support for transformers, which allow you to filter and alter nodes
+    using your own custom logic, on top of (or instead of) Sanitize's core
+    filter. See the README for details.
 
 Version 1.1.1.dev (git)
   * Requires Nokogiri &gt;= 1.4.0.</diff>
      <filename>HISTORY</filename>
    </modified>
    <modified>
      <diff>@@ -38,7 +38,8 @@ Latest development version:
 == Usage
 
 If you don't specify any configuration options, Sanitize will use its strictest
-settings by default, which means it will strip all HTML.
+settings by default, which means it will strip all HTML and leave only text
+behind.
 
   require 'rubygems'
   require 'sanitize'
@@ -145,6 +146,72 @@ include the symbol &lt;code&gt;:relative&lt;/code&gt; in the protocol array:
     'a' =&gt; {'href' =&gt; ['http', 'https', :relative]}
   }
 
+=== Transformers
+
+Transformers allow you to filter and alter nodes using your own custom logic, on
+top of (or instead of) Sanitize's core filter. A transformer is any object that
+responds to &lt;code&gt;call()&lt;/code&gt; (such as a lambda or proc) and returns either
+&lt;code&gt;nil&lt;/code&gt; or a Hash containing certain optional response values.
+
+To use one or more transformers, pass them to the &lt;code&gt;:transformers&lt;/code&gt;
+config setting:
+
+  Sanitize.clean(html, :transformers =&gt; [transformer_one, transformer_two])
+
+==== Input
+
+Each registered transformer's &lt;code&gt;call()&lt;/code&gt; method will be called once for
+each element node in the HTML, and will receive as an argument an environment
+Hash that contains the following items:
+
+[&lt;code&gt;:config&lt;/code&gt;]
+  The current Sanitize configuration Hash.
+
+[&lt;code&gt;:node&lt;/code&gt;]
+  A Nokogiri::XML::Node object representing an HTML element.
+
+==== Processing
+
+Each transformer has full access to the Nokogiri::XML::Node that's passed into
+it and to the rest of the document via the node's &lt;code&gt;document()&lt;/code&gt;
+method. Any changes will be reflected instantly in the document and passed on to
+subsequently-called transformers and to Sanitize itself. A transformer may even
+call Sanitize internally to perform custom sanitization if needed.
+
+Nodes are passed into transformers in the order in which they're traversed. It's
+important to note that Nokogiri traverses markup from the deepest node upward,
+not from the first node to the last node:
+
+  html        = '&lt;div&gt;&lt;span&gt;foo&lt;/span&gt;&lt;/div&gt;'
+  transformer = lambda{|env| puts env[:node].name }
+
+  # Prints &quot;span&quot;, then &quot;div&quot;.
+  Sanitize.clean(html, :transformers =&gt; transformer)
+
+Transformers have a tremendous amount of power, including the power to
+completely bypass Sanitize's built-in filtering. Be careful!
+
+==== Output
+
+A transformer may return either +nil+ or a Hash. A return value of +nil+
+indicates that the transformer does not wish to act on the current node in any
+way. A returned Hash may contain the following items, all of which are optional:
+
+[&lt;code&gt;:attr_whitelist&lt;/code&gt;]
+  Array of attribute names to add to the whitelist for the current node, in
+  addition to any whitelisted attributes already defined in the current config.
+
+[&lt;code&gt;:node&lt;/code&gt;]
+  A Nokogiri::XML::Node object that should replace the current node. All
+  subsequent transformers and Sanitize itself will receive this new node.
+
+[&lt;code&gt;:whitelist&lt;/code&gt;]
+  If _true_, the current node (and only the current node) will be whitelisted,
+  regardless of the current Sanitize config.
+
+[&lt;code&gt;:whitelist_nodes&lt;/code&gt;]
+  Array of specific Nokogiri::XML::Node objects to whitelist, anywhere in the
+  document, regardless of the current Sanitize config.
 
 == Contributors
 </diff>
      <filename>README.rdoc</filename>
    </modified>
    <modified>
      <diff>@@ -56,7 +56,7 @@ class Sanitize
     sanitize.clean!(html)
   end
 
-  # Sanitizes the specified Nokogiri::XML::Node.
+  # Sanitizes the specified Nokogiri::XML::Node and all its children.
   def self.clean_node!(node, config = {})
     sanitize = Sanitize.new(config)
     sanitize.clean_node!(node)
@@ -70,6 +70,7 @@ class Sanitize
   def initialize(config = {})
     # Sanitize configuration.
     @config = Config::DEFAULT.merge(config)
+    @config[:transformers] = Array(@config[:transformers])
 
     # Specific nodes to whitelist (along with all their attributes). This array
     # is generated at runtime by transformers, and is cleared before and after
@@ -88,7 +89,7 @@ class Sanitize
   def clean!(html)
     @whitelist_nodes = []
     fragment = Nokogiri::HTML::DocumentFragment.parse(html)
-    fragment.traverse {|node| clean_node!(node) }
+    clean_node!(fragment)
     @whitelist_nodes = []
 
     output_method_params = {:encoding =&gt; 'utf-8', :indent =&gt; 0}
@@ -112,16 +113,19 @@ class Sanitize
     return result == html ? nil : html[0, html.length] = result
   end
 
-  # Sanitizes the specified Nokogiri::XML::Node.
+  # Sanitizes the specified Nokogiri::XML::Node and all its children.
   def clean_node!(node)
     raise ArgumentError unless node.is_a?(Nokogiri::XML::Node)
 
-    if node.element?
-      clean_element!(node)
-    elsif node.comment?
-      node.unlink unless @config[:allow_comments]
-    elsif node.cdata?
-      node.replace(Nokogiri::XML::Text.new(node.text, node.document))
+    node.traverse do |traversed_node|
+      if traversed_node.element?
+        clean_element!(traversed_node)
+      elsif traversed_node.comment?
+        traversed_node.unlink unless @config[:allow_comments]
+      elsif traversed_node.cdata?
+        traversed_node.replace(Nokogiri::XML::Text.new(traversed_node.text,
+            traversed_node.document))
+      end
     end
 
     node</diff>
      <filename>lib/sanitize.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
 class Sanitize
-  VERSION = '1.2.0.dev.20091102'
+  VERSION = '1.2.0.dev.20091104'
 end</diff>
      <filename>lib/sanitize/version.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,11 +2,11 @@
 
 Gem::Specification.new do |s|
   s.name = %q{sanitize}
-  s.version = &quot;1.1.1.dev.20091102&quot;
+  s.version = &quot;1.2.0.dev.20091104&quot;
 
   s.required_rubygems_version = Gem::Requirement.new(&quot;&gt; 1.3.1&quot;) if s.respond_to? :required_rubygems_version=
   s.authors = [&quot;Ryan Grove&quot;]
-  s.date = %q{2009-11-02}
+  s.date = %q{2009-11-04}
   s.email = %q{ryan@wonko.com}
   s.files = [&quot;HISTORY&quot;, &quot;LICENSE&quot;, &quot;README.rdoc&quot;, &quot;lib/sanitize/config/basic.rb&quot;, &quot;lib/sanitize/config/relaxed.rb&quot;, &quot;lib/sanitize/config/restricted.rb&quot;, &quot;lib/sanitize/config.rb&quot;, &quot;lib/sanitize/version.rb&quot;, &quot;lib/sanitize.rb&quot;]
   s.homepage = %q{http://github.com/rgrove/sanitize/}</diff>
      <filename>sanitize.gemspec</filename>
    </modified>
    <modified>
      <diff>@@ -306,20 +306,23 @@ end
 describe 'transformers' do
   # YouTube transformer
   youtube = lambda do |env|
-    node = env[:node]
-    name = node.name.to_s.downcase
+    node   = env[:node]
+    parent = node.parent
+    name   = node.name.to_s.downcase
 
     return nil unless name == 'param' || name == 'embed'
-    return nil unless node.parent.name.to_s.downcase == 'object'
+    return nil unless parent.name.to_s.downcase == 'object'
 
     if name == 'param'
-      return nil unless movie_node = node.parent.search('param[@name=&quot;movie&quot;]')[0]
+      return nil unless movie_node = parent.search('param[@name=&quot;movie&quot;]')[0]
       url = movie_node['value']
     elsif name == 'embed'
       url = node['src']
     end
 
-    Sanitize.clean_node!(node.parent, {
+    return nil unless url &amp;&amp; url =~ /^http:\/\/(?:www\.)?youtube\.com\/v\//
+
+    Sanitize.clean_node!(parent, {
       :elements   =&gt; ['embed', 'object', 'param'],
       :attributes =&gt; {
         'embed'  =&gt; ['allowfullscreen', 'allowscriptaccess', 'height', 'src', 'type', 'width'],
@@ -328,24 +331,20 @@ describe 'transformers' do
       }
     })
 
-    if url &amp;&amp; url =~ /^http:\/\/(?:www\.)?youtube\.com\/v\//
-      return {
-        :whitelist_nodes =&gt; [node, node.parent]
-      }
-    end
+    {:whitelist_nodes =&gt; [node, parent]}
   end
 
   should 'allow youtube video embeds via the youtube transformer' do
-    input  = '&lt;div&gt;&lt;object height=&quot;344&quot; width=&quot;425&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;425&quot; height=&quot;344&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;'
-    output = Nokogiri::HTML::DocumentFragment.parse('&lt;object height=&quot;344&quot; width=&quot;425&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;425&quot; height=&quot;344&quot;&gt;&lt;/embed&gt;&lt;/object&gt;').to_xhtml(:encoding =&gt; 'utf-8', :indent =&gt; 0, :save_with =&gt; Nokogiri::XML::Node::SaveOptions::AS_XHTML)
+    input  = '&lt;div&gt;&lt;object foo=&quot;bar&quot; height=&quot;344&quot; width=&quot;425&quot;&gt;&lt;b&gt;test&lt;/b&gt;&lt;param foo=&quot;bar&quot; name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;425&quot; height=&quot;344&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;'
+    output = Nokogiri::HTML::DocumentFragment.parse('&lt;object height=&quot;344&quot; width=&quot;425&quot;&gt;test&lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;425&quot; height=&quot;344&quot;&gt;&lt;/embed&gt;&lt;/object&gt;').to_xhtml(:encoding =&gt; 'utf-8', :indent =&gt; 0, :save_with =&gt; Nokogiri::XML::Node::SaveOptions::AS_XHTML)
 
-    Sanitize.clean!(input, :transformers =&gt; [youtube]).should.equal(output)
+    Sanitize.clean!(input, :transformers =&gt; youtube).should.equal(output)
   end
 
   should 'not allow non-youtube video embeds via the youtube transformer' do
     input  = '&lt;div&gt;&lt;object height=&quot;344&quot; width=&quot;425&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://www.eviltube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.eviltube.com/v/a1Y73sPHKxw&amp;hl=en&amp;fs=1&amp;&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;425&quot; height=&quot;344&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;'
     output = ''
 
-    Sanitize.clean!(input, :transformers =&gt; [youtube]).should.equal(output)
+    Sanitize.clean!(input, :transformers =&gt; youtube).should.equal(output)
   end
 end</diff>
      <filename>test/spec_sanitize.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>c50099b28da11ab8dd0dbfe70b8bfc9cd9b17fd6</id>
    </parent>
  </parents>
  <author>
    <name>Ryan Grove</name>
    <email>ryan@wonko.com</email>
  </author>
  <url>http://github.com/rgrove/sanitize/commit/3597b24156d0d2e347af2b999988a42ad6deffce</url>
  <id>3597b24156d0d2e347af2b999988a42ad6deffce</id>
  <committed-date>2009-11-04T18:01:27-08:00</committed-date>
  <authored-date>2009-11-04T18:01:27-08:00</authored-date>
  <message>Transformer refinements and (gasp!) documentation.</message>
  <tree>722c46a6311d57ecfa08421f4baf468cc7768d8d</tree>
  <committer>
    <name>Ryan Grove</name>
    <email>ryan@wonko.com</email>
  </committer>
</commit>
