nom-xml 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +3 -3
- data/lib/nom/xml/decorators/nodeset.rb +29 -1
- data/lib/nom/xml/decorators/terminology.rb +7 -26
- data/lib/nom/xml/nokogiri_extension.rb +3 -0
- data/lib/nom/xml/template_registry.rb +35 -58
- data/lib/nom/xml/term.rb +12 -1
- data/lib/nom/xml/version.rb +1 -1
- data/{nom.gemspec → nom-xml.gemspec} +6 -5
- data/spec/lib/nodeset_decorator_spec.rb +73 -0
- data/spec/lib/template_registry_spec.rb +215 -0
- data/spec/lib/terminology_decorator_spec.rb +11 -95
- data/spec/spec_helper.rb +1 -0
- data/spec/test_spec.rb +15 -0
- metadata +29 -9
data/README.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
#
|
1
|
+
# nom-xml
|
2
2
|
|
3
3
|
[](http://travis-ci.org/cbeer/nom)
|
4
4
|
|
5
5
|
A library to help you tame sprawling XML schemas.
|
6
6
|
|
7
|
-
|
7
|
+
nom-xml allows you to define a “terminology” to ease translation between XML and ruby objects – you can query the xml for Nodes or node values without ever writing a line of XPath. nom-xml is built on top of [nokogiri](http://nokogiri.org) decorators, which means you can mix-and-match NOM accessors with nokogiri xpaths, xml manipulation, and traversing and it will just work.
|
8
8
|
|
9
9
|
|
10
10
|
Some Handy Links
|
11
11
|
----------------
|
12
12
|
Here are some resources to help you learn more about nom-xml:
|
13
13
|
|
14
|
-
- [API](http://rubydoc.info/github/cbeer/nom) - A reference to
|
14
|
+
- [API](http://rubydoc.info/github/cbeer/nom-xml) - A reference to nom-xml's classes
|
15
15
|
- [#projecthydra](http://webchat.freenode.net/?channels=#projecthydra) on irc.freenode.net
|
16
16
|
- [Project Hydra Google Group](http://groups.google.com/group/hydra-tech) - community mailing list and forum
|
17
17
|
|
@@ -1,5 +1,29 @@
|
|
1
1
|
module Nom::XML::Decorators::NodeSet
|
2
2
|
|
3
|
+
def values_for_term term
|
4
|
+
result = self
|
5
|
+
result = result.select &(term.options[:if]) if term.options[:if].is_a? Proc
|
6
|
+
result = result.reject &(term.options[:unless]) if term.options[:unless].is_a? Proc
|
7
|
+
|
8
|
+
m = term.options[:accessor]
|
9
|
+
return_value = case
|
10
|
+
when m.nil?
|
11
|
+
result
|
12
|
+
when m.is_a?(Symbol)
|
13
|
+
result.collect { |r| r.send(m) }.compact
|
14
|
+
when m.is_a?(Proc)
|
15
|
+
result.collect { |r| m.call(r) }.compact
|
16
|
+
else
|
17
|
+
raise "Unknown accessor class: #{m.class}"
|
18
|
+
end
|
19
|
+
|
20
|
+
if return_value and (term.options[:single] or (result.length == 1 and result.first.is_a? Nokogiri::XML::Attr))
|
21
|
+
return return_value.first
|
22
|
+
end
|
23
|
+
|
24
|
+
return return_value
|
25
|
+
end
|
26
|
+
|
3
27
|
##
|
4
28
|
# Add a #method_missing handler to NodeSets. If all of the elements in the Nodeset
|
5
29
|
# respond to a method (e.g. if it is a term accessor), call that method on all the
|
@@ -9,7 +33,11 @@ module Nom::XML::Decorators::NodeSet
|
|
9
33
|
result = self.collect { |node| node.send(sym, *args, &block) }.flatten
|
10
34
|
self.class.new(self.document, result) rescue result
|
11
35
|
else
|
12
|
-
|
36
|
+
begin
|
37
|
+
self.document.template_registry.send(sym, self, *args, &block)
|
38
|
+
rescue NameError
|
39
|
+
super
|
40
|
+
end
|
13
41
|
end
|
14
42
|
end
|
15
43
|
|
@@ -20,7 +20,11 @@ module Nom::XML::Decorators::Terminology
|
|
20
20
|
|
21
21
|
self.send(method, *args, &block)
|
22
22
|
else
|
23
|
-
|
23
|
+
begin
|
24
|
+
self.document.template_registry.send(method, self, *args, &block)
|
25
|
+
rescue NameError
|
26
|
+
super
|
27
|
+
end
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
@@ -35,7 +39,7 @@ module Nom::XML::Decorators::Terminology
|
|
35
39
|
def terms
|
36
40
|
@terms ||= self.ancestors.map { |p| p.term_accessors(self).map { |keys, values| values } }.flatten.compact.uniq
|
37
41
|
end
|
38
|
-
|
42
|
+
|
39
43
|
protected
|
40
44
|
##
|
41
45
|
# Collection of salient terminology accessors for this node
|
@@ -94,9 +98,6 @@ module Nom::XML::Decorators::Terminology
|
|
94
98
|
|
95
99
|
xpath = term.local_xpath
|
96
100
|
|
97
|
-
xpath += "[#{term.options[:if]}]" if term.options[:if] and term.options[:if].is_a? String
|
98
|
-
xpath += "[not(#{term.options[:unless]})]" if term.options[:unless] and term.options[:unless].is_a? String
|
99
|
-
|
100
101
|
xpath += "[#{args.join('][')}]" unless args.empty?
|
101
102
|
|
102
103
|
result = case self
|
@@ -106,26 +107,6 @@ module Nom::XML::Decorators::Terminology
|
|
106
107
|
self.xpath(xpath, self.document.terminology_namespaces)
|
107
108
|
end
|
108
109
|
|
109
|
-
result
|
110
|
-
result = result.reject &(term.options[:unless]) if term.options[:unless].is_a? Proc
|
111
|
-
|
112
|
-
m = term.options[:accessor]
|
113
|
-
|
114
|
-
return_value = case
|
115
|
-
when m.nil?
|
116
|
-
result
|
117
|
-
when m.is_a?(Symbol)
|
118
|
-
result.collect { |r| r.send(m) }.compact
|
119
|
-
when m.is_a?(Proc)
|
120
|
-
result.collect { |r| m.call(r) }.compact
|
121
|
-
else
|
122
|
-
raise "Unknown accessor class: #{m.class}"
|
123
|
-
end
|
124
|
-
|
125
|
-
if return_value and (term.options[:single] or (result.length == 1 and result.first.is_a? Nokogiri::XML::Attr))
|
126
|
-
return return_value.first
|
127
|
-
end
|
128
|
-
|
129
|
-
return return_value
|
110
|
+
result.values_for_term(term)
|
130
111
|
end
|
131
112
|
end
|
@@ -9,6 +9,9 @@ module Nom::XML
|
|
9
9
|
unless decorators(Nokogiri::XML::Node).include? Nom::XML::Decorators::Terminology
|
10
10
|
decorators(Nokogiri::XML::Node) << Nom::XML::Decorators::Terminology
|
11
11
|
end
|
12
|
+
unless decorators(Nokogiri::XML::Attr).include? Nom::XML::Decorators::Terminology
|
13
|
+
decorators(Nokogiri::XML::Attr) << Nom::XML::Decorators::Terminology
|
14
|
+
end
|
12
15
|
|
13
16
|
unless decorators(Nokogiri::XML::NodeSet).include? Nom::XML::Decorators::NodeSet
|
14
17
|
decorators(Nokogiri::XML::NodeSet) << Nom::XML::Decorators::NodeSet
|
@@ -44,71 +44,46 @@ class Nom::XML::TemplateRegistry
|
|
44
44
|
def instantiate(node_type, *args)
|
45
45
|
result = create_detached_node(nil, node_type, *args)
|
46
46
|
# Strip namespaces from text and CDATA nodes. Stupid Nokogiri.
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
unless jruby?
|
48
|
+
result.traverse { |node|
|
49
|
+
if node.is_a?(Nokogiri::XML::CharacterData)
|
50
|
+
node.namespace = nil
|
51
|
+
end
|
52
|
+
}
|
50
53
|
end
|
51
|
-
}
|
52
54
|
return result
|
53
55
|
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# @return the new [Nokogiri::XML::Node]
|
63
|
-
def add_next_sibling(target_node, node_type, *args, &block)
|
64
|
-
attach_node(:add_next_sibling, target_node, :parent, node_type, *args, &block)
|
65
|
-
end
|
66
|
-
|
67
|
-
# +instantiate+ a node and add it as a preceding sibling of the [Nokogiri::XML::Node] specified by +target_node+
|
68
|
-
# @return the new [Nokogiri::XML::Node]
|
69
|
-
def add_previous_sibling(target_node, node_type, *args, &block)
|
70
|
-
attach_node(:add_previous_sibling, target_node, :parent, node_type, *args, &block)
|
71
|
-
end
|
72
|
-
|
73
|
-
# +instantiate+ a node and add it as a following sibling of the [Nokogiri::XML::Node] specified by +target_node+
|
74
|
-
# @return +target_node+
|
75
|
-
def after(target_node, node_type, *args, &block)
|
76
|
-
attach_node(:after, target_node, :parent, node_type, *args, &block)
|
77
|
-
end
|
78
|
-
|
79
|
-
# +instantiate+ a node and add it as a preceding sibling of the [Nokogiri::XML::Node] specified by +target_node+
|
80
|
-
# @return +target_node+
|
81
|
-
def before(target_node, node_type, *args, &block)
|
82
|
-
attach_node(:before, target_node, :parent, node_type, *args, &block)
|
83
|
-
end
|
84
|
-
|
85
|
-
# +instantiate+ a node replace the [Nokogiri::XML::Node] specified by +target_node+ with it
|
86
|
-
# @return the new [Nokogiri::XML::Node]
|
87
|
-
def replace(target_node, node_type, *args, &block)
|
88
|
-
attach_node(:replace, target_node, :parent, node_type, *args, &block)
|
89
|
-
end
|
90
|
-
|
91
|
-
# +instantiate+ a node replace the [Nokogiri::XML::Node] specified by +target_node+ with it
|
92
|
-
# @return +target_node+
|
93
|
-
def swap(target_node, node_type, *args, &block)
|
94
|
-
attach_node(:swap, target_node, :parent, node_type, *args, &block)
|
95
|
-
end
|
96
|
-
|
97
|
-
def methods
|
98
|
-
super + @templates.keys.collect { |k| k.to_s }
|
99
|
-
end
|
100
|
-
|
101
|
-
def method_missing(sym,*args)
|
102
|
-
sym = sym.to_s.sub(/_$/,'').to_sym
|
103
|
-
if @templates.has_key?(sym)
|
57
|
+
ACTIONS = {
|
58
|
+
:add_child => :self, :add_next_sibling => :parent, :add_previous_sibling => :parent,
|
59
|
+
:after => :parent, :before => :parent, :replace => :parent, :swap => :parent
|
60
|
+
}
|
61
|
+
def method_missing(sym,*args,&block)
|
62
|
+
method = sym.to_s.sub(/_+$/,'').to_sym
|
63
|
+
if @templates.has_key?(method)
|
104
64
|
instantiate(sym,*args)
|
65
|
+
elsif ACTIONS.has_key?(method)
|
66
|
+
target_node = args.shift
|
67
|
+
attach_node(method, target_node, ACTIONS[method], *args, &block)
|
68
|
+
elsif method.to_s =~ /^(#{ACTIONS.keys.join('|')})_(.+)$/
|
69
|
+
method = $1.to_sym
|
70
|
+
template = $2.to_sym
|
71
|
+
if ACTIONS.has_key?(method) and @templates.has_key?(template)
|
72
|
+
target_node = args.shift
|
73
|
+
attach_node(method, target_node, ACTIONS[method], template, *args, &block)
|
74
|
+
end
|
105
75
|
else
|
106
76
|
super(sym,*args)
|
107
77
|
end
|
78
|
+
|
108
79
|
end
|
109
80
|
|
110
81
|
private
|
111
82
|
|
83
|
+
def jruby?
|
84
|
+
defined?(RUBY_ENGINE) and (RUBY_ENGINE == 'jruby')
|
85
|
+
end
|
86
|
+
|
112
87
|
# Create a new Nokogiri::XML::Node based on the template for +node_type+
|
113
88
|
#
|
114
89
|
# @param [Nokogiri::XML::Node] builder_node The node to use as starting point for building the node using Nokogiri::XML::Builder.with(builder_node). This provides namespace info, etc for constructing the new Node object. If nil, defaults to {Nom::XML::TemplateRegistry#empty_root_node}. This is just used to create the new node and will not be included in the response.
|
@@ -144,11 +119,13 @@ class Nom::XML::TemplateRegistry
|
|
144
119
|
new_node = create_detached_node(builder_node, node_type, *args)
|
145
120
|
result = target_node.send(method, new_node)
|
146
121
|
# Strip namespaces from text and CDATA nodes. Stupid Nokogiri.
|
147
|
-
|
148
|
-
|
149
|
-
node.
|
150
|
-
|
151
|
-
|
122
|
+
unless jruby?
|
123
|
+
new_node.traverse { |node|
|
124
|
+
if node.is_a?(Nokogiri::XML::CharacterData)
|
125
|
+
node.namespace = nil
|
126
|
+
end
|
127
|
+
}
|
128
|
+
end
|
152
129
|
if block_given?
|
153
130
|
yield result
|
154
131
|
else
|
data/lib/nom/xml/term.rb
CHANGED
@@ -46,7 +46,12 @@ module Nom::XML
|
|
46
46
|
# Get the relative xpath to this node from its immediate parent's term
|
47
47
|
# @return [String]
|
48
48
|
def local_xpath
|
49
|
-
("#{xmlns}:" unless xmlns.blank? ).to_s + (options[:path] || name).to_s
|
49
|
+
xpath = ("#{xmlns}:" unless xmlns.blank? ).to_s + (options[:path] || name).to_s
|
50
|
+
|
51
|
+
xpath += "[#{options[:if]}]" if options[:if] and options[:if].is_a? String
|
52
|
+
xpath += "[not(#{options[:unless]})]" if options[:unless] and options[:unless].is_a? String
|
53
|
+
|
54
|
+
xpath
|
50
55
|
end
|
51
56
|
|
52
57
|
##
|
@@ -56,6 +61,12 @@ module Nom::XML
|
|
56
61
|
terminology.document.root.xpath(xpath, terminology.namespaces)
|
57
62
|
end
|
58
63
|
|
64
|
+
##
|
65
|
+
# Get the document values associated with the term (after e.g. accessors)
|
66
|
+
def values
|
67
|
+
terminology.document.root.xpath(xpath, terminology.namespaces).value_for_term(self)
|
68
|
+
end
|
69
|
+
|
59
70
|
##
|
60
71
|
# Does this term have a sub-term called term_name
|
61
72
|
# @attr [String] term_key
|
data/lib/nom/xml/version.rb
CHANGED
@@ -6,11 +6,11 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "nom-xml"
|
7
7
|
s.version = Nom::XML::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["Chris Beer"]
|
10
|
-
s.email = %q{cabeer@stanford.edu}
|
11
|
-
s.homepage = %q{http://github.com/cbeer/nom}
|
12
|
-
s.summary = %q{
|
13
|
-
s.description = %q{
|
9
|
+
s.authors = ["Chris Beer", "Michael B. Klein"]
|
10
|
+
s.email = %q{cabeer@stanford.edu mbklein@gmail.com}
|
11
|
+
s.homepage = %q{http://github.com/cbeer/nom-xml}
|
12
|
+
s.summary = %q{ A library to help you tame sprawling XML schemas. }
|
13
|
+
s.description = %q{ NOM allows you to define a “terminology” to ease translation between XML and ruby objects }
|
14
14
|
|
15
15
|
s.add_dependency 'activesupport'
|
16
16
|
s.add_dependency 'i18n'
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_development_dependency "rspec"
|
20
20
|
s.add_development_dependency "rake"
|
21
21
|
s.add_development_dependency "yard"
|
22
|
+
s.add_development_dependency "equivalent-xml"
|
22
23
|
s.files = `git ls-files`.split("\n")
|
23
24
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
25
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Nom::XML::Decorators::NodeSet do
|
4
|
+
subject {
|
5
|
+
doc = Nokogiri::XML '<root><a n="1"/><b /><c /></root>'
|
6
|
+
doc.nom!
|
7
|
+
doc
|
8
|
+
}
|
9
|
+
describe "#values_for_term" do
|
10
|
+
|
11
|
+
|
12
|
+
it "should do if" do
|
13
|
+
t = mock(:options => { :if => lambda { |x| false }})
|
14
|
+
|
15
|
+
t1 = mock(:options => { :if => lambda { |x| true }})
|
16
|
+
|
17
|
+
subject.xpath('//*').values_for_term(t).should be_empty
|
18
|
+
subject.xpath('//*').values_for_term(t1).should_not be_empty
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should do unless" do
|
22
|
+
t = mock(:options => { :unless => lambda { |x| false }})
|
23
|
+
|
24
|
+
t1 = mock(:options => { :unless => lambda { |x| true }})
|
25
|
+
|
26
|
+
subject.xpath('//*').values_for_term(t).should_not be_empty
|
27
|
+
subject.xpath('//*').values_for_term(t1).should be_empty
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should do a nil accessor" do
|
31
|
+
t = mock(:options => { :accessor => nil})
|
32
|
+
|
33
|
+
subject.xpath('//*').values_for_term(t).should == subject.xpath('//*')
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should do a Proc accessor" do
|
37
|
+
t = mock(:options => { :accessor => lambda { |x| 1 }})
|
38
|
+
subject.xpath('//a').values_for_term(t).should == [1]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should do a symbol accessor" do
|
42
|
+
t = mock(:options => { :accessor => :z})
|
43
|
+
subject.xpath('//a').first.should_receive(:z).and_return(1)
|
44
|
+
subject.xpath('//a').values_for_term(t).should == [1]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should do single" do
|
48
|
+
t = mock(:options => { :single => true})
|
49
|
+
subject.xpath('//*').values_for_term(t).should == subject.xpath('//*').first
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should treat an attribute as a single" do
|
54
|
+
t = mock(:options => { })
|
55
|
+
subject.xpath('//@n').values_for_term(t).should be_a_kind_of Nokogiri::XML::Attr
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "method missing and respond to" do
|
61
|
+
it "should respond to methods on nodes if all nodes in the nodeset respond to the method" do
|
62
|
+
subject.xpath('//*').should respond_to :text
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should respond to methods on nodes if all nodes in the nodeset respond to the method" do
|
66
|
+
subject.xpath('//*').should_not respond_to :text_123
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should work" do
|
70
|
+
subject.xpath('//*').name.should include("a", "b", "c")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "NOM::XML::TemplateRegistry" do
|
4
|
+
|
5
|
+
let(:file) { '<people xmlns="urn:registry-test"><person title="Actor">Alice</person></people>' }
|
6
|
+
let(:xml) { Nokogiri::XML(file) }
|
7
|
+
let(:expectations) {
|
8
|
+
{
|
9
|
+
:before => %{<people xmlns="urn:registry-test"><person title="Builder">Bob</person><person title="Actor">Alice</person></people>},
|
10
|
+
:after => %{<people xmlns="urn:registry-test"><person title="Actor">Alice</person><person title="Builder">Bob</person></people>},
|
11
|
+
:instead => %{<people xmlns="urn:registry-test"><person title="Builder">Bob</person></people>}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
subject {
|
15
|
+
xml.set_terminology(:namespaces => { 'default' => 'urn:registry-test' }) do |t|
|
16
|
+
t.person(:xmlns => 'default') {
|
17
|
+
t.title(:path => "@title")
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
xml.define_template :person do |xml,name,title|
|
22
|
+
xml.person(:title => title) do
|
23
|
+
xml.text(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
xml.nom!
|
28
|
+
|
29
|
+
xml
|
30
|
+
}
|
31
|
+
|
32
|
+
describe "template definitions" do
|
33
|
+
it "should contain predefined templates" do
|
34
|
+
subject.template_registry.node_types.should include(:person)
|
35
|
+
subject.template_registry.node_types.should_not include(:zombie)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "ZOMG ZOMBIES!!11!!!1" do
|
39
|
+
before(:each) do
|
40
|
+
subject.define_template :zombie do |xml,name|
|
41
|
+
xml.monster(:wants => 'braaaaainz') do
|
42
|
+
xml.text(name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
it "should define new templates" do
|
47
|
+
subject.template_registry.node_types.should include(:zombie)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should instantiate a detached node from a template" do
|
51
|
+
node = subject.template_registry.instantiate(:zombie, 'Zeke')
|
52
|
+
expectation = Nokogiri::XML('<monster wants="braaaaainz">Zeke</monster>').root
|
53
|
+
node.should be_equivalent_to(expectation)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise an error when trying to instantiate an unknown node_type" do
|
57
|
+
lambda { subject.template_registry.instantiate(:demigod, 'Hercules') }.should raise_error(NameError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should raise an exception if a missing method name doesn't match a node_type" do
|
61
|
+
lambda { subject.template_registry.demigod('Hercules') }.should raise_error(NameError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should undefine existing templates" do
|
65
|
+
subject.template_registry.node_types.should include(:zombie)
|
66
|
+
subject.template_registry.undefine :zombie
|
67
|
+
subject.template_registry.node_types.should_not include(:zombie)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should complain if the template name isn't a symbol" do
|
71
|
+
lambda { subject.template_registry.define("die!") { |xml| subject.this_never_happened } }.should raise_error(TypeError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should report on whether a given template is defined" do
|
75
|
+
subject.template_registry.has_node_type?(:zombie).should == true
|
76
|
+
subject.template_registry.has_node_type?(:demigod).should == false
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "template-based document manipulations" do
|
83
|
+
it "should accept a Nokogiri::XML::Node as target" do
|
84
|
+
subject.template_registry.after(subject.root.elements.first, :person, 'Bob', 'Builder')
|
85
|
+
subject.root.elements.length.should == 2
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should accept a Nokogiri::XML::NodeSet as target" do
|
89
|
+
subject.template_registry.after(subject.root.elements, :person, 'Bob', 'Builder')
|
90
|
+
subject.root.elements.length.should == 2
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should instantiate a detached node from a template using the template name as a method" do
|
94
|
+
node = subject.template_registry.person('Odin', 'All-Father')
|
95
|
+
expectation = Nokogiri::XML('<person title="All-Father">Odin</person>').root
|
96
|
+
node.should be_equivalent_to(expectation)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should add_child" do
|
100
|
+
return_value = subject.template_registry.add_child(subject.root, :person, 'Bob', 'Builder')
|
101
|
+
return_value.should == subject.person[1]
|
102
|
+
subject.should be_equivalent_to(expectations[:after]).respecting_element_order
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should add_next_sibling" do
|
106
|
+
return_value = subject.template_registry.add_next_sibling(subject.person.first, :person, 'Bob', 'Builder')
|
107
|
+
return_value.should == subject.person[1]
|
108
|
+
subject.should be_equivalent_to(expectations[:after]).respecting_element_order
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should add_previous_sibling" do
|
112
|
+
return_value = subject.template_registry.add_previous_sibling(subject.person.first, :person, 'Bob', 'Builder')
|
113
|
+
return_value.should == subject.person.first
|
114
|
+
subject.should be_equivalent_to(expectations[:before]).respecting_element_order
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should after" do
|
118
|
+
return_value = subject.template_registry.after(subject.person.first, :person, 'Bob', 'Builder')
|
119
|
+
return_value.should == subject.person.first
|
120
|
+
subject.should be_equivalent_to(expectations[:after]).respecting_element_order
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should before" do
|
124
|
+
return_value = subject.template_registry.before(subject.person.first, :person, 'Bob', 'Builder')
|
125
|
+
return_value.should == subject.person[1]
|
126
|
+
subject.should be_equivalent_to(expectations[:before]).respecting_element_order
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should replace" do
|
130
|
+
target_node = subject.person.first
|
131
|
+
return_value = subject.template_registry.replace(target_node, :person, 'Bob', 'Builder')
|
132
|
+
return_value.should == subject.person.first
|
133
|
+
subject.should be_equivalent_to(expectations[:instead]).respecting_element_order
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should swap" do
|
137
|
+
target_node = subject.person.first
|
138
|
+
return_value = subject.template_registry.swap(target_node, :person, 'Bob', 'Builder')
|
139
|
+
return_value.should == target_node
|
140
|
+
subject.should be_equivalent_to(expectations[:instead]).respecting_element_order
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should yield the result if a block is given" do
|
144
|
+
target_node = subject.person.first
|
145
|
+
expectation = Nokogiri::XML('<person xmlns="urn:registry-test" title="Actor">Alice</person>').root
|
146
|
+
subject.template_registry.swap(target_node, :person, 'Bob', 'Builder') { |old_node|
|
147
|
+
old_node.should be_equivalent_to(expectation)
|
148
|
+
old_node
|
149
|
+
}.should be_equivalent_to(expectation)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "document-based document manipulations" do
|
154
|
+
it "should accept a Nokogiri::XML::Node as target" do
|
155
|
+
subject.root.elements.first.after_person('Bob', 'Builder')
|
156
|
+
subject.root.elements.length.should == 2
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should accept a Nokogiri::XML::NodeSet as target" do
|
160
|
+
subject.person.after_person(:person, 'Bob', 'Builder')
|
161
|
+
subject.root.elements.length.should == 2
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should instantiate a detached node from a template" do
|
165
|
+
node = subject.template_registry.instantiate(:person, 'Odin', 'All-Father')
|
166
|
+
expectation = Nokogiri::XML('<person title="All-Father">Odin</person>').root
|
167
|
+
node.should be_equivalent_to(expectation)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should add_child_node" do
|
171
|
+
return_value = subject.root.add_child_person('Bob', 'Builder')
|
172
|
+
return_value.should == subject.person[1]
|
173
|
+
subject.should be_equivalent_to(expectations[:after]).respecting_element_order
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should add_next_sibling_node" do
|
177
|
+
return_value = subject.person[0].add_next_sibling_person('Bob', 'Builder')
|
178
|
+
return_value.should == subject.person[1]
|
179
|
+
subject.should be_equivalent_to(expectations[:after]).respecting_element_order
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should add_previous_sibling_node" do
|
183
|
+
return_value = subject.person[0].add_previous_sibling_person('Bob', 'Builder')
|
184
|
+
return_value.should == subject.person.first
|
185
|
+
subject.should be_equivalent_to(expectations[:before]).respecting_element_order
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should after_node" do
|
189
|
+
return_value = subject.person[0].after_person('Bob', 'Builder')
|
190
|
+
return_value.should == subject.person.first
|
191
|
+
subject.should be_equivalent_to(expectations[:after]).respecting_element_order
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should before_node" do
|
195
|
+
return_value = subject.person[0].before_person('Bob', 'Builder')
|
196
|
+
return_value.should == subject.person[1]
|
197
|
+
subject.should be_equivalent_to(expectations[:before]).respecting_element_order
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should replace_node" do
|
201
|
+
target_node = subject.person.first
|
202
|
+
return_value = target_node.replace_person('Bob', 'Builder')
|
203
|
+
return_value.should == subject.person.first
|
204
|
+
subject.should be_equivalent_to(expectations[:instead]).respecting_element_order
|
205
|
+
end
|
206
|
+
|
207
|
+
it "should swap_node" do
|
208
|
+
target_node = subject.person.first
|
209
|
+
return_value = target_node.swap_person('Bob', 'Builder')
|
210
|
+
return_value.should == target_node
|
211
|
+
subject.should be_equivalent_to(expectations[:instead]).respecting_element_order
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
@@ -26,7 +26,6 @@ describe "Nutrition" do
|
|
26
26
|
c.nested
|
27
27
|
end
|
28
28
|
|
29
|
-
t.d :if => 'my-custom-function()'
|
30
29
|
end
|
31
30
|
|
32
31
|
xml.nom!
|
@@ -34,102 +33,19 @@ describe "Nutrition" do
|
|
34
33
|
xml
|
35
34
|
}
|
36
35
|
|
37
|
-
describe "#
|
38
|
-
subject do
|
39
|
-
m = mock()
|
40
|
-
m.stub(:document).and_return(mock(:terminology_namespaces => {}))
|
41
|
-
m.stub(:term_accessors).and_return(@term_accessors)
|
42
|
-
m.extend Nom::XML::Decorators::Terminology
|
43
|
-
m
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should define terminology accessors" do
|
47
|
-
mock_term = mock(:options => {})
|
48
|
-
@term_accessors = { :asdf => mock_term }
|
49
|
-
|
50
|
-
subject.should respond_to(:asdf)
|
51
|
-
end
|
52
|
-
|
53
|
-
it "should perform basic xpath queries" do
|
54
|
-
mock_term = mock(:local_xpath => '//asdf', :options => {})
|
55
|
-
@term_accessors = { :asdf => mock_term }
|
56
|
-
|
57
|
-
subject.should_receive(:xpath).with('//asdf', anything)
|
58
|
-
|
59
|
-
subject.asdf
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should perform xpath queries with constraints" do
|
63
|
-
mock_term = mock(:local_xpath => '//asdf', :options => {})
|
64
|
-
@term_accessors = { :asdf => mock_term }
|
65
|
-
|
66
|
-
subject.should_receive(:xpath).with('//asdf[predicate="value"]', anything)
|
67
|
-
subject.should_receive(:xpath).with('//asdf[predicate="\"value\""]', anything)
|
68
|
-
subject.should_receive(:xpath).with('//asdf[custom-xpath-predicate()]', anything)
|
69
|
-
subject.should_receive(:xpath).with('//asdf[custom-xpath-predicate()][predicate="value"]', anything)
|
70
|
-
|
71
|
-
subject.asdf(:predicate => 'value')
|
72
|
-
subject.asdf(:predicate => '"value"')
|
73
|
-
subject.asdf('custom-xpath-predicate()')
|
74
|
-
subject.asdf('custom-xpath-predicate()', :predicate => 'value')
|
75
|
-
end
|
76
|
-
|
77
|
-
it "should execute accessors" do
|
78
|
-
mock_term = mock(:local_xpath => '//asdf', :options => {:accessor => :text })
|
79
|
-
@term_accessors = { :asdf => mock_term }
|
80
|
-
|
81
|
-
m = mock()
|
82
|
-
subject.should_receive(:xpath).with('//asdf', anything).and_return([m])
|
83
|
-
m.should_receive(:text)
|
84
|
-
|
85
|
-
subject.asdf
|
86
|
-
end
|
87
|
-
|
88
|
-
it "should execute proc-based accessors" do
|
89
|
-
mock_term = mock(:local_xpath => '//asdf', :options => {:accessor => lambda { |x| x.zxcvb } })
|
90
|
-
@term_accessors = { :asdf => mock_term }
|
36
|
+
describe "#add_terminology_method_overrides!" do
|
91
37
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
it "should raise an error on unknown accessors" do
|
100
|
-
mock_term = mock(:local_xpath => '//asdf', :options => {:accessor => 123 })
|
101
|
-
@term_accessors = { :asdf => mock_term }
|
102
|
-
|
103
|
-
subject.should_receive(:xpath).with('//asdf', anything)
|
104
|
-
|
105
|
-
expect { subject.asdf }.to raise_error
|
106
|
-
end
|
107
|
-
|
108
|
-
it "should convert single-valued objects to single values" do
|
109
|
-
mock_term = mock(:local_xpath => '//asdf', :options => {:single => true })
|
110
|
-
@term_accessors = { :asdf => mock_term }
|
111
|
-
|
112
|
-
subject.should_receive(:xpath).with('//asdf', anything).and_return([1])
|
113
|
-
|
114
|
-
subject.asdf.should == 1
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should evaluate if" do
|
118
|
-
mock_term = mock(:local_xpath => '//asdf', :options => {:if => 'my-custom-function()' })
|
119
|
-
@term_accessors = { :asdf => mock_term }
|
120
|
-
|
121
|
-
subject.should_receive(:xpath).with('//asdf[my-custom-function()]', anything).and_return([1])
|
122
|
-
|
123
|
-
subject.asdf.should == [1]
|
38
|
+
it "should warn you if you try to override already existing methods" do
|
39
|
+
pending if defined? JRUBY_VERSION
|
40
|
+
mock_term = {:text => mock(:options => {})}
|
41
|
+
document.a.first.stub(:term_accessors).and_return mock_term
|
42
|
+
expect { document.a.first.add_terminology_method_overrides! }.to raise_error /Trying to redefine/
|
124
43
|
end
|
125
|
-
|
126
|
-
it "should
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
subject.should_receive(:xpath).with('//asdf[not(my-custom-function())]', anything).and_return([1])
|
131
|
-
|
132
|
-
subject.asdf.should == [1]
|
44
|
+
|
45
|
+
it "should let you override the warning" do
|
46
|
+
mock_term = {:text => mock(:options => { :override => true } )}
|
47
|
+
document.a.first.stub(:term_accessors).and_return mock_term
|
48
|
+
expect { document.a.first.add_terminology_method_overrides! }.to_not raise_error /Trying to redefine/
|
133
49
|
end
|
134
50
|
end
|
135
51
|
|
data/spec/spec_helper.rb
CHANGED
data/spec/test_spec.rb
CHANGED
@@ -2,6 +2,21 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Nom::XML do
|
4
4
|
|
5
|
+
it "shouldn't break under jruby" do
|
6
|
+
doc = Nokogiri::XML <<-eoxml
|
7
|
+
<root>
|
8
|
+
<a b="1">123</a>
|
9
|
+
</root>
|
10
|
+
eoxml
|
11
|
+
|
12
|
+
doc.set_terminology do |t|
|
13
|
+
t.b :path => '//@b', :accessor => lambda { |x| x.text }
|
14
|
+
end
|
15
|
+
|
16
|
+
doc.nom!
|
17
|
+
|
18
|
+
doc.b.should == "1"
|
19
|
+
end
|
5
20
|
it "should do stuff with terminologies" do
|
6
21
|
doc = Nokogiri::XML <<-eos
|
7
22
|
<root>
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nom-xml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Chris Beer
|
9
|
+
- Michael B. Klein
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
+
date: 2012-12-03 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: activesupport
|
@@ -107,8 +108,25 @@ dependencies:
|
|
107
108
|
- - ! '>='
|
108
109
|
- !ruby/object:Gem::Version
|
109
110
|
version: '0'
|
110
|
-
|
111
|
-
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: equivalent-xml
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
description: ! ' NOM allows you to define a “terminology” to ease translation between
|
128
|
+
XML and ruby objects '
|
129
|
+
email: cabeer@stanford.edu mbklein@gmail.com
|
112
130
|
executables: []
|
113
131
|
extensions: []
|
114
132
|
extra_rdoc_files:
|
@@ -131,7 +149,7 @@ files:
|
|
131
149
|
- lib/nom/xml/term.rb
|
132
150
|
- lib/nom/xml/terminology.rb
|
133
151
|
- lib/nom/xml/version.rb
|
134
|
-
- nom.gemspec
|
152
|
+
- nom-xml.gemspec
|
135
153
|
- spec/examples/mods_example_spec.rb
|
136
154
|
- spec/examples/namespaced_example_spec.rb
|
137
155
|
- spec/examples/nutrition_example_spec.rb
|
@@ -141,11 +159,12 @@ files:
|
|
141
159
|
- spec/fixtures/xml_namespaces.xml
|
142
160
|
- spec/lib/nodeset_decorator_spec.rb
|
143
161
|
- spec/lib/nokogiri_extension_spec.rb
|
162
|
+
- spec/lib/template_registry_spec.rb
|
144
163
|
- spec/lib/terminology_decorator_spec.rb
|
145
164
|
- spec/lib/terminology_spec.rb
|
146
165
|
- spec/spec_helper.rb
|
147
166
|
- spec/test_spec.rb
|
148
|
-
homepage: http://github.com/cbeer/nom
|
167
|
+
homepage: http://github.com/cbeer/nom-xml
|
149
168
|
licenses: []
|
150
169
|
post_install_message:
|
151
170
|
rdoc_options: []
|
@@ -159,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
159
178
|
version: '0'
|
160
179
|
segments:
|
161
180
|
- 0
|
162
|
-
hash: -
|
181
|
+
hash: -2880685956354160513
|
163
182
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
183
|
none: false
|
165
184
|
requirements:
|
@@ -168,13 +187,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
187
|
version: '0'
|
169
188
|
segments:
|
170
189
|
- 0
|
171
|
-
hash: -
|
190
|
+
hash: -2880685956354160513
|
172
191
|
requirements: []
|
173
192
|
rubyforge_project:
|
174
193
|
rubygems_version: 1.8.23
|
175
194
|
signing_key:
|
176
195
|
specification_version: 3
|
177
|
-
summary:
|
196
|
+
summary: A library to help you tame sprawling XML schemas.
|
178
197
|
test_files:
|
179
198
|
- spec/examples/mods_example_spec.rb
|
180
199
|
- spec/examples/namespaced_example_spec.rb
|
@@ -185,6 +204,7 @@ test_files:
|
|
185
204
|
- spec/fixtures/xml_namespaces.xml
|
186
205
|
- spec/lib/nodeset_decorator_spec.rb
|
187
206
|
- spec/lib/nokogiri_extension_spec.rb
|
207
|
+
- spec/lib/template_registry_spec.rb
|
188
208
|
- spec/lib/terminology_decorator_spec.rb
|
189
209
|
- spec/lib/terminology_spec.rb
|
190
210
|
- spec/spec_helper.rb
|