From 09fa87e4036a1d49c2bc37fed73fc706dcbeb8c1 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Wed, 18 Feb 2015 14:08:22 -0700 Subject: [PATCH] allow a simpler syntax for using a builder on detached nodes rather than node = nil Nokogiri::XML::Builder.with(document.root) do |xml| xml.element do |xml| node = xml.parent xml.parent.unlink end end you can just do node = Nokogiri::XML::Builder.detached(document) do |xml| xml.element end --- lib/nokogiri/xml/builder.rb | 54 +++++++++++++++++++++++++++++-------- test/xml/test_builder.rb | 14 ++++++++++ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/nokogiri/xml/builder.rb b/lib/nokogiri/xml/builder.rb index a957de621e..53c39c7037 100644 --- a/lib/nokogiri/xml/builder.rb +++ b/lib/nokogiri/xml/builder.rb @@ -234,6 +234,9 @@ class Builder # A context object for use when the block has no arguments attr_accessor :context + # The detached children when building detached nodes + attr_reader :children + attr_accessor :arity # :nodoc: ### @@ -254,6 +257,25 @@ def self.with root, &block new({}, root, &block) end + ### + # Create a builder with an existing document. This is for use when + # you have an existing document that you would like to augment with + # builder methods. The builder context created will not have a + # parent, so any elements created will need to later be attached + # to the document + # + # For example: + # + # doc = Nokogiri::XML::Document.new + # doc << Nokogiri::XML::Builder.detached(doc) do |xml| + # # ... Use normal builder methods here ... + # xml.awesome # add the "awesome" tag below "some_tag" + # end + # + def self.detached document, &block + new({}, nil, document, &block).children + end + ### # Create a new Builder object. +options+ are sent to the top level # Document that is being built. @@ -263,9 +285,13 @@ def self.with root, &block # Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| # ... # end - def initialize options = {}, root = nil, &block + def initialize options = {}, root = nil, document = nil, &block - if root + if document + @doc = document + @parent = nil + @children = Nokogiri::XML::NodeSet.new(@doc) + elsif root @doc = root.document @parent = root else @@ -318,15 +344,17 @@ def comment string # Build a tag that is associated with namespace +ns+. Raises an # ArgumentError if +ns+ has not been defined higher in the tree. def [] ns - if @parent != @doc - @ns = @parent.namespace_definitions.find { |x| x.prefix == ns.to_s } - end - return self if @ns - - @parent.ancestors.each do |a| - next if a == doc - @ns = a.namespace_definitions.find { |x| x.prefix == ns.to_s } + if @parent + if @parent != @doc + @ns = @parent.namespace_definitions.find { |x| x.prefix == ns.to_s } + end return self if @ns + + @parent.ancestors.each do |a| + next if a == doc + @ns = a.namespace_definitions.find { |x| x.prefix == ns.to_s } + return self if @ns + end end @ns = { :pending => ns.to_s } @@ -380,7 +408,11 @@ def method_missing method, *args, &block # :nodoc: ### # Insert +node+ as a child of the current Node def insert(node, &block) - node = @parent.add_child(node) + if @parent + node = @parent.add_child(node) + else + @children << node + end if block_given? old_parent = @parent @parent = node diff --git a/test/xml/test_builder.rb b/test/xml/test_builder.rb index 83e2e1ec2e..b05dcc5392 100644 --- a/test/xml/test_builder.rb +++ b/test/xml/test_builder.rb @@ -331,6 +331,20 @@ def test_builder_can_handle_namespace_override assert_nil doc.at_xpath("//*[local-name() = 'products']").namespace end + def test_detached + doc = Nokogiri::XML::Document.new + doc.root = doc.create_element('root') + nodes = Nokogiri::XML::Builder.detached(doc) do |xml| + xml['foo'].bar('xmlns:foo' => 'fooey') + xml['foo2'].bar('xmlns:foo2' => 'fooey') + end + assert_equal doc.root.children.length, 0 + doc.root << nodes + assert_equal doc.root.children.length, 2 + assert_equal doc.root.children.first.namespace.prefix, 'foo' + assert_equal doc.root.children.last.namespace.prefix, 'foo2' + end + private def namespaces_defined_on(node)