require "spec" require "xml" require "spec/helpers/string" require "semantic_version" describe XML do it "parses" do doc = XML.parse(<<-XML John XML ) doc.document.should eq(doc) doc.name.should eq("document") doc.attributes.should be_empty doc.namespace.should be_nil people = doc.root.not_nil! people.name.should eq("people") people.type.should eq(XML::Node::Type::ELEMENT_NODE) people.attributes.should be_empty children = doc.children children.size.should eq(1) children.should_not be_empty people = children[0] people.name.should eq("people") people.document.should eq(doc) children = people.children children.size.should eq(3) text = children[0] text.name.should eq("text") text.content.should eq("\n ") person = children[1] person.name.should eq("person") text = children[2] text.content.should eq("\n") attrs = person.attributes attrs.should_not be_empty attrs.size.should eq(2) attr = attrs[0] attr.name.should eq("id") attr.content.should eq("1") attr.text.should eq("1") attr.inner_text.should eq("1") attr = attrs[1] attr.name.should eq("id2") attr.content.should eq("2") attrs["id"].content.should eq("1") attrs["id2"].content.should eq("2") attrs["id3"]?.should be_nil expect_raises(KeyError) { attrs["id3"] } person["id"].should eq("1") person["id2"].should eq("2") person["id3"]?.should be_nil expect_raises(KeyError) { person["id3"] } name = person.children.find! { |node| node.name == "name" } name.content.should eq("John") name.parent.should eq(person) end it "parses from io" do io = IO::Memory.new(<<-XML John XML ) doc = XML.parse(io) doc.document.should eq(doc) doc.name.should eq("document") people = doc.children.find! { |node| node.name == "people" } person = people.children.find! { |node| node.name == "person" } person["id"].should eq("1") end it "raises exception on empty string" do expect_raises XML::Error, "Document is empty" do XML.parse("") end end it "does to_s" do string = <<-XML \ John XML doc = XML.parse(string) doc.to_s.strip.should eq(<<-XML John XML ) end it "navigates in tree" do doc = XML.parse(<<-XML XML ) people = doc.first_element_child.not_nil! people.name.should eq("people") person = people.first_element_child.not_nil! person.name.should eq("person") person["id"].should eq("1") text = person.next.not_nil! text.content.should eq("\n ") text.previous.should eq(person) text.previous_sibling.should eq(person) person.next_sibling.should eq(text) person2 = text.next.not_nil! person2.name.should eq("person") person2["id"].should eq("2") person.next_element.should eq(person2) person2.previous_element.should eq(person) end describe "#errors" do it do options = XML::ParserOptions::RECOVER | XML::ParserOptions::NONET xml = XML.parse(%(), options) xml.root.not_nil!.name.should eq("people") xml.errors.try(&.map(&.to_s)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] xml = XML.parse(%()) xml.errors.should be_nil end describe "NOERROR option (https://github.com/crystal-lang/crystal/issues/16090)" do it "is unset by default" do XML.parse("").errors.try(&.map(&.message)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] end it "if set, may suppress context-error handler" do if SemanticVersion.parse(XML.libxml2_version) < SemanticVersion.parse("2.13.0") XML.parse("", XML::ParserOptions[RECOVER, NOERROR]).errors.try(&.map(&.message)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] else XML.parse("", XML::ParserOptions[RECOVER, NOERROR]).errors.try(&.map(&.message)).should be_nil end end it "explicitly unset" do XML.parse("", XML::ParserOptions[RECOVER]).errors.try(&.map(&.message)).should eq ["Opening and ending tag mismatch: people line 1 and foo"] end end end describe "#namespace" do describe "when the node has a namespace" do describe "with a prefix" do it "return the prefixed namespace" do doc = XML.parse(<<-XML) XML namespace = doc.root.not_nil!.namespace.should be_a XML::Namespace namespace.href.should eq "http://a9.com/-/spec/opensearchrss/1.0/" namespace.prefix.should eq "openSearch" end end describe "with a default prefix" do it "return the default namespace" do doc = XML.parse(<<-XML) XML namespace = doc.root.not_nil!.namespace.should be_a XML::Namespace namespace.href.should eq "http://a9.com/-/spec/opensearchrss/1.0/" namespace.prefix.should be_nil end end describe "without an explicit declaration on the node" do it "returns the related namespace" do doc = XML.parse(<<-XML) XML root = doc.root.not_nil! namespace = root.children[1].namespace.should be_a XML::Namespace namespace.href.should eq "http://www.w3.org/2005/Atom" namespace.prefix.should be_nil namespace = root.children[3].namespace.should be_a XML::Namespace namespace.href.should eq "https://a-namespace" namespace.prefix.should eq "a" end end end describe "when the node does not have namespace" do it "should return nil" do doc = XML.parse(<<-XML) XML doc.root.not_nil!.namespace.should be_nil end end describe "when the element does not have a namespace, but has namespace declarations" do it "should return nil" do doc = XML.parse(<<-XML) XML doc.root.not_nil!.namespace.should be_nil end end end describe "#namespace_definitions" do it "returns namespaces explicitly defined" do doc = XML.parse(<<-XML) XML namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespace_definitions namespaces.size.should eq(1) namespaces[0].href.should eq("http://c") namespaces[0].prefix.should eq "c" end it "returns an empty array if no namespaces are defined" do doc = XML.parse(<<-XML) XML doc.root.not_nil!.first_element_child.not_nil!.namespace_definitions.should be_empty end end describe "#namespace_scopes" do it "gets root namespaces scopes" do doc = XML.parse(<<-XML) XML namespaces = doc.root.not_nil!.namespace_scopes namespaces.size.should eq(2) namespaces[0].href.should eq("http://www.w3.org/2005/Atom") namespaces[0].prefix.should be_nil namespaces[1].href.should eq("http://a9.com/-/spec/opensearchrss/1.0/") namespaces[1].prefix.should eq("openSearch") end it "returns empty array if no namespaces scopes exists" do doc = XML.parse(<<-XML) John XML namespaces = doc.root.not_nil!.namespace_scopes namespaces.size.should eq(0) end it "includes parent namespaces" do doc = XML.parse(<<-XML) XML namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespace_scopes namespaces.size.should eq(3) namespaces[0].href.should eq("http://c") namespaces[0].prefix.should eq "c" namespaces[1].href.should eq("http://www.w3.org/2005/Atom") namespaces[1].prefix.should be_nil namespaces[2].href.should eq("http://a9.com/-/spec/opensearchrss/1.0/") namespaces[2].prefix.should eq("openSearch") end end describe "#namespaces" do it "gets root namespaces as hash" do doc = XML.parse(<<-XML) XML namespaces = doc.root.not_nil!.namespaces namespaces.should eq({ "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:openSearch" => "http://a9.com/-/spec/opensearchrss/1.0/", }) end it "includes parent namespaces" do doc = XML.parse(<<-XML) XML namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespaces namespaces.should eq({ "xmlns:c" => "http://c", "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:openSearch" => "http://a9.com/-/spec/opensearchrss/1.0/", }) end it "returns an empty hash if there are no namespaces" do doc = XML.parse(<<-XML) XML namespaces = doc.root.not_nil!.first_element_child.not_nil!.namespaces namespaces.should eq({} of String => String?) end end it "reads big xml file (#1455)" do content = "." * 20_000 string = %(#{content}) parsed = XML.parse(IO::Memory.new(string)) parsed.root.not_nil!.children[0].text.should eq(content) end it "sets node text/content" do doc = XML.parse(<<-XML) John XML root = doc.root.not_nil! root.text = "Peter" root.text.should eq("Peter") root.content = "Foo 👌" root.content.should eq("Foo 👌") end it "doesn't set invalid node content" do doc = XML.parse(<<-XML) John XML root = doc.root.not_nil! expect_raises(Exception, "Cannot escape") do root.content = "\0" end end it "escapes content" do doc = XML.parse(<<-XML) John XML root = doc.root.not_nil! root.text = "" root.text.should eq("") assert_prints root.to_xml, %(<foo>) end it "escapes content HTML fragment" do doc = XML.parse_html(<<-XML, XML::HTMLParserOptions.default | XML::HTMLParserOptions::NOIMPLIED | XML::HTMLParserOptions::NODEFDTD)

foo

XML node = doc.children.first node.text = "" node.text.should eq("") assert_prints node.to_xml, %(

<foo>

) end it "parses HTML UTF-8 from memory (#13703)" do doc = XML.parse_html("

České psaní

") node = doc.root.try(&.children.first).should_not be_nil node.text.should eq "České psaní" end it "parses HTML UTF-8 from IO (#13703)" do doc = XML.parse_html(IO::Memory.new("

České psaní

")) node = doc.root.try(&.children.first).should_not be_nil node.text.should eq "České psaní" end it "parses XML UTF-8 from memory (#13703)" do doc = XML.parse("

České psaní

") node = doc.root.try(&.children.first).should_not be_nil node.text.should eq "České psaní" end it "parses XML UTF-8 from IO (#13703)" do doc = XML.parse(IO::Memory.new("

České psaní

")) node = doc.root.try(&.children.first).should_not be_nil node.text.should eq "České psaní" end it "gets empty content" do doc = XML.parse("") doc.children.first.content.should eq("") end it "sets node name" do doc = XML.parse(<<-XML John XML ) root = doc.root.not_nil! root.name = "last-name" root.name.should eq("last-name") end it "doesn't set invalid node name" do doc = XML.parse(<<-XML John XML ) root = doc.root.not_nil! expect_raises(XML::Error, "Invalid node name") do root.name = " foo bar" end expect_raises(XML::Error, "Invalid node name") do root.name = "foo bar" end expect_raises(XML::Error, "Invalid node name") do root.name = "1foo" end expect_raises(XML::Error, "Invalid node name") do root.name = "\0foo" end end it "gets encoding" do doc = XML.parse(<<-XML XML ) doc.encoding.should eq("UTF-8") end it "gets encoding when nil" do doc = XML.parse(<<-XML XML ) doc.encoding.should be_nil end it "gets version" do doc = XML.parse(<<-XML XML ) doc.version.should eq("1.0") end it "unlinks nodes" do xml = <<-XML Jane Doe XML document = XML.parse(xml) node = document.xpath_node("//lastname").not_nil! node.unlink document.xpath_node("//lastname").should be_nil end it "does to_s with correct encoding (#2319)" do xml_str = <<-XML たろう XML doc = XML.parse(xml_str) doc.root.to_s.should eq("\n たろう\n") end it "sets an attribute" do doc = XML.parse(%{}) root = doc.root.not_nil! root["bar"] = "baz" root["bar"].should eq("baz") root.to_s.should eq(%{}) end it "changes an attribute" do doc = XML.parse(%{}) root = doc.root.not_nil! root["bar"] = "baz" root["bar"].should eq("baz") root.to_s.should eq(%{}) root["bar"] = 1 root["bar"].should eq("1") end it "deletes an attribute" do doc = XML.parse(%{}) root = doc.root.not_nil! res = root.delete("bar") root["bar"]?.should be_nil root.to_s.should eq(%{}) res.should eq "baz" res = root.delete("biz") res.should be_nil end it "shows content when inspecting attribute" do doc = XML.parse(%{}) attr = doc.root.not_nil!.attributes.first attr.inspect.should contain(%(content="baz")) end it ".build" do XML.build do |builder| builder.element "foo" { } end.should eq %[\n\n] end describe ".build_fragment" do it "builds fragment without XML declaration" do XML.build_fragment do |builder| builder.element "foo" { } end.should eq %[\n] end it "closes open elements" do XML.build_fragment do |builder| builder.start_element "foo" builder.start_element "bar" end.should eq %[\n] end end it ".libxml2_version" do XML.libxml2_version.should match /2\.\d+\.\d+/ end end