chemicals 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +36 -0
- data/Rakefile +10 -0
- data/chemicals.gemspec +24 -0
- data/lib/chemicals.rb +5 -0
- data/lib/chemicals/.DS_Store +0 -0
- data/lib/chemicals/parser.rb +97 -0
- data/lib/chemicals/renderer.rb +72 -0
- data/lib/chemicals/template.rb +94 -0
- data/lib/chemicals/version.rb +3 -0
- data/spec/chemicals/chemicals_spec.rb +201 -0
- data/spec/chemicals/chemicals_spec_helper.rb +14 -0
- data/spec/chemicals/examples.yml +517 -0
- data/spec/chemicals/parser_spec.rb +252 -0
- data/spec/chemicals/renderer_spec.rb +201 -0
- metadata +150 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
## 0.1.2
|
2
|
+
|
3
|
+
* Template option `:symbolize_keys` that allows hash keys to string or symbols. Default to `true`.
|
4
|
+
|
5
|
+
## 0.1.1
|
6
|
+
|
7
|
+
* Performance improvement: Don't parse the source again if it is already a Nokogiri document (Parser).
|
8
|
+
|
9
|
+
## 0.1.0
|
10
|
+
|
11
|
+
* Entirely removed Hashie dependency. All keys are now symbolized in the Parser (bonus: smaller memory footprint).
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Mattias Putman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Chemicals
|
2
|
+
|
3
|
+
## TODO
|
4
|
+
|
5
|
+
* Add simple object conversion (ex. String to Fixnum)
|
6
|
+
* Add shorthand for parsing under same name of element/attribute:
|
7
|
+
`<person ch:as='#' />`
|
8
|
+
* Add support for constant (are not parsed/rendered).
|
9
|
+
* `as` with depth. ex: `ch:as='name/first_name'`
|
10
|
+
* More granular matching of nodes. ex Matching a simgle node within a collection
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'chemicals'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install chemicals
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
TODO: Write usage instructions here
|
29
|
+
|
30
|
+
## Contributing
|
31
|
+
|
32
|
+
1. Fork it
|
33
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
34
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
35
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
36
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/chemicals.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/chemicals/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.authors = ["Mattias Putman"]
|
6
|
+
s.email = ["mattias.putman@gmail.com"]
|
7
|
+
s.description = %q{Clever XML Parsing library}
|
8
|
+
s.summary = %q{Clever XML Parsing library}
|
9
|
+
s.homepage = ""
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split($\)
|
12
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
14
|
+
s.name = "chemicals"
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.version = Chemicals::VERSION
|
17
|
+
|
18
|
+
s.add_dependency 'nokogiri'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rake'
|
21
|
+
s.add_development_dependency 'mocha'
|
22
|
+
s.add_development_dependency 'guard'
|
23
|
+
s.add_development_dependency 'guard-minitest'
|
24
|
+
end
|
data/lib/chemicals.rb
ADDED
Binary file
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Chemicals
|
2
|
+
class Parser
|
3
|
+
def initialize template
|
4
|
+
@template = template
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse source
|
8
|
+
@namespaces = {}
|
9
|
+
# get the document in nokogiri without blanks
|
10
|
+
# or if the source is already a nokogiri node, then assume it is without blanks.
|
11
|
+
root = if source.kind_of? Nokogiri::XML::Node
|
12
|
+
source.document.root
|
13
|
+
else
|
14
|
+
Nokogiri::XML(source.to_s) { |c| c.noblanks }.root
|
15
|
+
end
|
16
|
+
# delete all default namespaces and map the new ones in @namespaces
|
17
|
+
handle_namespace root
|
18
|
+
# map to a general namespace prefix => href hash
|
19
|
+
@namespaces = Hash[(@namespaces.values + root.namespace_definitions).map { |ns|
|
20
|
+
[ns.prefix, ns.href] if ns.prefix
|
21
|
+
}]
|
22
|
+
# begin parsing with the root node
|
23
|
+
parse_node root, @template.for(root.path, @namespaces)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def parse_node source, config
|
29
|
+
return nil unless config
|
30
|
+
# parse all child nodes and attribute nodes
|
31
|
+
parsed = (source.children.to_a + source.attribute_nodes).map do |node|
|
32
|
+
# parse node with the correspondent config (using node xpath)
|
33
|
+
parse_node(node, @template.for(node.path, @namespaces)) || {}
|
34
|
+
end
|
35
|
+
# reject nil values
|
36
|
+
parsed.reject! { |key, value| !value } if parsed.kind_of? Hash
|
37
|
+
# in arrays reject empty hashes
|
38
|
+
parsed.reject! { |value| value.empty? } if parsed.kind_of? Array
|
39
|
+
# we have a few cases here
|
40
|
+
parsed = case source
|
41
|
+
when Nokogiri::XML::Text
|
42
|
+
source.content
|
43
|
+
when Nokogiri::XML::Attr
|
44
|
+
source.content
|
45
|
+
when Nokogiri::XML::Element
|
46
|
+
# an array of arrays is flattened
|
47
|
+
if parsed.kind_of?(Array) && parsed.all? { |part| part.kind_of? Array }
|
48
|
+
parsed.flatten
|
49
|
+
# everything else is merged
|
50
|
+
else
|
51
|
+
parsed.inject { |result, part|
|
52
|
+
# internal arrays are added
|
53
|
+
result.merge!(part) { |key, old_value, new_value|
|
54
|
+
# This way mixed collections come together.
|
55
|
+
old_value + new_value
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# If nothing got parsed but we expect a hash then make it a hash!
|
61
|
+
if parsed.empty? && config[:mode] == :merge
|
62
|
+
parsed = {}
|
63
|
+
end
|
64
|
+
# wrap in array if collecting
|
65
|
+
parsed = [parsed] if config[:mode] == :collect
|
66
|
+
# wrap in alias
|
67
|
+
parsed = {config[:as] => parsed} if config.has_key?(:as) && !config[:as].nil?
|
68
|
+
# return parsed version and reject all nil values!
|
69
|
+
parsed
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_namespace node
|
73
|
+
if node.namespace
|
74
|
+
# a namespace without a prefix is a default namespace
|
75
|
+
if !node.namespace.prefix
|
76
|
+
# not mapped this namespace?
|
77
|
+
unless @namespaces.has_key? node.namespace.href
|
78
|
+
# define the new namespace
|
79
|
+
node.document.root.add_namespace_definition "ns#{@namespaces.size}",
|
80
|
+
node.namespace.href
|
81
|
+
# add a mapping
|
82
|
+
@namespaces[node.namespace.href] = node.document.root.namespace_definitions.find { |ns|
|
83
|
+
ns.prefix && ns.href == node.namespace.href
|
84
|
+
}
|
85
|
+
end
|
86
|
+
# change the namespace
|
87
|
+
node.namespace = @namespaces[node.namespace.href]
|
88
|
+
# namespace with prefix
|
89
|
+
else
|
90
|
+
@namespaces[node.namespace.href] ||= node.namespace
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# do the same for all child elements and attributes
|
94
|
+
(node.children.to_a + node.attribute_nodes).each { |child| handle_namespace child }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Chemicals
|
2
|
+
class Renderer
|
3
|
+
|
4
|
+
def initialize template
|
5
|
+
@template = template
|
6
|
+
end
|
7
|
+
|
8
|
+
def render source
|
9
|
+
# begin with a new document
|
10
|
+
document = Nokogiri::XML::Document.new
|
11
|
+
# add a temp node as a namespace referebce
|
12
|
+
document.root = Nokogiri::XML::Element.new 'temp', document
|
13
|
+
@template.add_namespaces document.root
|
14
|
+
# now render the entire document as the new root
|
15
|
+
document.root = render_node document, source, @template.raw.root
|
16
|
+
# again add all required namespaces to the real root
|
17
|
+
@template.add_namespaces document.root
|
18
|
+
# return the root
|
19
|
+
document.root
|
20
|
+
end
|
21
|
+
|
22
|
+
def render_node document, source, template, collect = true
|
23
|
+
# the template is already the template to apply this source on
|
24
|
+
# do nothing if there's no source
|
25
|
+
return nil unless source && config = @template.for(template.path)
|
26
|
+
# don't render the chemicals namespace
|
27
|
+
return nil if template.namespace && template.namespace.href == 'http://piesync.com/xml/chemicals'
|
28
|
+
# unwrap if necessary
|
29
|
+
source = source[config[:as].to_sym] if config.has_key?(:as) && !config[:as].nil? && collect
|
30
|
+
# is this a collect node? Rendering a collect node is the same as rendering the
|
31
|
+
# template for each part of the source (and render them as a non-collect node)
|
32
|
+
# The result are the rendered nodes.
|
33
|
+
if collect && config[:mode] == :collect
|
34
|
+
return source.map { |part| render_node document, part, template, false } if source
|
35
|
+
end
|
36
|
+
# render all children
|
37
|
+
rendered = (template.children.to_a + template.attribute_nodes).map do |child|
|
38
|
+
render_node document, source, child, true if source
|
39
|
+
end.flatten
|
40
|
+
# reject all nil values
|
41
|
+
rendered.reject! { |value| !value }
|
42
|
+
# we again have a few cases how to render
|
43
|
+
case template
|
44
|
+
when Nokogiri::XML::Attr
|
45
|
+
return unless source
|
46
|
+
node = Nokogiri::XML::Attr.new document, template.name
|
47
|
+
node.content = source
|
48
|
+
when Nokogiri::XML::Text
|
49
|
+
node = Nokogiri::XML::Text.new source, document if source
|
50
|
+
when Nokogiri::XML::Element
|
51
|
+
# don't render an element if it has no data in its children
|
52
|
+
return unless !rendered.empty?
|
53
|
+
node = Nokogiri::XML::Element.new template.name, document
|
54
|
+
rendered.each do |child|
|
55
|
+
# manually add attributes cause otherwise resulting in a segfault
|
56
|
+
if child.kind_of? Nokogiri::XML::Attr
|
57
|
+
node.set_attribute child.name, child.value
|
58
|
+
node.attribute(child.name).namespace = child.namespace
|
59
|
+
# add the non-attibutes
|
60
|
+
else
|
61
|
+
node.add_child child
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
# Add the correct namespaces!
|
66
|
+
node.namespace = document.root.namespace_definitions.find { |ns|
|
67
|
+
ns.href == template.namespace.href
|
68
|
+
} if node && !template.namespace.nil?
|
69
|
+
node
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Chemicals
|
2
|
+
class Template
|
3
|
+
|
4
|
+
NS = 'http://piesync.com/xml/chemicals'
|
5
|
+
|
6
|
+
def initialize(template, options = {})
|
7
|
+
@template = Nokogiri::XML(template) { |c| c.noblanks }
|
8
|
+
@options = {
|
9
|
+
symbolize_keys: true
|
10
|
+
}.merge!(options)
|
11
|
+
@cache = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# returns the raw template
|
15
|
+
def raw
|
16
|
+
@template
|
17
|
+
end
|
18
|
+
|
19
|
+
# get the configuration for an xpath expression
|
20
|
+
def for path, namespaces = nil
|
21
|
+
@cache[path] ||= if namespaces
|
22
|
+
for_node @template.at(canonicalize(path), namespaces)
|
23
|
+
else
|
24
|
+
for_node @template.at(canonicalize(path))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# get the configuration for a config node
|
29
|
+
def for_node config_node
|
30
|
+
# nil if no config node
|
31
|
+
return nil unless config_node
|
32
|
+
return nil if @template.at(canonicalize(config_node.path)).nil?
|
33
|
+
# configuation is different in every case
|
34
|
+
config = case config_node
|
35
|
+
when Nokogiri::XML::Text
|
36
|
+
config_node.content == '@' ? {} : {
|
37
|
+
as: @options[:symbolize_keys] ? config_node.content.to_sym : config_node.content
|
38
|
+
}
|
39
|
+
when Nokogiri::XML::Attr
|
40
|
+
{
|
41
|
+
as: @options[:symbolize_keys] ? config_node.value.to_sym : config_node.value
|
42
|
+
}
|
43
|
+
when Nokogiri::XML::Element
|
44
|
+
as = attribute(config_node, :as)
|
45
|
+
{
|
46
|
+
as: as ? (@options[:symbolize_keys] ? as.to_sym : as) : nil,
|
47
|
+
mode: mode(config_node, as)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def mode config_node, as
|
53
|
+
mode = attribute(config_node, :mode)
|
54
|
+
if mode
|
55
|
+
mode.to_sym
|
56
|
+
elsif as # only merge if we parse this node.
|
57
|
+
:merge
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# add the required namespaces to a node
|
64
|
+
def add_namespaces node
|
65
|
+
raw.root.namespace_definitions.each do |ns|
|
66
|
+
node.add_namespace_definition ns.prefix, ns.href if ns.href != NS
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# parse a document
|
71
|
+
def parse source
|
72
|
+
@parser ||= Parser.new self
|
73
|
+
@parser.parse source
|
74
|
+
end
|
75
|
+
|
76
|
+
# render a hash
|
77
|
+
def render source
|
78
|
+
@renderer ||= Renderer.new self
|
79
|
+
@renderer.render source
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def attribute node, name
|
85
|
+
node.attribute_with_ns(name.to_s, NS).value if node.namespaced_key?(name.to_s, NS)
|
86
|
+
end
|
87
|
+
|
88
|
+
# canonicalize an xpath expression
|
89
|
+
def canonicalize xpath
|
90
|
+
# this means removing indexes
|
91
|
+
xpath.gsub(/\[[^\]]*\]/, '')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'minitest/spec'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
require 'chemicals'
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
describe Chemicals::Renderer do
|
13
|
+
describe 'rendering a text node' do
|
14
|
+
it 'should render the element value directly in the text node when the text node is aliased as @' do
|
15
|
+
template, raw = ChemicalsSpecHelper.test_example :simple_text
|
16
|
+
template.render('John Doe').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should render the content unwrapped from its alias' do
|
20
|
+
template, raw = ChemicalsSpecHelper.test_example :simple_text_alias
|
21
|
+
template.render(name: 'John Doe').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'rendering an attribute' do
|
26
|
+
it 'should render attributes with aliases' do
|
27
|
+
template, raw = ChemicalsSpecHelper.test_example :simple_attributes
|
28
|
+
template.render(full_name: 'John Doe').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'rendering an element' do
|
33
|
+
it 'should unwrap contents in aliased elements' do
|
34
|
+
template, raw = ChemicalsSpecHelper.test_example :simple_element_alias
|
35
|
+
template.render(individual: 'John Doe').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should skip an element when no alias is provided' do
|
39
|
+
template, raw = ChemicalsSpecHelper.test_example :skip_element
|
40
|
+
template.render(individual: 'John Doe').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'rendering a collection of elements' do
|
45
|
+
it 'should render each element' do
|
46
|
+
template, raw = ChemicalsSpecHelper.test_example :simple_collection
|
47
|
+
template.render(emails: [
|
48
|
+
'john.doe@gmail.com',
|
49
|
+
'john@acme.com'
|
50
|
+
]).to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should render multiple collections independently' do
|
54
|
+
template, raw = ChemicalsSpecHelper.test_example :multiple_collections
|
55
|
+
template.render(
|
56
|
+
contact: {
|
57
|
+
emails: [
|
58
|
+
'john.doe@gmail.com',
|
59
|
+
'john@acme.com'
|
60
|
+
],
|
61
|
+
phone_numbers: ['1', '2']
|
62
|
+
}).to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should be able to render collections in the presence of other elements' do
|
66
|
+
template, raw = ChemicalsSpecHelper.test_example :mixed_collection
|
67
|
+
template.render( \
|
68
|
+
contact: {
|
69
|
+
emails: [
|
70
|
+
'john.doe@gmail.com',
|
71
|
+
'john@acme.com'
|
72
|
+
],
|
73
|
+
name: 'John Doe'
|
74
|
+
}).to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should be able to render multiple collections in the presence of other elements' do
|
78
|
+
template, raw = ChemicalsSpecHelper.test_example :mixed_collections
|
79
|
+
template.render(
|
80
|
+
contact: {
|
81
|
+
emails: [
|
82
|
+
{ address: 'john.doe@gmail.com', label: 'work' },
|
83
|
+
{ address: 'john@acme.com' }
|
84
|
+
],
|
85
|
+
phones: ['1'],
|
86
|
+
name: 'John Doe'
|
87
|
+
}).to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should be able to extreme parse multiple collections in the presence of other elements' do
|
91
|
+
template, raw = ChemicalsSpecHelper.test_example :mixed_collections_extreme
|
92
|
+
template.render(
|
93
|
+
[{
|
94
|
+
emails: [
|
95
|
+
{ address: 'john.doe@gmail.com', label: 'work' },
|
96
|
+
{ address:'john@acme.com' }
|
97
|
+
],
|
98
|
+
phones: ['1'],
|
99
|
+
name: 'John Doe'
|
100
|
+
},
|
101
|
+
{
|
102
|
+
phones: ['1', '2', '3'],
|
103
|
+
addresses: [{ country: 'Belgium', country_code: 'BE',
|
104
|
+
street: 'Désiré Van Monckhovenstraat', housenumber: '123' }]
|
105
|
+
}]).to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should render mixed elements, collections and text nodes' do
|
110
|
+
template, raw, raw_render = ChemicalsSpecHelper.test_example :mixed_elements_text
|
111
|
+
template.render(
|
112
|
+
[{
|
113
|
+
name: { given: 'John', family: 'Doe' },
|
114
|
+
emails: ['john.doe@gmail.com', 'john@acme.com'],
|
115
|
+
phones: [
|
116
|
+
{ country: 'Belgium', number: '1' },
|
117
|
+
{ country: 'USA', number: '2' }
|
118
|
+
]
|
119
|
+
},
|
120
|
+
{
|
121
|
+
name: { given: 'Jane' },
|
122
|
+
emails: [ 'jane.doe@gmail.com' ]
|
123
|
+
}
|
124
|
+
]).to_xml.must_equal ChemicalsSpecHelper.format(raw_render)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should render mixed elements, collections, text nodes, attributes and ignore useless nodes' do
|
128
|
+
template, raw, raw_render = ChemicalsSpecHelper.test_example :mixed_elements_text_attributes
|
129
|
+
template.render([
|
130
|
+
{
|
131
|
+
name: { given: 'John', family: 'Doe' },
|
132
|
+
emails: [
|
133
|
+
{ label: 'home', address: 'john.doe@gmail.com' },
|
134
|
+
{ address: 'john@acme.com' }
|
135
|
+
],
|
136
|
+
phones: [
|
137
|
+
{ country: 'Belgium', number: '1', system: 'sap' },
|
138
|
+
{ country: 'USA', number: '2' }
|
139
|
+
]
|
140
|
+
},
|
141
|
+
{
|
142
|
+
name: { given: 'Jane' },
|
143
|
+
emails: [{ label: 'work', address: 'jane.doe@gmail.com' }]
|
144
|
+
}
|
145
|
+
]).to_xml.must_equal ChemicalsSpecHelper.format(raw_render)
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'rendering attributes' do
|
149
|
+
it 'should render attributes with aliases' do
|
150
|
+
template, raw = ChemicalsSpecHelper.test_example :simple_attributes
|
151
|
+
template.render(full_name: 'John Doe').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should not render attributes not mentioned in the template' do
|
156
|
+
template, raw = ChemicalsSpecHelper.test_example :ignore_attribute
|
157
|
+
template.render({}).must_equal nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should mix attributes and aliased text nodes' do
|
161
|
+
template, raw = ChemicalsSpecHelper.test_example :text_attributes
|
162
|
+
template.render(age: '24', name: 'John Doe').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should be able to render chinese characters' do
|
168
|
+
template, raw = ChemicalsSpecHelper.test_example :chinese
|
169
|
+
template.render('你好世界').to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should be able to render namespaces' do
|
173
|
+
template, raw = ChemicalsSpecHelper.test_example :namespaces
|
174
|
+
template.render(
|
175
|
+
name: { age: '24', first_name: 'John', last_name: 'Doe' }).to_xml.must_equal ChemicalsSpecHelper.format(raw)
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should be reasonably fast' do
|
180
|
+
template, raw = ChemicalsSpecHelper.test_example :mixed_elements_text
|
181
|
+
1000.times do
|
182
|
+
template.render(
|
183
|
+
[{
|
184
|
+
name: { given: 'John', family: 'Doe' },
|
185
|
+
emails: ['john.doe@gmail.com', 'john@acme.com'],
|
186
|
+
phones: [
|
187
|
+
{ country: 'Belgium', number: '1' },
|
188
|
+
{ country: 'USA', number: '2' }
|
189
|
+
]
|
190
|
+
},
|
191
|
+
{
|
192
|
+
name: { given: 'Jane' },
|
193
|
+
emails: [ 'jane.doe@gmail.com' ]
|
194
|
+
}
|
195
|
+
])
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
__END__
|
201
|
+
|