From ee40e9e9fc99caeadfd309c2cc2dbf0d6edc56d7 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Sun, 12 Apr 2020 12:54:13 -0400 Subject: [PATCH] Better handle some cases & Release `1.0.2` (#55) * Better handle mixed content in some cases * Correctly set node content, even if content is blank * Add specs to ensure compatibility with XML to JSON conventions * Release 1.0.2 --- shard.yml | 2 +- snap/snapcraft.yaml | 2 +- spec/converters/xml_spec.cr | 61 +++++++++++++++++++++++++++++++++++-- src/converters/xml.cr | 26 +++++++++------- src/oq.cr | 2 +- 5 files changed, 76 insertions(+), 17 deletions(-) diff --git a/shard.yml b/shard.yml index 58eff89..4375bf3 100644 --- a/shard.yml +++ b/shard.yml @@ -3,7 +3,7 @@ name: oq description: | A performant, and portable jq wrapper thats facilitates the consumption and output of formats other than JSON; using jq filters to transform the data. -version: 1.0.1 +version: 1.0.2 authors: - George Dietrich diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 129ebe2..1b0d9f7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: oq -version: '1.0.1' +version: '1.0.2' summary: A performant, and portable jq wrapper to support formats other than JSON description: | A performant, and portable jq wrapper thats facilitates the consumption and output of formats other than JSON; using jq filters to transform the data. diff --git a/spec/converters/xml_spec.cr b/spec/converters/xml_spec.cr index ef0f2c0..986626f 100644 --- a/spec/converters/xml_spec.cr +++ b/spec/converters/xml_spec.cr @@ -141,11 +141,66 @@ XML_ALL_EMPTY = <<-XML + + 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 + describe "conventions" do + describe "an empty element" do + it "self closing" do + run_binary("", args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":null}\n) + end + end + + it "non self closing" do + run_binary("", args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":null}\n) + end + end + end + + it "an element with pure text content" do + run_binary("text", args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":"text"}\n) + end + end + + it "an empty element with attributes" do + run_binary(%(), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":{"@name":"value"}}\n) + end + end + + it "an element with pure text content and attributes" do + run_binary(%(text), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":{"@name":"value","#text":"text"}}\n) + end + end + + it "an element containing elements with different names" do + run_binary(%( text text ), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":{"a":"text","b":"text"}}\n) + end + end + + it "an element containing elements with identical names" do + run_binary(%( text text ), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":{"a":["text","text"]}}\n) + end + end + + it "an element containing elements and contiguous text" do + run_binary(%(texttext), args: ["-i", "xml", "-c", "."]) do |output| + output.should eq %({"e":{"#text":"text","a":"text"}}\n) + end + end + end + describe "should raise if invalid" do it "should output correctly" do run_binary(%(xz), args: ["-i", "xml", "-c", ".root"]) do |output| - output.should eq %({"y":"z"}\n) + output.should eq %({"#text":"x","y":"z"}\n) end end end diff --git a/src/converters/xml.cr b/src/converters/xml.cr index 2e9f4e9..87c74f4 100644 --- a/src/converters/xml.cr +++ b/src/converters/xml.cr @@ -18,19 +18,16 @@ module OQ::Converters::Xml xml.read end - # TODO: clean up after crystal-lang/crystal#8186 is released - if node = xml.expand - process_element_node node, builder - else - raise XML::Error.new LibXML.xmlGetLastError - end + process_element_node xml.expand, builder end end end private def self.process_element_node(node : XML::Node, builder : JSON::Builder) : Nil # If the node doesn't have nested elements nor attributes; just emit a scalar value - return builder.field node.name, get_node_value node if !has_nested_elements(node) && node.attributes.empty? + if !has_nested_elements(node) && node.attributes.empty? + return builder.field node.name, get_node_value node + end # Otherwise process the node as a key/value pair builder.field node.name do @@ -67,7 +64,14 @@ module OQ::Converters::Xml # Determine how to process a node's children node.children.group_by(&.name).each do |name, children| # Skip non significant whitespace; Skip mixed character input - next if children.first.text? && has_nested_elements(node) + if children.first.text? && has_nested_elements(node) + # Only emit text content if there is only one child + if children.size == 1 + builder.field "#text", children.first.content + end + + next + end # Array if children.size > 1 @@ -89,12 +93,12 @@ module OQ::Converters::Xml end private def self.get_node_value(node : XML::Node) : String? - node.children.empty? || node.children.first.content.blank? ? nil : node.children.first.content + node.children.empty? ? nil : node.children.first.content end def self.serialize(input : IO, output : IO, **args) : Nil - json = JSON::PullParser.new(input) - builder = XML::Builder.new(output) + json = JSON::PullParser.new input + builder = XML::Builder.new output indent, prolog, root, xml_item = self.parse_args(args) builder.indent = indent diff --git a/src/oq.cr b/src/oq.cr index 2caf01b..f224212 100644 --- a/src/oq.cr +++ b/src/oq.cr @@ -6,7 +6,7 @@ require "./converters/*" # A performant, and portable jq wrapper thats facilitates the consumption and output of formats other than JSON; using jq filters to transform the data. module OQ - VERSION = "1.0.1" + VERSION = "1.0.2" # The support formats that can be converted to/from. enum Format