2

So let's say I have a builder template like the following:

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  xml.environment do |environment|
    environment.title title
    environment.feed feed
    environment.status status
    environment.description description
    # many many more
  end
end
builder.to_xml

If feed and description were nil, it could output:

<?xml version="1.0" encoding="UTF-8"?>
<environment>
  <title>title</title>
  <feed/>
  <status>status</status>
  <description/>
</environment>

I'd rather it ignored the nils altogether. What's the best way of achieving this?

Desired output:

<?xml version="1.0" encoding="UTF-8"?>
<environment>
  <title>title</title>
  <status>status</status>
</environment>

Current solution:

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  xml.environment do |environment|
    environment.title title if title
    environment.feed feed if feed
    environment.status status if status
    environment.description description if description
    # many many more
  end
end
builder.to_xml

Is there a cleaner way?

Would this be a useful option on Builder#initialize?

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8', :empty_nodes => false)

lebreeze
  • 5,094
  • 26
  • 34
  • The best I've come up with is either looping through the nodes and removing them at the end, or iterating through the available values and only generating nodes if they should be. – lebreeze Mar 23 '11 at 09:15

2 Answers2

1

I know this question is old and already has an answer, but having just spent a little bit of time figuring this out, the quick solution to this problem (though probably not performant) lies in Nokogiri::XML::Node#traverse.

Once you've built your document, you'll just need to traverse it once, checking for empty tags and removing them as you go, like so:

builder.doc.traverse do |node|
  node.remove if node.element? && node.text == ""
end

Note that #traverse returns the result of the last operation, so make sure return something (your builder, or your XML string) after you've finished traversing.

Corey Woodcox
  • 96
  • 1
  • 5
1

It depends how many fields you really have, but my suggestion is that you're already making a mistake by manually typing in each one.

If order doesn't matter, just use a hash. (otherwise use an OrderedHash)

fields = {
  :title => title,
  :feed  => feed,
  :status => status,
  :description => description,
  # many more
}

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  xml.environment do |environment|
    fields.each do |field_name, field_value|
      if field_value
        environment.send(field_name, field_value)
      end
    end
  end
end
builder.to_xml

seems to work.

It's not pretty, but at least you just have to define your fields and values in one place. Preferably an object method?

Matthew Rudy
  • 16,724
  • 3
  • 46
  • 44
  • also, Rails' XML serializer outputs the following `` – Matthew Rudy Mar 23 '11 at 10:34
  • Hmmm... Order is very important and there are quite a lot of fields, included nested ones. If it makes a difference there are numerous templates to implement and ideally people will be able to include their own (eventually). Here's one example xsd: http://eeml.org/xsd/0.5.1/0.5.1.xsd – lebreeze Mar 23 '11 at 10:39
  • 1
    `require 'active_support/ordered_hash'` or just pinch the code – Matthew Rudy Mar 23 '11 at 11:35