Skip to content

Commit

Permalink
Merge 32bfcb3 into e3b7d06
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeiwan committed Dec 9, 2015
2 parents e3b7d06 + 32bfcb3 commit 6638202
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 10 deletions.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,63 @@ The example above shows an example of how you can use all three at the same time

Notice that we have the attribute "lang" defined twice.
The `@lang` value takes precedence over the `:attribute![:subtitle]["lang"]` value.

## Pretty Print

You can prettify the output XML to make it more readable. Use these options:
* `pretty_print` – controls pretty mode (default: `false`)
* `indent` – specifies indentation in spaces (default: `2`)
* `compact` – controls compact mode (default: `true`)

**This feature is not available for XML documents generated from arrays with unwrap option set to false as such documents are not valid**

**Examples**

``` ruby
puts Gyoku.xml({user: { name: 'John', job: { title: 'Programmer' }, :@status => 'active' }}, pretty_print: true)
#<user status='active'>
# <name>John</name>
# <job>
# <title>Programmer</title>
# </job>
#</user>
```

``` ruby
puts Gyoku.xml({user: { name: 'John', job: { title: 'Programmer' }, :@status => 'active' }}, pretty_print: true, indent: 4)
#<user status='active'>
# <name>John</name>
# <job>
# <title>Programmer</title>
# </job>
#</user>
```

``` ruby
puts Gyoku.xml({user: { name: 'John', job: { title: 'Programmer' }, :@status => 'active' }}, pretty_print: true, compact: false)
#<user status='active'>
# <name>
# John
# </name>
# <job>
# <title>
# Programmer
# </title>
# </job>
#</user>
```

**Generate XML from an array with `unwrap` option set to `true`**
``` ruby
puts Gyoku::Array.to_xml(["john", "jane"], "user", true, {}, pretty_print: true, unwrap: true)
#<user>
# <user>john</user>
# <user>jane</user>
#</user>
```

**Generate XML from an array with `unwrap` option unset (`false` by default)**
``` ruby
puts Gyoku::Array.to_xml(["john", "jane"], "user", true, {}, pretty_print: true)
#<user>john</user><user>jane</user>
```
22 changes: 17 additions & 5 deletions lib/gyoku/array.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "builder"

require "gyoku/prettifier.rb"
require "gyoku/hash"
require "gyoku/xml_value"

Expand All @@ -8,9 +9,22 @@ class Array

NESTED_ELEMENT_NAME = "element"

# Builds XML and prettifies it if +pretty_print+ option is set to +true+
def self.to_xml(array, key, escape_xml = true, attributes = {}, options = {})
xml = build_xml(array, key, escape_xml, attributes, options)

if options[:pretty_print] && options[:unwrap]
Prettifier.prettify(xml, options)
else
xml
end
end

private

# Translates a given +array+ to XML. Accepts the XML +key+ to add the elements to,
# whether to +escape_xml+ and an optional Hash of +attributes+.
def self.to_xml(array, key, escape_xml = true, attributes = {}, options = {})
def self.build_xml(array, key, escape_xml = true, attributes = {}, options = {})

self_closing = options.delete(:self_closing)
unwrap = options[:unwrap] || false
Expand All @@ -24,10 +38,10 @@ def self.to_xml(array, key, escape_xml = true, attributes = {}, options = {})
if unwrap
xml << Hash.to_xml(item, options)
else
xml.tag!(key, attrs) { xml << Hash.to_xml(item, options) }
xml.tag!(key, attrs) { xml << Hash.build_xml(item, options) }
end
when ::Array then
xml.tag!(key, attrs) { xml << Array.to_xml(item, NESTED_ELEMENT_NAME) }
xml.tag!(key, attrs) { xml << Array.build_xml(item, NESTED_ELEMENT_NAME) }
when NilClass then
xml.tag!(key, "xsi:nil" => "true")
else
Expand All @@ -37,8 +51,6 @@ def self.to_xml(array, key, escape_xml = true, attributes = {}, options = {})
end
end

private

# Iterates over a given +array+ with a Hash of +attributes+ and yields a builder +xml+
# instance, the current +item+, any XML +attributes+ and the current +index+.
def self.iterate_with_xml(array, key, attributes, options, &block)
Expand Down
22 changes: 17 additions & 5 deletions lib/gyoku/hash.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
require "builder"

require "gyoku/prettifier.rb"
require "gyoku/array"
require "gyoku/xml_key"
require "gyoku/xml_value"

module Gyoku
class Hash

# Translates a given +hash+ with +options+ to XML.
# Builds XML and prettifies it if +pretty_print+ option is set to +true+
def self.to_xml(hash, options = {})
xml = build_xml(hash, options)

if options[:pretty_print]
Prettifier.prettify(xml, options)
else
xml
end
end

private

# Translates a given +hash+ with +options+ to XML.
def self.build_xml(hash, options = {})
iterate_with_xml hash do |xml, key, value, attributes|
self_closing = key.to_s[-1, 1] == "/"
escape_xml = key.to_s[-1, 1] != "!"
xml_key = XMLKey.create key, options

case
when :content! === key then xml << XMLValue.create(value, escape_xml, options)
when ::Array === value then xml << Array.to_xml(value, xml_key, escape_xml, attributes, options.merge(:self_closing => self_closing))
when ::Hash === value then xml.tag!(xml_key, attributes) { xml << Hash.to_xml(value, options) }
when ::Array === value then xml << Array.build_xml(value, xml_key, escape_xml, attributes, options.merge(:self_closing => self_closing))
when ::Hash === value then xml.tag!(xml_key, attributes) { xml << build_xml(value, options) }
when self_closing then xml.tag!(xml_key, attributes)
when NilClass === value then xml.tag!(xml_key, "xsi:nil" => "true")
else xml.tag!(xml_key, attributes) { xml << XMLValue.create(value, escape_xml, options) }
end
end
end

private

# Iterates over a given +hash+ and yields a builder +xml+ instance, the current
# Hash +key+ and any XML +attributes+.
#
Expand Down
29 changes: 29 additions & 0 deletions lib/gyoku/prettifier.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'rexml/document'

module Gyoku
class Prettifier
DEFAULT_INDENT = 2
DEFAULT_COMPACT = true

attr_accessor :indent, :compact

def self.prettify(xml, options = {})
new(options).prettify(xml)
end

def initialize(options = {})
@indent = options[:indent] || DEFAULT_INDENT
@compact = options[:compact].nil? ? DEFAULT_COMPACT : options[:compact]
end

# Adds intendations and newlines to +xml+ to make it more readable
def prettify(xml)
result = ''
formatter = REXML::Formatters::Pretty.new indent
formatter.compact = compact
doc = REXML::Document.new xml
formatter.write doc, result
result
end
end
end
38 changes: 38 additions & 0 deletions spec/gyoku/array_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,44 @@

expect(to_xml(array, "value")).to eq(result)
end

context "when :pretty_print option is set to true" do
context "when :unwrap option is set to true" do
it "returns prettified xml" do
array = ["one", "two", {"three" => "four"}]
options = { pretty_print: true, unwrap: true }
result = "<test>\n <test>one</test>\n <test>two</test>\n <three>four</three>\n</test>"
expect(to_xml(array, "test", true, {}, options)).to eq(result)
end

context "when :indent option is specified" do
it "returns prettified xml with specified indent" do
array = ["one", "two", {"three" => "four"}]
options = { pretty_print: true, indent: 3, unwrap: true }
result = "<test>\n <test>one</test>\n <test>two</test>\n <three>four</three>\n</test>"
expect(to_xml(array, "test", true, {}, options)).to eq(result)
end
end

context "when :compact option is specified" do
it "returns prettified xml with specified compact mode" do
array = ["one", {"two" => "three"}]
options = { pretty_print: true, compact: false, unwrap: true }
result = "<test>\n <test>\n one\n </test>\n <two>\n three \n </two>\n</test>"
expect(to_xml(array, "test", true, {}, options)).to eq(result)
end
end
end

context "when :unwrap option is not set" do
it "returns non-prettified xml" do
array = ["one", "two", {"three" => "four"}]
options = { pretty_print: true }
result = "<test>one</test><test>two</test><test><three>four</three></test>"
expect(to_xml(array, "test", true, {}, options)).to eq(result)
end
end
end
end

def to_xml(*args)
Expand Down
27 changes: 27 additions & 0 deletions spec/gyoku/hash_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@
expect(to_xml(:some => [{ :new => "user" }, { :old => "gorilla" }])).
to eq("<some><new>user</new></some><some><old>gorilla</old></some>")
end

context "when :pretty_print option is set to true" do
it "returns prettified xml" do
hash = { some: { user: { name: "John", groups: ["admin", "editor"] } } }
options = { pretty_print: true }
result = "<some>\n <user>\n <name>John</name>\n <groups>admin</groups>\n <groups>editor</groups>\n </user>\n</some>"
expect(to_xml(hash, options)).to eq(result)
end

context "when :indent option is specified" do
it "returns prettified xml with specified indent" do
hash = { some: { user: { name: "John" } } }
options = { pretty_print: true, indent: 4 }
result = "<some>\n <user>\n <name>John</name>\n </user>\n</some>"
expect(to_xml(hash, options)).to eq(result)
end
end

context "when :compact option is specified" do
it "returns prettified xml with specified compact mode" do
hash = { some: { user: { name: "John" } } }
options = { pretty_print: true, compact: false }
result = "<some>\n <user>\n <name>\n John\n </name>\n </user>\n</some>"
expect(to_xml(hash, options)).to eq(result)
end
end
end
end

it "converts Hash key Symbols to lowerCamelCase" do
Expand Down
39 changes: 39 additions & 0 deletions spec/gyoku/prettifier_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require "spec_helper"

describe Gyoku::Prettifier do
describe "#prettify" do
context "when xml is valid" do
let!(:xml) { Gyoku::Hash.build_xml(test: { pretty: "xml" }) }

it "returns prettified xml" do
expect(subject.prettify(xml)).to eql("<test>\n <pretty>xml</pretty>\n</test>")
end

context "when indent option is specified" do
it "returns prettified xml with indent" do
options = { indent: 3 }
subject = Gyoku::Prettifier.new(options)
expect(subject.prettify(xml)).to eql("<test>\n <pretty>xml</pretty>\n</test>")
end
end

context "when compact option is specified" do
it "returns prettified xml with indent" do
options = { compact: false }
subject = Gyoku::Prettifier.new(options)
expect(subject.prettify(xml)).to eql("<test>\n <pretty>\n xml\n </pretty>\n</test>")
end
end
end

context "when xml is not valid" do
let!(:xml) do
Gyoku::Array.build_xml(["one", "two"], "test")
end

it "raises an error" do
expect{ subject.prettify(xml) }.to raise_error REXML::ParseException
end
end
end
end

0 comments on commit 6638202

Please sign in to comment.