Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a method so you can know whether an element is empty #19

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 20 additions & 2 deletions lib/crack/xml.rb
Expand Up @@ -13,7 +13,7 @@
# This represents the hard part of the work, all I did was change the # This represents the hard part of the work, all I did was change the
# underlying parser. # underlying parser.
class REXMLUtilityNode #:nodoc: class REXMLUtilityNode #:nodoc:
attr_accessor :name, :attributes, :children, :type attr_accessor :name, :attributes, :children, :type, :empty_node


def self.typecasts def self.typecasts
@@typecasts @@typecasts
Expand Down Expand Up @@ -62,13 +62,22 @@ def initialize(name, normalized_attributes = {})
@attributes = undasherize_keys(attributes) @attributes = undasherize_keys(attributes)
@children = [] @children = []
@text = false @text = false
@empty_node = false
end end


def add_node(node) def add_node(node)
@text = true if node.is_a? String @text = true if node.is_a? String
@children << node @children << node
end end


def emptyze!(obj,value)
# Monkeypatch to allow empty_node? in the hash values
obj.class.instance_eval do
define_method(:empty_node?) { @_empty_node }
end
obj.instance_eval { @_empty_node = value }
end

def to_hash def to_hash
if @type == "file" if @type == "file"
f = StringIO.new((@children.first || '').unpack('m').first) f = StringIO.new((@children.first || '').unpack('m').first)
Expand All @@ -84,6 +93,7 @@ class << f
t = typecast_value( unnormalize_xml_entities( inner_html ) ) t = typecast_value( unnormalize_xml_entities( inner_html ) )
t.class.send(:attr_accessor, :attributes) t.class.send(:attr_accessor, :attributes)
t.attributes = attributes t.attributes = attributes
emptyze!(t,false)
return { name => t } return { name => t }
else else
#change repeating groups into an array #change repeating groups into an array
Expand Down Expand Up @@ -114,6 +124,8 @@ class << f
out = out.empty? ? nil : out out = out.empty? ? nil : out
end end


emptyze!(out,@empty_node)

if @type && out.nil? if @type && out.nil?
{ name => typecast_value(out) } { name => typecast_value(out) }
else else
Expand Down Expand Up @@ -189,6 +201,7 @@ class XML
def self.parse(xml) def self.parse(xml)
stack = [] stack = []
parser = REXML::Parsers::BaseParser.new(xml) parser = REXML::Parsers::BaseParser.new(xml)
inner_elements = [0]


while true while true
event = parser.pull event = parser.pull
Expand All @@ -199,10 +212,15 @@ def self.parse(xml)
# do nothing # do nothing
when :start_element when :start_element
stack.push REXMLUtilityNode.new(event[1], event[2]) stack.push REXMLUtilityNode.new(event[1], event[2])
inner_elements[-1] += 1
inner_elements << 0
when :end_element when :end_element
if stack.size > 1 if stack.size > 1
temp = stack.pop temp = stack.pop
temp.empty_node = true if inner_elements.pop == 0
stack.last.add_node(temp) stack.last.add_node(temp)
else
stack.last.empty_node = true if inner_elements.pop == 0
end end
when :text, :cdata when :text, :cdata
stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty? stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
Expand All @@ -211,4 +229,4 @@ def self.parse(xml)
stack.length > 0 ? stack.pop.to_hash : {} stack.length > 0 ? stack.pop.to_hash : {}
end end
end end
end end
28 changes: 27 additions & 1 deletion test/xml_test.rb
Expand Up @@ -68,6 +68,32 @@ class XmlTest < Test::Unit::TestCase


Crack::XML.parse(xml).should == hash Crack::XML.parse(xml).should == hash
end end

context "Using empty_node? method in the output hash" do
setup do
@data1 = Crack::XML.parse("<a h='s' />")
@data2 = Crack::XML.parse("<a><h>s</h></a>")
@data3 = Crack::XML.parse("<a><b><c></c></b></a>")
end

should "allow us to differenciate an empty tag with attributes from inner tags with text nodes" do
@data1.should == @data2
@data1["a"].empty_node?.should_not == @data2["a"].empty_node?
end

should "be done by monkeypatching the hash keys with a method called empty_node? that tells whether the given node was empty" do
@data3["a"].methods.should include(:empty_node?)
@data3["a"].empty_node?.should == false
@data3["a"]["b"].empty_node?.should == false
@data3["a"]["b"]["c"].empty_node?.should == true
end

should "be false in text nodes" do
@data2["a"]["h"].empty_node?.should == false
end

end



context "Parsing xml with text and attributes" do context "Parsing xml with text and attributes" do
setup do setup do
Expand Down Expand Up @@ -486,4 +512,4 @@ class XmlTest < Test::Unit::TestCase
should "handle an xml string containing a single space" do should "handle an xml string containing a single space" do
Crack::XML.parse(' ').should == {} Crack::XML.parse(' ').should == {}
end end
end end