om 1.4.4 → 1.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/Gemfile.lock +1 -1
- data/History.textile +4 -0
- data/lib/om/version.rb +1 -1
- data/lib/om/xml/document.rb +33 -29
- data/lib/om/xml/term.rb +62 -53
- data/lib/om/xml/terminology.rb +49 -48
- data/spec/fixtures/no_namespace.xml +7 -0
- data/spec/unit/term_spec.rb +30 -33
- data/spec/unit/term_xpath_generator_spec.rb +37 -29
- data/spec/unit/terminology_spec.rb +79 -53
- metadata +9 -8
data/lib/om/xml/terminology.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# When you're defining a Terminology, you will usually use a "Terminology Builder":OM/XML/Terminology/Builder.html to create it
|
2
|
-
# Each line you put into a "Terminology Builder":OM/XML/Terminology/Builder.html is passed to the constructor for a "Term Builder":OM/XML/Term/Builder.html.
|
2
|
+
# Each line you put into a "Terminology Builder":OM/XML/Terminology/Builder.html is passed to the constructor for a "Term Builder":OM/XML/Term/Builder.html.
|
3
3
|
# See the "OM::XML::Term::Builder":OM/XML/Term/Builder.html API docs for complete description of your options for defining each Term.
|
4
4
|
#
|
5
5
|
# The most important thing to define in a Terminology is the root term. This is the place where you set namespaces and schemas for the Terminology
|
@@ -8,23 +8,24 @@
|
|
8
8
|
# t.root(:path=>"mods", :xmlns=>"http://www.loc.gov/mods/v3", :schema=>"http://www.loc.gov/standards/mods/v3/mods-3-2.xsd")
|
9
9
|
# end
|
10
10
|
# terminology = terminology_builder.build
|
11
|
+
# If you do not set a namespace, the terminology will assume there is no namespace on the xml document.
|
11
12
|
class OM::XML::Terminology
|
12
|
-
|
13
|
+
|
13
14
|
class BadPointerError < StandardError; end
|
14
15
|
class CircularReferenceError < StandardError; end
|
15
|
-
|
16
|
+
|
16
17
|
# Terminology::Builder Class Definition
|
17
18
|
#
|
18
|
-
# When coding against Builders, remember that they rely on MethodMissing,
|
19
|
-
# so any time you call a method on the Builder that it doesn't explicitly recognize,
|
19
|
+
# When coding against Builders, remember that they rely on MethodMissing,
|
20
|
+
# so any time you call a method on the Builder that it doesn't explicitly recognize,
|
20
21
|
# the Builder will add your method & arguments to the it's settings and return itself.
|
21
22
|
class Builder
|
22
|
-
|
23
|
+
|
23
24
|
attr_accessor :schema, :namespaces
|
24
25
|
attr_reader :term_builders
|
25
26
|
###
|
26
27
|
# Create a new Terminology Builder object. +options+ are sent to the top level
|
27
|
-
# Document that is being built.
|
28
|
+
# Document that is being built.
|
28
29
|
# (not yet supported:) +root+ can be a point in an existing Terminology that you want to add Mappers into
|
29
30
|
#
|
30
31
|
# Building a document with a particular encoding for example:
|
@@ -37,10 +38,10 @@ class OM::XML::Terminology
|
|
37
38
|
@namespaces = options.fetch(:namespaces,{})
|
38
39
|
@term_builders = {}
|
39
40
|
@cur_term_builder = nil
|
40
|
-
|
41
|
+
|
41
42
|
yield self if block_given?
|
42
43
|
end
|
43
|
-
|
44
|
+
|
44
45
|
# Set the root of the Terminology, along with namespace & schema info
|
45
46
|
def root opts, &block
|
46
47
|
@schema = opts.fetch(:schema,nil)
|
@@ -55,36 +56,36 @@ class OM::XML::Terminology
|
|
55
56
|
term_opts.delete(:schema)
|
56
57
|
root_term_builder.settings.merge!(term_opts)
|
57
58
|
@term_builders[root_term_builder.name] = root_term_builder
|
58
|
-
|
59
|
+
|
59
60
|
return root_term_builder
|
60
61
|
end
|
61
|
-
|
62
|
+
|
62
63
|
# Returns an array of Terms that have been marked as "root" terms
|
63
64
|
def root_term_builders
|
64
65
|
@term_builders.values.select {|term_builder| term_builder.settings[:is_root_term] == true }
|
65
66
|
end
|
66
|
-
|
67
|
+
|
67
68
|
def method_missing method, *args, &block # :nodoc:
|
68
69
|
parent_builder = @cur_term_builder
|
69
70
|
@cur_term_builder = OM::XML::Term::Builder.new(method.to_s.sub(/[_!]$/, ''), self)
|
70
|
-
|
71
|
+
|
71
72
|
# Attach to parent
|
72
73
|
if parent_builder
|
73
74
|
parent_builder.add_child @cur_term_builder
|
74
75
|
else
|
75
76
|
@term_builders [@cur_term_builder.name] = @cur_term_builder
|
76
77
|
end
|
77
|
-
|
78
|
+
|
78
79
|
# Apply options
|
79
80
|
opts = args.shift
|
80
81
|
@cur_term_builder.settings.merge!(opts) if opts
|
81
|
-
|
82
|
+
|
82
83
|
# Parse children
|
83
84
|
yield if block
|
84
|
-
|
85
|
+
|
85
86
|
@cur_term_builder = parent_builder
|
86
87
|
end
|
87
|
-
|
88
|
+
|
88
89
|
# Returns the TermBuilder corresponding to the given _pointer_.
|
89
90
|
def retrieve_term_builder(*args)
|
90
91
|
args_cp = args.dup
|
@@ -100,35 +101,35 @@ class OM::XML::Terminology
|
|
100
101
|
end
|
101
102
|
return current_term
|
102
103
|
end
|
103
|
-
|
104
|
+
|
104
105
|
def build
|
105
106
|
terminology = OM::XML::Terminology.new(:schema=>@schema, :namespaces=>@namespaces)
|
106
|
-
root_term_builders.each do |root_term_builder|
|
107
|
-
root_term_builder.children = self.term_builders.dup
|
107
|
+
root_term_builders.each do |root_term_builder|
|
108
|
+
root_term_builder.children = self.term_builders.dup
|
108
109
|
root_term_builder.children.delete(root_term_builder.name)
|
109
|
-
end
|
110
|
+
end
|
110
111
|
@term_builders.each_value do |root_builder|
|
111
112
|
terminology.add_term root_builder.build(terminology)
|
112
113
|
end
|
113
114
|
terminology
|
114
115
|
end
|
115
116
|
end
|
116
|
-
|
117
|
+
|
117
118
|
# Terminology Class Definition
|
118
|
-
|
119
|
+
|
119
120
|
attr_accessor :terms, :schema, :namespaces
|
120
|
-
|
121
|
+
|
121
122
|
def initialize(options={})
|
122
123
|
@schema = options.fetch(:schema,nil)
|
123
124
|
@namespaces = options.fetch(:namespaces,{})
|
124
125
|
@terms = {}
|
125
126
|
end
|
126
|
-
|
127
|
+
|
127
128
|
# Add a term to the root of the terminology
|
128
129
|
def add_term(term)
|
129
130
|
@terms[term.name.to_sym] = term
|
130
131
|
end
|
131
|
-
|
132
|
+
|
132
133
|
# Returns true if the current terminology has a term defined at the location indicated by +pointers+ array
|
133
134
|
def has_term?(*pointers)
|
134
135
|
begin
|
@@ -138,7 +139,7 @@ class OM::XML::Terminology
|
|
138
139
|
return false
|
139
140
|
end
|
140
141
|
end
|
141
|
-
|
142
|
+
|
142
143
|
# Returns the Term corresponding to the given _pointer_.
|
143
144
|
# Proxies are not expanded
|
144
145
|
def retrieve_term(*args)
|
@@ -162,11 +163,11 @@ class OM::XML::Terminology
|
|
162
163
|
if current_term.kind_of? OM::XML::NamedTermProxy
|
163
164
|
args = (current_term.proxy_pointer + args).flatten
|
164
165
|
current_term = context.children[args.shift]
|
165
|
-
end
|
166
|
+
end
|
166
167
|
args.empty? ? current_term : retrieve_node_subsequent(args, current_term)
|
167
168
|
end
|
168
169
|
|
169
|
-
|
170
|
+
|
170
171
|
##
|
171
172
|
# This is very similar to retrieve_term, however it expands proxy paths out into their cannonical paths
|
172
173
|
def retrieve_node(*args)
|
@@ -174,7 +175,7 @@ class OM::XML::Terminology
|
|
174
175
|
if current_term.kind_of? OM::XML::NamedTermProxy
|
175
176
|
args = (current_term.proxy_pointer + args).flatten
|
176
177
|
current_term = terms[args.shift]
|
177
|
-
end
|
178
|
+
end
|
178
179
|
args.empty? ? current_term : retrieve_node_subsequent(args, current_term)
|
179
180
|
end
|
180
181
|
|
@@ -187,7 +188,7 @@ class OM::XML::Terminology
|
|
187
188
|
return pointers.first
|
188
189
|
end
|
189
190
|
query_constraints = nil
|
190
|
-
|
191
|
+
|
191
192
|
if pointers.length > 1 && !pointers.last.kind_of?(Symbol)
|
192
193
|
query_constraints = pointers.pop
|
193
194
|
end
|
@@ -199,12 +200,12 @@ class OM::XML::Terminology
|
|
199
200
|
constraint_value = query_constraints
|
200
201
|
xpath_template = term.xpath_constrained
|
201
202
|
xpath_query = eval( '"' + xpath_template + '"' )
|
202
|
-
elsif query_constraints.kind_of?(Hash) && !query_constraints.empty?
|
203
|
-
key_value_pair = query_constraints.first
|
203
|
+
elsif query_constraints.kind_of?(Hash) && !query_constraints.empty?
|
204
|
+
key_value_pair = query_constraints.first
|
204
205
|
constraint_value = key_value_pair.last
|
205
206
|
xpath_template = term.children[key_value_pair.first].xpath_constrained
|
206
|
-
xpath_query = eval( '"' + xpath_template + '"' )
|
207
|
-
else
|
207
|
+
xpath_query = eval( '"' + xpath_template + '"' )
|
208
|
+
else
|
208
209
|
xpath_query = term.xpath
|
209
210
|
end
|
210
211
|
else
|
@@ -212,36 +213,36 @@ class OM::XML::Terminology
|
|
212
213
|
end
|
213
214
|
xpath_query
|
214
215
|
end
|
215
|
-
|
216
|
+
|
216
217
|
# Use the current terminology to generate an xpath with (optional) node indexes for each of the term pointers.
|
217
|
-
# Ex. terminology.xpath_with_indexes({:conference=>0}, {:role=>1}, :text )
|
218
|
+
# Ex. terminology.xpath_with_indexes({:conference=>0}, {:role=>1}, :text )
|
218
219
|
# will yield an xpath like this: '//oxns:name[@type="conference"][1]/oxns:role[2]/oxns:roleTerm[@type="text"]'
|
219
220
|
def xpath_with_indexes(*pointers)
|
220
221
|
OM::XML::TermXpathGenerator.generate_xpath_with_indexes(self, *pointers)
|
221
222
|
end
|
222
|
-
|
223
|
+
|
223
224
|
# Retrieves a Term corresponding to +term_pointers+ and return the corresponding xml_builder_template for that term.
|
224
225
|
# The resulting xml_builder_template can be passed as a block into Nokogiri::XML::Builder.new
|
225
226
|
#
|
226
227
|
# +term_pointers+ point to the Term you want to generate a builder template for
|
227
228
|
# If the last term_pointer is a String or a Hash, it will be passed into the Term's xml_builder_template method as extra_opts
|
228
229
|
# see also: Term.xml_builder_template
|
229
|
-
def xml_builder_template(*term_pointers)
|
230
|
+
def xml_builder_template(*term_pointers)
|
230
231
|
extra_opts = {}
|
231
|
-
|
232
|
+
|
232
233
|
if term_pointers.length > 1 && !term_pointers.last.kind_of?(Symbol)
|
233
234
|
extra_opts = term_pointers.pop
|
234
235
|
end
|
235
|
-
|
236
|
+
|
236
237
|
term = retrieve_term(*term_pointers)
|
237
238
|
return term.xml_builder_template(extra_opts)
|
238
239
|
end
|
239
|
-
|
240
|
+
|
240
241
|
# Returns an array of Terms that have been marked as "root" terms
|
241
242
|
def root_terms
|
242
243
|
terms.values.select {|term| term.is_root_term? }
|
243
244
|
end
|
244
|
-
|
245
|
+
|
245
246
|
# Return an XML representation of the Terminology and its terms
|
246
247
|
# @param [Hash] options, the term will be added to it. If :children=>false, skips rendering child Terms
|
247
248
|
# @param [Nokogiri::XML::Document] (optional) document to insert the term xml into
|
@@ -263,24 +264,24 @@ class OM::XML::Terminology
|
|
263
264
|
}
|
264
265
|
end
|
265
266
|
}
|
266
|
-
xml.terms
|
267
|
+
xml.terms
|
267
268
|
}
|
268
269
|
end
|
269
270
|
document = builder.doc
|
270
271
|
terms.values.each {|term| term.to_xml(options,document.xpath("//terms").first)}
|
271
272
|
return document
|
272
273
|
end
|
273
|
-
|
274
|
+
|
274
275
|
def self.term_generic_name(*pointers)
|
275
276
|
pointers_to_flat_array(pointers, false).join("_")
|
276
277
|
end
|
277
|
-
|
278
|
+
|
278
279
|
def self.term_hierarchical_name(*pointers)
|
279
280
|
pointers_to_flat_array(pointers, true).join("_")
|
280
281
|
end
|
281
|
-
|
282
|
+
|
282
283
|
def self.pointers_to_flat_array(pointers, include_indices=true)
|
283
284
|
OM.pointers_to_flat_array(pointers, include_indices)
|
284
285
|
end
|
285
|
-
|
286
|
+
|
286
287
|
end
|
data/spec/unit/term_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
2
2
|
require "om"
|
3
3
|
|
4
4
|
describe "OM::XML::Term" do
|
5
|
-
|
5
|
+
|
6
6
|
before(:each) do
|
7
7
|
@test_name_part = OM::XML::Term.new(:namePart, {}).generate_xpath_queries!
|
8
8
|
@test_volume = OM::XML::Term.new(:volume, :path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
|
@@ -10,11 +10,8 @@ describe "OM::XML::Term" do
|
|
10
10
|
@test_affiliation = OM::XML::Term.new(:affiliation)
|
11
11
|
@test_role_code = OM::XML::Term.new(:roleTerm, :attributes=>{:type=>"code"})
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
describe '#new' do
|
15
|
-
it "should set default values" do
|
16
|
-
@test_name_part.namespace_prefix.should == "oxns"
|
17
|
-
end
|
18
15
|
it "should set path from mapper name if no path is provided" do
|
19
16
|
@test_name_part.path.should == "namePart"
|
20
17
|
end
|
@@ -25,14 +22,14 @@ describe "OM::XML::Term" do
|
|
25
22
|
local_mapping.xpath_constrained.should be_nil
|
26
23
|
end
|
27
24
|
end
|
28
|
-
|
25
|
+
|
29
26
|
describe 'inner_xml' do
|
30
27
|
it "should be a kind of Nokogiri::XML::Node" do
|
31
28
|
pending
|
32
29
|
@test_mapping.inner_xml.should be_kind_of(Nokogiri::XML::Node)
|
33
30
|
end
|
34
31
|
end
|
35
|
-
|
32
|
+
|
36
33
|
describe '#from_node' do
|
37
34
|
it "should create a mapper from a nokogiri node" do
|
38
35
|
pending "probably should do this in the Builder"
|
@@ -52,7 +49,7 @@ describe "OM::XML::Term" do
|
|
52
49
|
mapper.path.should == "name"
|
53
50
|
mapper.attributes.should == {:type=>"personal"}
|
54
51
|
mapper.internal_xml.should == node
|
55
|
-
|
52
|
+
|
56
53
|
child = mapper.children[:first_name]
|
57
54
|
|
58
55
|
child.name.should == :first_name
|
@@ -61,35 +58,35 @@ describe "OM::XML::Term" do
|
|
61
58
|
child.internal_xml.should == node.xpath("./mapper").first
|
62
59
|
end
|
63
60
|
end
|
64
|
-
|
61
|
+
|
65
62
|
describe ".label" do
|
66
63
|
it "should default to the mapper name with underscores converted to spaces"
|
67
64
|
end
|
68
|
-
|
65
|
+
|
69
66
|
describe ".retrieve_term" do
|
70
67
|
it "should crawl down into mapper children to find the desired term" do
|
71
68
|
mock_role = mock("mapper", :children =>{:text=>"the target"})
|
72
|
-
mock_conference = mock("mapper", :children =>{:role=>mock_role})
|
73
|
-
@test_name_part.expects(:children).returns({:conference=>mock_conference})
|
69
|
+
mock_conference = mock("mapper", :children =>{:role=>mock_role})
|
70
|
+
@test_name_part.expects(:children).returns({:conference=>mock_conference})
|
74
71
|
@test_name_part.retrieve_term(:conference, :role, :text).should == "the target"
|
75
72
|
end
|
76
73
|
it "should return an empty hash if no term can be found" do
|
77
74
|
@test_name_part.retrieve_term(:journal, :issue, :end_page).should == nil
|
78
75
|
end
|
79
76
|
end
|
80
|
-
|
77
|
+
|
81
78
|
describe 'inner_xml' do
|
82
79
|
it "should be a kind of Nokogiri::XML::Node" do
|
83
80
|
pending
|
84
81
|
@test_name_part.inner_xml.should be_kind_of(Nokogiri::XML::Node)
|
85
82
|
end
|
86
83
|
end
|
87
|
-
|
84
|
+
|
88
85
|
describe "getters/setters" do
|
89
86
|
it "should set the corresponding .settings value and return the current value" do
|
90
87
|
[:path, :index_as, :required, :data_type, :variant_of, :path, :attributes, :default_content_path, :namespace_prefix].each do |method_name|
|
91
88
|
@test_name_part.send(method_name.to_s+"=", "#{method_name.to_s}foo").should == "#{method_name.to_s}foo"
|
92
|
-
@test_name_part.send(method_name).should == "#{method_name.to_s}foo"
|
89
|
+
@test_name_part.send(method_name).should == "#{method_name.to_s}foo"
|
93
90
|
end
|
94
91
|
end
|
95
92
|
end
|
@@ -136,21 +133,21 @@ describe "OM::XML::Term" do
|
|
136
133
|
@test_name_part.ancestors.should include(@test_volume)
|
137
134
|
end
|
138
135
|
end
|
139
|
-
|
136
|
+
|
140
137
|
describe "generate_xpath_queries!" do
|
141
138
|
it "should return the current object" do
|
142
139
|
@test_name_part.generate_xpath_queries!.should == @test_name_part
|
143
140
|
end
|
144
|
-
it "should regenerate the xpath values" do
|
141
|
+
it "should regenerate the xpath values" do
|
145
142
|
@test_volume.xpath_relative.should be_nil
|
146
143
|
@test_volume.xpath.should be_nil
|
147
144
|
@test_volume.xpath_constrained.should be_nil
|
148
|
-
|
145
|
+
|
149
146
|
@test_volume.generate_xpath_queries!.should == @test_volume
|
150
|
-
|
151
|
-
@test_volume.xpath_relative.should == '
|
152
|
-
@test_volume.xpath.should == '//
|
153
|
-
@test_volume.xpath_constrained.should == '//
|
147
|
+
|
148
|
+
@test_volume.xpath_relative.should == 'detail[@type="volume"]'
|
149
|
+
@test_volume.xpath.should == '//detail[@type="volume"]'
|
150
|
+
@test_volume.xpath_constrained.should == '//detail[@type="volume" and contains(number, "#{constraint_value}")]'.gsub('"', '\"')
|
154
151
|
end
|
155
152
|
it "should trigger update on any child objects" do
|
156
153
|
mock_child = mock("child term")
|
@@ -159,44 +156,44 @@ describe "OM::XML::Term" do
|
|
159
156
|
@test_name_part.generate_xpath_queries!
|
160
157
|
end
|
161
158
|
end
|
162
|
-
|
159
|
+
|
163
160
|
describe "#xml_builder_template" do
|
164
|
-
|
161
|
+
|
165
162
|
it "should generate a template call for passing into the builder block (assumes 'xml' as the argument for the block)" do
|
166
163
|
@test_date.xml_builder_template.should == 'xml.namePart( \'#{builder_new_value}\', \'type\'=>\'date\' )'
|
167
164
|
@test_affiliation.xml_builder_template.should == 'xml.affiliation( \'#{builder_new_value}\' )'
|
168
165
|
end
|
169
166
|
it "should accept extra options" do
|
170
|
-
marcrelator_role_xml_builder_template = 'xml.roleTerm( \'#{builder_new_value}\', \'type\'=>\'code\', \'authority\'=>\'marcrelator\' )'
|
167
|
+
marcrelator_role_xml_builder_template = 'xml.roleTerm( \'#{builder_new_value}\', \'type\'=>\'code\', \'authority\'=>\'marcrelator\' )'
|
171
168
|
@test_role_code.xml_builder_template(:attributes=>{"authority"=>"marcrelator"}).should == marcrelator_role_xml_builder_template
|
172
169
|
end
|
173
|
-
|
170
|
+
|
174
171
|
it "should work for namespaced nodes" do
|
175
172
|
@ical_date = OM::XML::Term.new(:ical_date, :path=>"ical:date")
|
176
173
|
@ical_date.xml_builder_template.should == "xml[\'ical\'].date( '\#{builder_new_value}' )"
|
177
174
|
@ical_date = OM::XML::Term.new(:ical_date, :path=>"date", :namespace_prefix=>"ical")
|
178
175
|
@ical_date.xml_builder_template.should == "xml[\'ical\'].date( '\#{builder_new_value}' )"
|
179
176
|
end
|
180
|
-
|
181
|
-
it "should work for nodes with default_content_path" do
|
177
|
+
|
178
|
+
it "should work for nodes with default_content_path" do
|
182
179
|
@test_volume.xml_builder_template.should == "xml.detail( \'type\'=>'volume' ) { xml.number( '\#{builder_new_value}' ) }"
|
183
180
|
end
|
184
|
-
|
181
|
+
|
185
182
|
it "should support terms that are attributes" do
|
186
183
|
@type_attribute_term = OM::XML::Term.new(:type_attribute, :path=>{:attribute=>:type})
|
187
184
|
@type_attribute_term.xml_builder_template.should == "xml.@type( '\#{builder_new_value}' )"
|
188
185
|
end
|
189
|
-
|
186
|
+
|
190
187
|
it "should support terms with namespaced attributes" do
|
191
188
|
@french_title = OM::XML::Term.new(:french_title, :path=>"title", :attributes=>{"xml:lang"=>"fre"})
|
192
189
|
@french_title.xml_builder_template.should == "xml.title( '\#{builder_new_value}', 'xml:lang'=>'fre' )"
|
193
190
|
end
|
194
|
-
|
191
|
+
|
195
192
|
it "should support terms that are namespaced attributes" do
|
196
193
|
@xml_lang_attribute_term = OM::XML::Term.new(:xml_lang_attribute, :path=>{:attribute=>"xml:lang"})
|
197
194
|
@xml_lang_attribute_term.xml_builder_template.should == "xml.@xml:lang( '\#{builder_new_value}' )"
|
198
195
|
end
|
199
|
-
|
196
|
+
|
200
197
|
end
|
201
|
-
|
198
|
+
|
202
199
|
end
|