diff --git a/spec/converters/xml_spec.cr b/spec/converters/xml_spec.cr index 462bf1c..b8906b2 100644 --- a/spec/converters/xml_spec.cr +++ b/spec/converters/xml_spec.cr @@ -98,6 +98,15 @@ XML_INLINE_ARRAY_WITHIN_ARRAY = <<-XML XML +XML_NAMESPACE_ARRAY = <<-XML + + + 1 + 2 + 3 + +XML + XML_DOCTYPE = <<-XML @@ -146,6 +155,26 @@ XML_ALL_EMPTY = <<-XML XML +XML_NAMESPACE_PREFIXES = <<-XML + + + foo + bar + +XML + +XML_NESTED_NAMESPACES = <<-XML + + + herp + + + + + + +XML + describe OQ::Converters::XML do describe ".deserialize" do # See https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html @@ -211,11 +240,9 @@ describe OQ::Converters::XML do end describe Object do - describe "a key/value pair" do - it "should output correctly" do - run_binary(%(Fred), args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"person":"Fred"}\n) - end + it "a key/value pair" do + run_binary(%(Fred), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"person":"Fred"}\n) end end @@ -227,82 +254,94 @@ describe OQ::Converters::XML do end end - describe "with whitespace" do - it "should output correctly" do - run_binary(WITH_WHITESPACE, args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"item":{"flagID":"0","itemID":"0","locationID":"0","ownerID":"0","quantity":"-1","typeID":"0"}}\n) - end + it "with whitespace" do + run_binary(WITH_WHITESPACE, args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"item":{"flagID":"0","itemID":"0","locationID":"0","ownerID":"0","quantity":"-1","typeID":"0"}}\n) end end - describe "with the prolog" do - it "should output correctly" do - run_binary(%(0), args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"item":{"typeID":"0"}}\n) - end + it "with the prolog" do + run_binary(%(0), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"item":{"typeID":"0"}}\n) end end - describe "a simple object" do - it "should output correctly" do - run_binary(%(JaneDoe), args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"person":{"firstname":"Jane","lastname":"Doe"}}\n) - end + it "a simple object" do + run_binary(%(JaneDoe), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"person":{"firstname":"Jane","lastname":"Doe"}}\n) end end - describe "attributes" do - it "should output correctly" do - run_binary(%(JaneDoe), args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"person":{"@id":"1","@foo":"bar","firstname":"Jane","lastname":"Doe"}}\n) - end + it "attributes" do + run_binary(%(JaneDoe), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"person":{"@id":"1","@foo":"bar","firstname":"Jane","lastname":"Doe"}}\n) end end - describe "nested objects" do - it "should output correctly" do - run_binary(%(JaneDoe15061
123 Foo Street
), args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"person":{"firstname":"Jane","lastname":"Doe","location":{"zip":"15061","address":"123 Foo Street"}}}\n) - end + it "nested objects" do + run_binary(%(JaneDoe15061
123 Foo Street
), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"person":{"firstname":"Jane","lastname":"Doe","location":{"zip":"15061","address":"123 Foo Street"}}}\n) end end - describe "complex object" do - it "should output correctly" do - run_binary(%(24), args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"root":{"x":{"@a":"1","a":"2"},"y":{"@b":"3","#text":"4"}}}\n) - end + it "complex object" do + run_binary(%(24), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"root":{"x":{"@a":"1","a":"2"},"y":{"@b":"3","#text":"4"}}}\n) end end - describe "with mixed content" do - it "with a single #text node" do - run_binary(%(xz), args: ["-i", "xml", "-c", ".root"]) do |output| - output.should eq %({"#text":"x","y":"z"}\n) - end + it "with mixed content" do + run_binary(%(xz), args: ["-i", "xml", "-c", ".root"]) do |output| + output.should eq %({"#text":"x","y":"z"}\n) end end - describe "with an inline array" do - it "should output correctly" do - run_binary(XML_INLINE_ARRAY, args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"article":{"@key":"tr/ibm/RJ2144","author":["E. F. Codd","Robert S. Arnold","Jean-Marc Cadiou","Chin-Liang Chang","Nick Roussopoulos"],"title":"RENDEZVOUS Version 1: An Experimental English Language Query Formulation System for Casual Users of Relational Data Bases.","journal":"IBM Research Report","volume":"RJ2144","month":"January","year":"1978","ee":"db/labs/ibm/RJ2144.html","cdrom":"ibmTR/rj2144.pdf"}}\n) - end + it "with an inline array" do + run_binary(XML_INLINE_ARRAY, args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"article":{"@key":"tr/ibm/RJ2144","author":["E. F. Codd","Robert S. Arnold","Jean-Marc Cadiou","Chin-Liang Chang","Nick Roussopoulos"],"title":"RENDEZVOUS Version 1: An Experimental English Language Query Formulation System for Casual Users of Relational Data Bases.","journal":"IBM Research Report","volume":"RJ2144","month":"January","year":"1978","ee":"db/labs/ibm/RJ2144.html","cdrom":"ibmTR/rj2144.pdf"}}\n) end end - describe "with a doctype" do - it "should output correctly" do - run_binary(XML_DOCTYPE, args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"dblp":{"mastersthesis":{"@key":"ms/Brown92","author":"Kurt P. Brown","title":"PRPL: A Database Workload Specification Language, v1.3.","year":"1992","school":"Univ. of Wisconsin-Madison"}}}\n) - end + it "with a doctype" do + run_binary(XML_DOCTYPE, args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"dblp":{"mastersthesis":{"@key":"ms/Brown92","author":"Kurt P. Brown","title":"PRPL: A Database Workload Specification Language, v1.3.","year":"1992","school":"Univ. of Wisconsin-Madison"}}}\n) end end - describe "with CDATA" do - it "should output correctly" do - run_binary(XML_CDATA, args: ["-i", "xml", "-c", "."]) do |output| - output.should eq %({"desc":"Some Description"}\n) + it "with CDATA" do + run_binary(XML_CDATA, args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"desc":"Some Description"}\n) + end + end + + it "with a prefixed key" do + run_binary(%(bar), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"a:foo":"bar"}\n) + end + end + + describe "with namespaces" do + it "strips prefixes and namespace declarations of a prefixed namespace" do + run_binary(%(bar), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"foo":"bar"}\n) + end + end + + it "strips prefixes and namespace declarations of a multiple namespaces" do + run_binary(%(bar), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"foo":"bar"}\n) + end + end + + it "skips prefixed elements that cause mixed content" do + run_binary(XML_NESTED_NAMESPACES, args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"root":{"foo":{"bar":{"baz":null}}}}\n) + end + end + + it "strips prefixes of elements" do + run_binary(XML_NAMESPACE_PREFIXES, args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"root":{"foo":"foo","bar":"bar"}}\n) end end end @@ -358,6 +397,14 @@ describe OQ::Converters::XML do end end end + + describe "with namespaces" do + it "strips prefixes and namespace declarations" do + run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"items":{"n:number":["1","2"],"number":"3"}}\n) + end + end + end end end @@ -605,132 +652,128 @@ describe OQ::Converters::XML do end end - describe "object value mixed/nested array values" do - it "should emit correctly" do - run_binary(%({"x":[1,[2,[3]]]}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - 1 - - 2 - - 3 - - - \n - XML - ) - end + it "object value mixed/nested array values" do + run_binary(%({"x":[1,[2,[3]]]}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + 1 + + 2 + + 3 + + + \n + XML + ) end end - describe "object value array primitive values" do - it "should emit correctly" do - run_binary(%({"x":[1,2,3]}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - 1 - 2 - 3 - \n - XML - ) - end + it "object value array primitive values" do + run_binary(%({"x":[1,2,3]}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + 1 + 2 + 3 + \n + XML + ) end end end describe Object do - describe "simple key/value" do - it "should output correctly" do - run_binary(%({"name":"Jim"}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - Jim - \n - XML - ) - end + it "simple key/value" do + run_binary(%({"name":"Jim"}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + Jim + \n + XML + ) end end - describe "nested object" do - it "should output correctly" do - run_binary(%({"name":"Jim", "city": {"street":"forbs"}}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - Jim - - forbs - - \n - XML - ) - end + it "nested object" do + run_binary(%({"name":"Jim", "city": {"street":"forbs"}}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + Jim + + forbs + + \n + XML + ) end end - describe "with an attribute" do - it "should output correctly" do - run_binary(%({"name":"Jim", "city": {"@street":"forbs"}}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - Jim - - \n - XML - ) - end + it "with an attribute" do + run_binary(%({"name":"Jim", "city": {"@street":"forbs"}}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + Jim + + \n + XML + ) end end - describe "with an attribute and #text" do - it "should output correctly" do - run_binary(%({"name":"Jim", "city": {"@street":"forbs", "#text": "Atlantic"}}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - Jim - Atlantic - \n - XML - ) - end + it "with an attribute and #text" do + run_binary(%({"name":"Jim", "city": {"@street":"forbs", "#text": "Atlantic"}}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + Jim + Atlantic + \n + XML + ) end end - describe "with attributes" do - it "should output correctly" do - run_binary(%({"name":"Jim", "city": {"@street":"forbs", "@post": 123}}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - Jim - - \n - XML - ) - end + it "with attributes" do + run_binary(%({"name":"Jim", "city": {"@street":"forbs", "@post": 123}}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + Jim + + \n + XML + ) end end - describe "with attributes and #text" do - it "should output correctly" do - run_binary(%({"name":"Jim", "city": {"@street":"forbs", "@post": 123, "#text": "Atlantic"}}), args: ["-o", "xml", "."]) do |output| - output.should eq(<<-XML - - - Jim - Atlantic - \n - XML - ) - end + it "with attributes and #text" do + run_binary(%({"name":"Jim", "city": {"@street":"forbs", "@post": 123, "#text": "Atlantic"}}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + Jim + Atlantic + \n + XML + ) + end + end + + it "with a prefixed key" do + run_binary(%({"foo:name":"Jim"}), args: ["-o", "xml", "."]) do |output| + output.should eq(<<-XML + + + Jim + \n + XML + ) end end end diff --git a/src/converters/xml.cr b/src/converters/xml.cr index 36a159b..7cff36d 100644 --- a/src/converters/xml.cr +++ b/src/converters/xml.cr @@ -29,7 +29,7 @@ module OQ::Converters::XML end # Otherwise process the node as a key/value pair - builder.field node.name do + builder.field self.normalize_node_name node do builder.object do process_children node, builder end @@ -61,7 +61,7 @@ module OQ::Converters::XML end # Determine how to process a node's children - node.children.group_by(&.name).each do |name, children| + node.children.group_by(&->normalize_node_name(::XML::Node)).each do |name, children| # Skip non significant whitespace; Skip mixed character input if children.first.text? && has_nested_elements(node) # Only emit text content if there is only one child @@ -95,6 +95,10 @@ module OQ::Converters::XML node.children.empty? ? nil : node.children.first.content end + private def self.normalize_node_name(node : ::XML::Node) : String + (namespace = node.namespace) && (prefix = namespace.prefix.presence) ? "#{prefix}:#{node.name}" : node.name + end + def self.serialize(input : IO, output : IO, **args) : Nil json = ::JSON::PullParser.new input builder = ::XML::Builder.new output