Skip to content

Commit

Permalink
Additional XML improvements (#93)
Browse files Browse the repository at this point in the history
* Add ability to force an element to be parsed as an array
* Rename XML alias CLI option
  • Loading branch information
Blacksmoke16 authored Aug 15, 2021
1 parent 166c954 commit 40d11ed
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 15 deletions.
52 changes: 43 additions & 9 deletions spec/converters/xml_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,9 @@ describe OQ::Converters::XML do
end
end

describe "with --namespace-alias" do
describe "with --xml-namespace-alias" do
it "should error" do
run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="https://a-namespace">bar</a:foo>), args: ["-i", "xml", "-c", "--namespace-alias", "aa=https://a-namespace", "."], success: false) do |_, _, error|
run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="https://a-namespace">bar</a:foo>), args: ["-i", "xml", "-c", "--xml-namespace-alias", "aa=https://a-namespace", "."], success: false) do |_, _, error|
error.should start_with "oq error:"
end
end
Expand Down Expand Up @@ -397,21 +397,21 @@ describe OQ::Converters::XML do
end
end

describe "with --namespace-alias" do
describe "with --xml-namespace-alias" do
it "normalizes the provided namespace" do
run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="https://a-namespace">bar</a:foo>), args: ["-i", "xml", "-c", "--xmlns", "--namespace-alias", "aa=https://a-namespace", "."]) do |output|
run_binary(%(<?xml version="1.0"?><a:foo xmlns:a="https://a-namespace">bar</a:foo>), args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "aa=https://a-namespace", "."]) do |output|
output.should eq %({"aa:foo":{"@xmlns:aa":"https://a-namespace","#text":"bar"}}\n)
end
end

it "normalizes the default namespace" do
run_binary(%(<?xml version="1.0"?><foo xmlns="https://a-namespace">bar</foo>), args: ["-i", "xml", "-c", "--xmlns", "--namespace-alias", "aa=https://a-namespace", "."]) do |output|
run_binary(%(<?xml version="1.0"?><foo xmlns="https://a-namespace">bar</foo>), args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "aa=https://a-namespace", "."]) do |output|
output.should eq %({"aa:foo":{"@xmlns:aa":"https://a-namespace","#text":"bar"}}\n)
end
end

it "normalizes multiple namespaces" do
run_binary(XML_NESTED_NAMESPACES, args: ["-i", "xml", "-c", "--xmlns", "--namespace-alias", "=https://a", "--namespace-alias", "bb=https://b", "."]) do |output|
run_binary(XML_NESTED_NAMESPACES, args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "=https://a", "--xml-namespace-alias", "bb=https://b", "."]) do |output|
output.should eq %({"bb:root":{"@xmlns":"https://a","@xmlns:bb":"https://b","foo":"herp","bb:foo":{"bar":{"@xmlns":"https://c","baz":{"@xmlns":"https://d"}}}}}\n)
end
end
Expand Down Expand Up @@ -503,21 +503,55 @@ describe OQ::Converters::XML do
end
end

describe "with --namespace-alias" do
describe "with --xml-namespace-alias" do
it do
run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "--xmlns", "--namespace-alias", "num=http://n", "."]) do |output|
run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "num=http://n", "."]) do |output|
output.should eq %({"items":{"@xmlns:num":"http://n","num:number":["1","2"],"number":{"@xmlns":"http://default","#text":"3"}}}\n)
end
end

it do
run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "--xmlns", "--namespace-alias", "=http://n", "--namespace-alias", "d=http://default", "."]) do |output|
run_binary(XML_NAMESPACE_ARRAY, args: ["-i", "xml", "-c", "--xmlns", "--xml-namespace-alias", "=http://n", "--xml-namespace-alias", "d=http://default", "."]) do |output|
output.should eq %({"items":{"@xmlns":"http://n","number":["1","2"],"d:number":{"@xmlns:d":"http://default","#text":"3"}}}\n)
end
end
end
end
end

describe "with a single element" do
it "without --xml-force-array" do
run_binary(%(<foo><item/></foo>), args: ["-i", "xml", "-c", "."]) do |output|
output.should eq %({"foo":{"item":null}}\n)
end
end

describe "with --xml-force-array" do
it "force parses it as an array" do
run_binary(%(<foo><item/></foo>), args: ["-i", "xml", "--xml-force-array", "item", "-c", "."]) do |output|
output.should eq %({"foo":{"item":[null]}}\n)
end
end

it "with an attribute" do
run_binary(%(<foo><item id="1"/></foo>), args: ["-i", "xml", "--xml-force-array", "item", "-c", "."]) do |output|
output.should eq %({"foo":{"item":[{"@id":"1"}]}}\n)
end
end

it "with a namespace" do
run_binary(%(<foo><item xmlns="https://ns"/></foo>), args: ["-i", "xml", "--xmlns", "--xml-force-array", "item", "-c", "."]) do |output|
output.should eq %({"foo":{"item":[{"@xmlns":"https://ns"}]}}\n)
end
end

it "with an aliased namespace" do
run_binary(%(<foo><i:item xmlns:i="https://ns"/></foo>), args: ["-i", "xml", "--xmlns", "--xml-force-array", "item:item", "--xml-namespace-alias", "item=https://ns", "-c", "."]) do |output|
output.should eq %({"foo":{"item:item":[{"@xmlns:item":"https://ns"}]}}\n)
end
end
end
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/converters/xml.cr
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ module OQ::Converters::XML
end

# Array
if children.size > 1
if children.size > 1 || self.processor.xml_forced_arrays.includes? name
process_array_node name, children, builder
else
if children.first.text?
Expand Down
15 changes: 11 additions & 4 deletions src/oq.cr
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,15 @@ module OQ
# Mapping to namespace aliases to their related namespace.
protected getter xml_namespaces = Hash(String, String).new

# Set of elements who should be force expanded to an array.
protected getter xml_forced_arrays = Set(String).new

# The args that'll be passed to `jq`.
@args : Array(String) = [] of String

# Keep a reference to the created temp files in order to delete them later.
@tmp_files = Set(File).new

def initialize(
@input_format : Format = Format::JSON,
@output_format : Format = Format::JSON,
Expand Down Expand Up @@ -124,8 +130,9 @@ module OQ
@xml_namespaces[href] = prefix
end

# Keep a reference to the created temp files in order to delete them later.
@tmp_files = Set(File).new
def add_forced_array(name : String) : Nil
xml_forced_arrays << name
end

# Consumes `#input_format` data from the provided *input* `IO`, along with any *input_args*.
# The data is then converted to `JSON`, passed to `jq`, and then converted to `#output_format` while being written to the *output* `IO`.
Expand All @@ -146,9 +153,9 @@ module OQ
# Extract `jq` arguments from `ARGV`.
self.extract_args input_args, output

# The --namespace-alias option must be used with the --xmlns option.
# The --xml-namespace-alias option must be used with the --xmlns option.
# TODO: Remove this in oq 2.x
raise ArgumentError.new "The `--namespace-alias` option must be used with the `--xmlns` option." if !@xmlns && !@xml_namespaces.empty?
raise ArgumentError.new "The `--xml-namespace-alias` option must be used with the `--xmlns` option." if !@xmlns && !@xml_namespaces.empty?

# Replace the *input* with a fake `ARGF` `IO` to handle both file and `IO` inputs
# in case `ARGV` is not being used for the input arguments.
Expand Down
3 changes: 2 additions & 1 deletion src/oq_cli.cr
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ OptionParser.parse do |parser|
parser.on("--no-prolog", "Whether the XML prolog should be emitted if converting to XML.") { processor.xml_prolog = false }
parser.on("--xml-item NAME", "The name for XML array elements without keys.") { |i| processor.xml_item = i }
parser.on("--xmlns", "If XML namespaces should be parsed. NOTE: This will become the default in oq 2.x.") { processor.xmlns = true }
parser.on("--namespace-alias NS", "Value should be in the form of: `key=namespace`. Elements within the provided namespace are normalized to the provided key. NOTE: Requires the `--xmlns` option to be passed as well.") { |ns| k, v = ns.split('=', 2); processor.add_xml_namespace k, v }
parser.on("--xml-force-array NAME", "Forces an element with the provided name to be parsed as an array even if it only contains one item.") { |n| processor.add_forced_array n }
parser.on("--xml-namespace-alias ALIAS", "Value should be in the form of: `key=namespace`. Elements within the provided namespace are normalized to the provided key. NOTE: Requires the `--xmlns` option to be passed as well.") { |a| k, v = a.split('=', 2); processor.add_xml_namespace k, v }
parser.on("--xml-root ROOT", "Name of the root XML element if converting to XML.") { |r| processor.xml_root = r }
parser.invalid_option { }
end
Expand Down

0 comments on commit 40d11ed

Please sign in to comment.