om 1.6.1 → 1.7.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +31 -19
- data/History.textile +3 -0
- data/devel/notes.txt +12 -0
- data/lib/om.rb +2 -0
- data/lib/om/version.rb +1 -1
- data/lib/om/xml/container.rb +7 -6
- data/lib/om/xml/document.rb +47 -31
- data/lib/om/xml/dynamic_node.rb +36 -6
- data/lib/om/xml/named_term_proxy.rb +9 -2
- data/lib/om/xml/term.rb +22 -1
- data/lib/om/xml/term_value_operators.rb +8 -1
- data/lib/om/xml/validation.rb +2 -5
- data/om.gemspec +3 -0
- data/spec/integration/serialization_spec.rb +53 -0
- data/spec/unit/container_spec.rb +21 -17
- data/spec/unit/dynamic_node_spec.rb +4 -1
- data/spec/unit/nokogiri_sanity_spec.rb +3 -0
- data/spec/unit/term_spec.rb +205 -166
- data/spec/unit/term_value_operators_spec.rb +21 -1
- data/spec/unit/terminology_builder_spec.rb +3 -1
- data/spec/unit/terminology_spec.rb +9 -3
- metadata +57 -6
data/Gemfile.lock
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
om (1.6.
|
4
|
+
om (1.6.2)
|
5
|
+
activemodel
|
6
|
+
activesupport
|
5
7
|
mediashelf-loggable
|
6
8
|
nokogiri (>= 1.4.2)
|
7
9
|
|
@@ -9,36 +11,45 @@ GEM
|
|
9
11
|
remote: http://rubygems.org/
|
10
12
|
specs:
|
11
13
|
RedCloth (4.2.9)
|
12
|
-
|
13
|
-
|
14
|
+
activemodel (3.2.8)
|
15
|
+
activesupport (= 3.2.8)
|
16
|
+
builder (~> 3.0.0)
|
17
|
+
activesupport (3.2.8)
|
18
|
+
i18n (~> 0.6)
|
19
|
+
multi_json (~> 1.0)
|
20
|
+
awesome_print (1.0.2)
|
21
|
+
builder (3.0.0)
|
22
|
+
columnize (0.3.6)
|
23
|
+
debugger (1.2.0)
|
14
24
|
columnize (>= 0.3.1)
|
15
25
|
debugger-linecache (~> 1.1.1)
|
16
|
-
debugger-ruby_core_source (~> 1.1.
|
17
|
-
debugger-linecache (1.1.
|
26
|
+
debugger-ruby_core_source (~> 1.1.3)
|
27
|
+
debugger-linecache (1.1.2)
|
18
28
|
debugger-ruby_core_source (>= 1.1.1)
|
19
29
|
debugger-ruby_core_source (1.1.3)
|
20
30
|
diff-lcs (1.1.3)
|
21
|
-
equivalent-xml (0.2.
|
31
|
+
equivalent-xml (0.2.9)
|
22
32
|
nokogiri (>= 1.4.3)
|
33
|
+
i18n (0.6.0)
|
23
34
|
linecache (0.46)
|
24
35
|
rbx-require-relative (> 0.0.4)
|
25
36
|
mediashelf-loggable (0.4.9)
|
26
37
|
metaclass (0.0.1)
|
27
|
-
mocha (0.
|
38
|
+
mocha (0.12.3)
|
28
39
|
metaclass (~> 0.0.1)
|
29
40
|
multi_json (1.3.6)
|
30
|
-
nokogiri (1.5.
|
41
|
+
nokogiri (1.5.5)
|
31
42
|
rake (0.9.2.2)
|
32
|
-
rbx-require-relative (0.0.
|
33
|
-
rcov (0.
|
34
|
-
rspec (2.
|
35
|
-
rspec-core (~> 2.
|
36
|
-
rspec-expectations (~> 2.
|
37
|
-
rspec-mocks (~> 2.
|
38
|
-
rspec-core (2.
|
39
|
-
rspec-expectations (2.
|
40
|
-
diff-lcs (~> 1.1.
|
41
|
-
rspec-mocks (2.
|
43
|
+
rbx-require-relative (0.0.9)
|
44
|
+
rcov (1.0.0)
|
45
|
+
rspec (2.11.0)
|
46
|
+
rspec-core (~> 2.11.0)
|
47
|
+
rspec-expectations (~> 2.11.0)
|
48
|
+
rspec-mocks (~> 2.11.0)
|
49
|
+
rspec-core (2.11.1)
|
50
|
+
rspec-expectations (2.11.2)
|
51
|
+
diff-lcs (~> 1.1.3)
|
52
|
+
rspec-mocks (2.11.2)
|
42
53
|
ruby-debug (0.10.4)
|
43
54
|
columnize (>= 0.1)
|
44
55
|
ruby-debug-base (~> 0.10.4.0)
|
@@ -50,13 +61,14 @@ GEM
|
|
50
61
|
simplecov-html (0.5.3)
|
51
62
|
simplecov-rcov (0.2.3)
|
52
63
|
simplecov (>= 0.4.1)
|
53
|
-
yard (0.
|
64
|
+
yard (0.8.2.1)
|
54
65
|
|
55
66
|
PLATFORMS
|
56
67
|
ruby
|
57
68
|
|
58
69
|
DEPENDENCIES
|
59
70
|
RedCloth (~> 4.2.9)
|
71
|
+
awesome_print
|
60
72
|
debugger
|
61
73
|
equivalent-xml (>= 0.2.4)
|
62
74
|
mocha (>= 0.9.8)
|
data/History.textile
CHANGED
data/devel/notes.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Purposes of OM (based on notes from conversation with Matt Zumwalt):
|
2
|
+
|
3
|
+
1. Provide convenient names to access information in an XML document --
|
4
|
+
essentially a declarative mechanism to define methods that will issue
|
5
|
+
xpath queries against an XML document.
|
6
|
+
|
7
|
+
2. Provides a convient way to create new, empty XML documents (or fragments),
|
8
|
+
with implied parent nodes (if any) and default attribute values (if any).
|
9
|
+
This is done by overriding the class method xml_template() in your XML
|
10
|
+
document class.
|
11
|
+
|
12
|
+
3. Provides a way to map your OM terminology into SOLR.
|
data/lib/om.rb
CHANGED
data/lib/om/version.rb
CHANGED
data/lib/om/xml/container.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module OM::XML::Container
|
2
|
+
extend ActiveSupport::Concern
|
2
3
|
|
3
4
|
attr_accessor :ng_xml
|
4
5
|
|
@@ -11,7 +12,7 @@ module OM::XML::Container
|
|
11
12
|
# Careful! If you call this from a constructor, be sure to provide something 'ie. self' as the @tmpl. Otherwise, you will get an infinite loop!
|
12
13
|
def from_xml(xml=nil, tmpl=self.new) # :nodoc:
|
13
14
|
if xml.nil?
|
14
|
-
tmpl.ng_xml = self.xml_template
|
15
|
+
# noop: handled in #ng_xml accessor.. tmpl.ng_xml = self.xml_template
|
15
16
|
elsif xml.kind_of? Nokogiri::XML::Node
|
16
17
|
tmpl.ng_xml = xml
|
17
18
|
else
|
@@ -30,12 +31,12 @@ module OM::XML::Container
|
|
30
31
|
|
31
32
|
end
|
32
33
|
|
34
|
+
def ng_xml
|
35
|
+
@ng_xml ||= self.class.xml_template
|
36
|
+
end
|
37
|
+
|
33
38
|
# Instance Methods -- These methods will be available on instances of classes that include this module
|
34
39
|
|
35
|
-
def self.included(klass)
|
36
|
-
klass.extend(ClassMethods)
|
37
|
-
end
|
38
|
-
|
39
40
|
def to_xml(xml = ng_xml)
|
40
41
|
if xml == ng_xml
|
41
42
|
return xml.to_xml
|
@@ -52,4 +53,4 @@ module OM::XML::Container
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
|
-
end
|
56
|
+
end
|
data/lib/om/xml/document.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module OM::XML::Document
|
2
|
-
|
2
|
+
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
# Class Methods -- These methods will be available on classes that include this Module
|
5
5
|
|
@@ -7,24 +7,39 @@ module OM::XML::Document
|
|
7
7
|
|
8
8
|
attr_accessor :terminology, :terminology_builder, :template_registry
|
9
9
|
|
10
|
+
def terminology
|
11
|
+
@terminology ||= terminology_builder.build
|
12
|
+
end
|
13
|
+
|
14
|
+
def terminology_builder
|
15
|
+
@terminology_builder ||= OM::XML::Terminology::Builder.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def template_registry
|
19
|
+
@template_registry ||= OM::XML::TemplateRegistry.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def rebuild_terminology!
|
23
|
+
@terminology = @terminology_builder.build
|
24
|
+
end
|
25
|
+
|
10
26
|
# Sets the OM::XML::Terminology for the Document
|
11
27
|
# Expects +&block+ that will be passed into OM::XML::Terminology::Builder.new
|
12
28
|
def set_terminology &block
|
13
|
-
@terminology_builder = OM::XML::Terminology::Builder.new(
|
14
|
-
|
15
|
-
@terminology = @terminology_builder.build
|
29
|
+
@terminology_builder = OM::XML::Terminology::Builder.new(&block)
|
30
|
+
rebuild_terminology!
|
16
31
|
end
|
17
32
|
|
18
33
|
# Update the OM::XML::Terminology with additional terms
|
19
34
|
def extend_terminology &block
|
20
|
-
|
21
|
-
|
35
|
+
self.terminology_builder.extend_terminology(&block)
|
36
|
+
rebuild_terminology!
|
22
37
|
end
|
23
|
-
|
38
|
+
|
24
39
|
# (Explicitly) inherit terminology from upstream classes
|
25
40
|
def use_terminology klass
|
26
|
-
|
27
|
-
|
41
|
+
self.terminology_builder = klass.terminology_builder.dup
|
42
|
+
rebuild_terminology!
|
28
43
|
end
|
29
44
|
|
30
45
|
# Define a new node template with the OM::XML::TemplateRegistry.
|
@@ -32,31 +47,38 @@ module OM::XML::Document
|
|
32
47
|
# * The +block+ does the work of creating the new node, and will receive
|
33
48
|
# a Nokogiri::XML::Builder and any other args passed to one of the node instantiation methods.
|
34
49
|
def define_template name, &block
|
35
|
-
|
36
|
-
@template_registry.define name, &block
|
50
|
+
self.template_registry.define name, &block
|
37
51
|
end
|
38
52
|
|
39
53
|
# Returns any namespaces defined by the Class' Terminology
|
40
54
|
def ox_namespaces
|
41
|
-
|
42
|
-
return {}
|
43
|
-
else
|
44
|
-
return @terminology.namespaces
|
45
|
-
end
|
55
|
+
self.terminology.namespaces
|
46
56
|
end
|
47
|
-
|
48
57
|
end
|
49
58
|
|
50
59
|
# Instance Methods -- These methods will be available on instances of classes that include this module
|
51
60
|
|
52
61
|
attr_accessor :ox_namespaces
|
53
62
|
|
54
|
-
|
55
|
-
|
63
|
+
included do
|
64
|
+
include OM::XML::Container
|
65
|
+
include OM::XML::TermValueOperators
|
66
|
+
include OM::XML::Validation
|
67
|
+
include ActiveModel::Dirty
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def ng_xml_will_change!
|
72
|
+
if self.respond_to?(:dirty=)
|
73
|
+
self.dirty = true
|
74
|
+
end
|
56
75
|
|
57
|
-
|
58
|
-
|
59
|
-
|
76
|
+
# throw away older version.
|
77
|
+
changed_attributes['ng_xml'] = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def ng_xml_changed?
|
81
|
+
changed.include?('ng_xml')
|
60
82
|
end
|
61
83
|
|
62
84
|
def method_missing(name, *args)
|
@@ -77,20 +99,13 @@ module OM::XML::Document
|
|
77
99
|
super
|
78
100
|
end
|
79
101
|
end
|
80
|
-
|
81
|
-
|
82
102
|
end
|
83
103
|
|
84
104
|
|
85
105
|
def find_by_xpath(xpath)
|
86
|
-
|
87
|
-
ng_xml.xpath(xpath)
|
88
|
-
else
|
89
|
-
ng_xml.xpath(xpath, ox_namespaces)
|
90
|
-
end
|
106
|
+
ng_xml.xpath(xpath, ox_namespaces)
|
91
107
|
end
|
92
108
|
|
93
|
-
|
94
109
|
# Applies the property's corresponding xpath query, returning the result Nokogiri::XML::NodeSet
|
95
110
|
def find_by_terms_and_value(*term_pointer)
|
96
111
|
xpath = self.class.terminology.xpath_for(*term_pointer)
|
@@ -176,7 +191,7 @@ module OM::XML::Document
|
|
176
191
|
# Returns a hash combining the current documents namespaces (provided by nokogiri) and any namespaces that have been set up by your Terminology.
|
177
192
|
# Most importantly, this matches the 'oxns' namespace to the namespace you provided in your Terminology's root term config
|
178
193
|
def ox_namespaces
|
179
|
-
@ox_namespaces ||= ng_xml.namespaces.merge(self.class.ox_namespaces)
|
194
|
+
@ox_namespaces ||= ng_xml.namespaces.merge(self.class.ox_namespaces).reject { |k,v| v.nil? || v.empty? }
|
180
195
|
end
|
181
196
|
|
182
197
|
private
|
@@ -185,6 +200,7 @@ module OM::XML::Document
|
|
185
200
|
target = self.find_by_terms(*target)
|
186
201
|
end
|
187
202
|
raise "You must call define_template before calling #{method}" if template_registry.nil?
|
203
|
+
ng_xml_will_change!
|
188
204
|
template_registry.send(method, target, *args, &block)
|
189
205
|
end
|
190
206
|
end
|
data/lib/om/xml/dynamic_node.rb
CHANGED
@@ -68,6 +68,7 @@ module OM
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def val=(args)
|
71
|
+
@document.ng_xml_will_change!
|
71
72
|
new_values = sanitize_new_values(args.first)
|
72
73
|
new_values.keys.sort { |a,b| a.to_i <=> b.to_i }.each do |y|
|
73
74
|
z = new_values[y]
|
@@ -79,21 +80,19 @@ module OM
|
|
79
80
|
@document.term_value_update(xpath, y.to_i, z)
|
80
81
|
end
|
81
82
|
end
|
82
|
-
if @document.respond_to?(:dirty=)
|
83
|
-
@document.dirty = true
|
84
|
-
end
|
85
83
|
end
|
86
84
|
|
87
85
|
def sanitize_new_values(new_values)
|
88
86
|
# Sanitize new_values to always be a hash with indexes
|
89
87
|
case new_values
|
90
88
|
when Hash
|
89
|
+
new_values.each {|k, v| v = serialize(v) }
|
91
90
|
when Array
|
92
91
|
nv = new_values.dup
|
93
92
|
new_values = {}
|
94
|
-
nv.each {|v| new_values[nv.index(v).to_s] = v}
|
93
|
+
nv.each {|v| new_values[nv.index(v).to_s] = serialize(v)}
|
95
94
|
else
|
96
|
-
new_values = {"0"=>new_values}
|
95
|
+
new_values = {"0"=>serialize(new_values)}
|
97
96
|
end
|
98
97
|
new_values
|
99
98
|
end
|
@@ -109,7 +108,34 @@ module OM
|
|
109
108
|
def val
|
110
109
|
query = xpath
|
111
110
|
trim_text = !query.index("text()").nil?
|
112
|
-
|
111
|
+
val = @document.find_by_xpath(query).collect {|node| (trim_text ? node.text.strip : node.text) }
|
112
|
+
deserialize(val)
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param string
|
116
|
+
# @return [String,Date,Integer]
|
117
|
+
def deserialize(val)
|
118
|
+
case term.type
|
119
|
+
when :date
|
120
|
+
val.map { |v| Date.parse(v)}
|
121
|
+
when :integer
|
122
|
+
val.map { |v| v.to_i}
|
123
|
+
else
|
124
|
+
val
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# @param val [String,Date,Integer]
|
129
|
+
def serialize (val)
|
130
|
+
case term.type
|
131
|
+
when :date
|
132
|
+
val.to_s
|
133
|
+
when :integer
|
134
|
+
val.to_s
|
135
|
+
else
|
136
|
+
val
|
137
|
+
end
|
138
|
+
|
113
139
|
end
|
114
140
|
|
115
141
|
def nodeset
|
@@ -117,6 +143,10 @@ module OM
|
|
117
143
|
trim_text = !query.index("text()").nil?
|
118
144
|
return @document.find_by_xpath(query)
|
119
145
|
end
|
146
|
+
|
147
|
+
def delete
|
148
|
+
nodeset.delete
|
149
|
+
end
|
120
150
|
|
121
151
|
def inspect
|
122
152
|
val.inspect
|
@@ -13,7 +13,7 @@ class OM::XML::NamedTermProxy
|
|
13
13
|
# @param [Hash] opts additional Term options
|
14
14
|
def initialize(name, proxy_pointer, terminology, opts={})
|
15
15
|
opts = {:namespace_prefix=>"oxns", :ancestors=>[], :children=>{}}.merge(opts)
|
16
|
-
[:children, :ancestors].each do |accessor_name|
|
16
|
+
[:children, :ancestors, :index_as].each do |accessor_name|
|
17
17
|
instance_variable_set("@#{accessor_name}", opts.fetch(accessor_name, nil) )
|
18
18
|
end
|
19
19
|
@terminology = terminology
|
@@ -45,7 +45,14 @@ class OM::XML::NamedTermProxy
|
|
45
45
|
return false
|
46
46
|
end
|
47
47
|
|
48
|
-
|
48
|
+
##
|
49
|
+
# Always co-erce :index_as attributes into an Array
|
50
|
+
def index_as
|
51
|
+
if @index_as
|
52
|
+
Array(@index_as)
|
53
|
+
else
|
54
|
+
self.proxied_term.index_as
|
55
|
+
end
|
49
56
|
end
|
50
57
|
|
51
58
|
# Any unknown method calls will be proxied to the proxied term
|
data/lib/om/xml/term.rb
CHANGED
@@ -135,6 +135,9 @@ class OM::XML::Term
|
|
135
135
|
attr_accessor :name, :xpath, :xpath_constrained, :xpath_relative, :path, :index_as, :required, :data_type, :variant_of, :path, :default_content_path, :is_root_term
|
136
136
|
attr_accessor :children, :internal_xml, :terminology
|
137
137
|
|
138
|
+
# the data type for this node
|
139
|
+
attr_accessor :type
|
140
|
+
|
138
141
|
# Any XML attributes that qualify the Term.
|
139
142
|
#
|
140
143
|
# @example Declare a Term that has a given attribute (ie. //title[@xml:lang='eng'])
|
@@ -170,11 +173,22 @@ class OM::XML::Term
|
|
170
173
|
# If you want to specify which namespace a term is using, use:
|
171
174
|
# namspace_prefix => "bar"
|
172
175
|
# This value defaults to nil, in which case if a default namespace is set in the termnology, that namespace will be used.
|
176
|
+
#
|
177
|
+
# @param name [Symbol] the name to refer to this term by
|
178
|
+
# @param opts [Hash]
|
179
|
+
# @options opts [Array] :index_as a list of indexing hints provided to to_solr
|
180
|
+
# @options opts [String] :path partial xpath that points to the node.
|
181
|
+
# @options opts [Hash] :attributes xml attributes to match in the selector
|
182
|
+
# @options opts [String] :namespace_prefix xml namespace for this node
|
183
|
+
# @options opts [Symbol] :type one of :string, :date, :integer. Defaults to :string
|
173
184
|
def initialize(name, opts={}, terminology=nil)
|
174
185
|
opts = {:ancestors=>[], :children=>{}}.merge(opts)
|
175
|
-
[:children, :ancestors,:path, :index_as, :required, :
|
186
|
+
[:children, :ancestors,:path, :index_as, :required, :variant_of, :path, :attributes, :default_content_path, :namespace_prefix].each do |accessor_name|
|
176
187
|
instance_variable_set("@#{accessor_name}", opts.fetch(accessor_name, nil) )
|
177
188
|
end
|
189
|
+
|
190
|
+
self.type = opts[:type] || :string
|
191
|
+
|
178
192
|
unless terminology.nil?
|
179
193
|
if opts[:namespace_prefix].nil?
|
180
194
|
unless terminology.namespaces["xmlns"].nil?
|
@@ -188,6 +202,7 @@ class OM::XML::Term
|
|
188
202
|
end
|
189
203
|
end
|
190
204
|
|
205
|
+
|
191
206
|
def self.from_node(mapper_xml)
|
192
207
|
name = mapper_xml.attribute("name").text.to_sym
|
193
208
|
attributes = {}
|
@@ -211,6 +226,12 @@ class OM::XML::Term
|
|
211
226
|
return new_mapper
|
212
227
|
end
|
213
228
|
|
229
|
+
##
|
230
|
+
# Always co-erce :index_as attributes into an Array
|
231
|
+
def index_as
|
232
|
+
Array(@index_as)
|
233
|
+
end
|
234
|
+
|
214
235
|
# crawl down into mapper's children hash to find the desired mapper
|
215
236
|
# ie. @test_mapper.retrieve_mapper(:conference, :role, :text)
|
216
237
|
def retrieve_term(*pointers)
|