require "spec" require "xml" private def xml <<-XML John Peter XML end module XML describe Reader do describe ".new" do context "with default parser options" do it "can be initialized from a string" do reader = Reader.new(xml) reader.should be_a(XML::Reader) reader.read.should be_true reader.name.should eq("people") reader.read.should be_true reader.name.should eq("#text") end it "can be initialized from an io" do io = IO::Memory.new(xml) reader = Reader.new(io) reader.should be_a(XML::Reader) reader.read.should be_true reader.name.should eq("people") reader.read.should be_true reader.name.should eq("#text") end end context "with custom parser options" do it "can be initialized from a string" do reader = Reader.new(xml, XML::ParserOptions::NOBLANKS) reader.should be_a(XML::Reader) reader.read.should be_true reader.name.should eq("people") reader.read.should be_true reader.name.should eq("person") end it "can be initialized from an io" do io = IO::Memory.new(xml) reader = Reader.new(io, XML::ParserOptions::NOBLANKS) reader.should be_a(XML::Reader) reader.read.should be_true reader.name.should eq("people") reader.read.should be_true reader.name.should eq("person") end end end describe "#read" do it "reads all nodes" do reader = Reader.new(xml) reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("people") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("person") reader["id"].should eq("1") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::TEXT) reader.name.should eq("#text") reader.value.should eq("John") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("person") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("person") reader["id"].should eq("2") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::TEXT) reader.name.should eq("#text") reader.value.should eq("Peter") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("person") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("people") reader.read.should be_false end it "reads all non-blank nodes with NOBLANKS option" do reader = Reader.new(xml, XML::ParserOptions::NOBLANKS) reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("people") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("person") reader["id"].should eq("1") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::TEXT) reader.name.should eq("#text") reader.value.should eq("John") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("person") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("person") reader["id"].should eq("2") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::TEXT) reader.name.should eq("#text") reader.value.should eq("Peter") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("name") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("person") reader.read.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("people") reader.read.should be_false end end describe "#next" do it "reads next node in doc order, skipping subtrees" do reader = Reader.new(xml) while reader.read break if reader.depth == 2 end reader.next.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("name") reader.next.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.next.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("person") reader["id"].should eq("1") reader.next.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.next.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("person") reader["id"].should eq("2") reader.next.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.next.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("people") reader.next.should be_false end end describe "#next_sibling" do it "reads next sibling node in doc order, skipping subtrees" do reader = Reader.new(xml) while reader.read break if reader.depth == 1 end reader.next_sibling.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("person") reader["id"].should eq("1") reader.next_sibling.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.next_sibling.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("person") reader["id"].should eq("2") reader.next_sibling.should be_true reader.node_type.should eq(XML::Reader::Type::SIGNIFICANT_WHITESPACE) reader.name.should eq("#text") reader.next_sibling.should be_false end end describe "#node_type" do it "returns the node type" do reader = Reader.new("") reader.node_type.should eq(XML::Reader::Type::NONE) reader.read reader.node_type.should eq(XML::Reader::Type::ELEMENT) end end describe "#name" do it "reads node name" do reader = Reader.new("") reader.name.should eq("") reader.read reader.name.should eq("root") end end describe "#empty_element?" do it "checks if the node is empty" do reader = Reader.new("") reader.empty_element?.should be_false reader.read reader.empty_element?.should be_true reader = Reader.new("") reader.read reader.empty_element?.should be_false end end describe "#has_attributes?" do it "checks if the node has attributes" do reader = Reader.new(%{}) reader.has_attributes?.should be_false reader.read # reader.has_attributes?.should be_true reader.read # reader.has_attributes?.should be_false reader.read # reader.has_attributes?.should be_true end end describe "#attributes_count" do it "returns the node's number of attributes" do reader = Reader.new(%{}) reader.attributes_count.should eq(0) reader.read # reader.attributes_count.should eq(1) reader.read # reader.attributes_count.should eq(0) reader.read # # This is weird, since has_attributes? will be true. reader.attributes_count.should eq(0) end end describe "#move_to_first_attribute" do it "moves to the first attribute of the node" do reader = Reader.new(%{}) reader.move_to_first_attribute.should be_false reader.read # reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.move_to_first_attribute.should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id") reader.value.should eq("1") reader.read # reader.move_to_first_attribute.should be_false reader.read # reader.move_to_first_attribute.should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id") reader.value.should eq("1") reader.read.should be_false end end describe "#move_to_next_attribute" do it "moves to the next attribute of the node" do reader = Reader.new(%{}) reader.move_to_next_attribute.should be_false reader.read # reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.move_to_next_attribute.should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id") reader.value.should eq("1") reader.move_to_next_attribute.should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id2") reader.value.should eq("2") reader.move_to_next_attribute.should be_false reader.read # reader.move_to_next_attribute.should be_false reader.read # reader.move_to_next_attribute.should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id") reader.value.should eq("1") reader.read.should be_false end end describe "#move_to_attribute" do it "moves to attribute with the specified name" do reader = Reader.new(%{}) reader.move_to_attribute("id2").should be_false reader.read # reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.move_to_attribute("id2").should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id2") reader.value.should eq("2") reader.move_to_attribute("id").should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id") reader.value.should eq("1") reader.move_to_attribute("bogus").should be_false reader.read # reader.move_to_attribute("id2").should be_false reader.read # reader.move_to_attribute("id2").should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id2") reader.value.should eq("2") reader.read.should be_false end it "raises if attribute contains null byte" do reader = Reader.new("") expect_raises(Exception) { reader.move_to_attribute("\0") } end end describe "#[]" do it "reads node attributes" do reader = Reader.new("") expect_raises(KeyError) { reader["id"] } reader.read expect_raises(KeyError) { reader["id"] } reader = Reader.new(%{}) reader.read reader["id"].should eq("1") reader = Reader.new(%{}) reader.read # reader["id"].should eq("1") reader.read # expect_raises(KeyError) { reader["id"] } reader.read # reader["id"].should eq("1") end it "raises if attribute contains null byte" do reader = Reader.new("") expect_raises(Exception) { reader["\0"] } end end describe "#[]?" do it "reads node attributes" do reader = Reader.new("") reader["id"]?.should be_nil reader.read reader["id"]?.should be_nil reader = Reader.new(%{}) reader.read reader["id"]?.should eq("1") reader = Reader.new(%{}) reader.read # reader["id"]?.should eq("1") reader.read # reader["id"]?.should be_nil reader.read # reader["id"]?.should eq("1") end it "raises if attribute contains null byte" do reader = Reader.new("") expect_raises(Exception) { reader["\0"]? } end end describe "#move_to_element" do it "moves to the element node that contains the current attribute node" do reader = Reader.new(%{}) reader.move_to_element.should be_false reader.read # reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("root") reader.move_to_element.should be_false reader.move_to_first_attribute.should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id") reader.move_to_element.should be_true reader.node_type.should eq(XML::Reader::Type::ELEMENT) reader.name.should eq("root") reader.read # reader.move_to_element.should be_false reader.move_to_first_attribute.should be_true reader.node_type.should eq(XML::Reader::Type::ATTRIBUTE) reader.name.should eq("id") reader.move_to_element.should be_true reader.node_type.should eq(XML::Reader::Type::END_ELEMENT) reader.name.should eq("root") reader.read.should be_false end end describe "#depth" do it "returns the depth of the node" do reader = Reader.new("") reader.depth.should eq(0) reader.read # reader.depth.should eq(0) reader.read # reader.depth.should eq(1) reader.read # reader.depth.should eq(0) end end describe "#read_inner_xml" do it "reads the contents of the node including child nodes and markup" do reader = Reader.new("\n\n\n") reader.read_inner_xml.should eq("") reader.read # reader.read_inner_xml.should eq("\n\n") reader.read # \n reader.read_inner_xml.should eq("") reader.read # reader.read_inner_xml.should eq("") reader.read # \n reader.read_inner_xml.should eq("") reader.read # reader.read_inner_xml.should eq("") reader.read.should be_false end end describe "#read_outer_xml" do it "reads the xml of the node including child nodes and markup" do reader = Reader.new("\n\n\n") reader.read_outer_xml.should eq("") reader.read # reader.read_outer_xml.should eq("\n\n") reader.read # \n reader.read_outer_xml.should eq("\n") reader.read # reader.read_outer_xml.should eq("") reader.read # \n reader.read_outer_xml.should eq("\n") reader.read # # Note that the closing element is transformed into a self-closing one. reader.read_outer_xml.should eq("") reader.read.should be_false end end describe "#expand" do it "raises an exception if the node could not be expanded" do reader = Reader.new(%{}) reader.read # node = reader.expand node.should be_a(XML::Node) node.attributes["id"].content.should eq("1") node.xpath_node("child").should be_a(XML::Node) end it "is only available until the next read" do reader = Reader.new(%{}) reader.read # reader.read # node = reader.expand node.should be_a(XML::Node) node.xpath_node("subchild").should be_a(XML::Node) reader.read # reader.read # node.xpath_node("subchild").should be_nil end end describe "#expand?" do it "parses the content of the node and subtree" do reader = Reader.new(%{}) reader.expand?.should be_nil reader.read # node = reader.expand? node.should be_a(XML::Node) node.not_nil!.attributes["id"].content.should eq("1") node.not_nil!.xpath_node("child").should be_a(XML::Node) end it "is only available until the next read" do reader = Reader.new(%{}) reader.read # reader.read # node = reader.expand? node.should be_a(XML::Node) node.not_nil!.xpath_node("subchild").should be_a(XML::Node) reader.read # reader.read # node.not_nil!.xpath_node("subchild").should be_nil end end describe "#value" do it "reads node text value" do reader = Reader.new(%{hello}) reader.value.should eq("") reader.read # reader.value.should eq("") reader.read # hello reader.value.should eq("hello") reader.read # reader.value.should eq(" world ") reader.read # reader.move_to_first_attribute.should be_true reader.value.should eq("1") end end describe "#to_unsafe" do it "returns a pointer to the underlying LibXML::XMLTextReader" do reader = Reader.new("") reader.to_unsafe.should be_a(LibXML::XMLTextReader) end end describe "#errors" do it "makes errors accessible" do options = XML::ParserOptions::RECOVER | XML::ParserOptions::NONET reader = XML::Reader.new(%(), options) reader.read reader.expand? reader.errors.map(&.to_s).should eq ["Opening and ending tag mismatch: people line 1 and foo"] end end end end