om 1.4.4 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|