om 1.6.1 → 1.7.0.rc1
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/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)
|