nom-xml 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://secure.travis-ci.org/cbeer/nom.png)](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
|