om 0.1.10 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +27 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/om/samples/mods_article.rb +64 -0
- data/lib/om/samples.rb +2 -0
- data/lib/om/tree_node.rb +24 -0
- data/lib/om/xml/document.rb +68 -0
- data/lib/om/xml/named_term_proxy.rb +37 -0
- data/lib/om/xml/node_generator.rb +14 -0
- data/lib/om/xml/term.rb +223 -0
- data/lib/om/xml/term_value_operators.rb +182 -0
- data/lib/om/xml/term_xpath_generator.rb +224 -0
- data/lib/om/xml/terminology.rb +216 -0
- data/lib/om/xml/vocabulary.rb +17 -0
- data/lib/om/xml.rb +17 -0
- data/lib/om.rb +2 -0
- data/om.gemspec +37 -6
- data/spec/fixtures/mods_articles/hydrangea_article1.xml +0 -1
- data/spec/integration/rights_metadata_integration_example_spec.rb +27 -15
- data/spec/unit/document_spec.rb +141 -0
- data/spec/unit/generator_spec.rb +7 -1
- data/spec/unit/named_term_proxy_spec.rb +39 -0
- data/spec/unit/node_generator_spec.rb +27 -0
- data/spec/unit/properties_spec.rb +1 -1
- data/spec/unit/term_builder_spec.rb +195 -0
- data/spec/unit/term_spec.rb +180 -0
- data/spec/unit/term_value_operators_spec.rb +361 -0
- data/spec/unit/term_xpath_generator_spec.rb +111 -0
- data/spec/unit/terminology_builder_spec.rb +178 -0
- data/spec/unit/terminology_spec.rb +322 -0
- data/spec/unit/validation_spec.rb +4 -0
- metadata +40 -7
@@ -0,0 +1,361 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::TermValueOperators" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@sample = OM::Samples::ModsArticle.from_xml( fixture( File.join("test_dummy_mods.xml") ) )
|
8
|
+
@article = OM::Samples::ModsArticle.from_xml( fixture( File.join("mods_articles","hydrangea_article1.xml") ) )
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ".term_values" do
|
12
|
+
|
13
|
+
it "should return build an array of values from the nodeset corresponding to the given term" do
|
14
|
+
expected_values = ["Berners-Lee", "Jobs", "Wozniak", "Klimt"]
|
15
|
+
result = @sample.term_values(:person, :last_name)
|
16
|
+
result.length.should == expected_values.length
|
17
|
+
expected_values.each {|v| result.should include(v)}
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
describe ".update_values" do
|
24
|
+
it "should update the xml according to the find_by_terms_and_values in the given hash" do
|
25
|
+
terms_update_hash = {[{":person"=>"0"}, "affiliation"]=>{"0"=>"affiliation1", "1"=>"affiliation2", "2"=>"affiliation3"}, [{:person=>1}, :last_name]=>"Andronicus", [{"person"=>"1"},:first_name]=>["Titus"],[{:person=>1},:role]=>["otherrole1","otherrole2"] }
|
26
|
+
result = @article.update_values(terms_update_hash)
|
27
|
+
result.should == {"person_0_affiliation"=>{"0"=>"affiliation1", "1"=>"affiliation2", "2"=>"affiliation3"}, "person_1_last_name"=>{"0"=>"Andronicus"},"person_1_first_name"=>{"0"=>"Titus"}, "person_1_role"=>{"0"=>"otherrole1","1"=>"otherrole2"}}
|
28
|
+
person_0_affiliation = @article.find_by_terms({:person=>0}, :affiliation)
|
29
|
+
person_0_affiliation[0].text.should == "affiliation1"
|
30
|
+
person_0_affiliation[1].text.should == "affiliation2"
|
31
|
+
person_0_affiliation[2].text.should == "affiliation3"
|
32
|
+
|
33
|
+
person_1_last_names = @article.find_by_terms({:person=>1}, :last_name)
|
34
|
+
person_1_last_names.length.should == 1
|
35
|
+
person_1_last_names.first.text.should == "Andronicus"
|
36
|
+
|
37
|
+
person_1_first_names = @article.find_by_terms({:person=>1}, :first_name)
|
38
|
+
person_1_first_names.first.text.should == "Titus"
|
39
|
+
|
40
|
+
person_1_roles = @article.find_by_terms({:person=>1}, :role)
|
41
|
+
person_1_roles[0].text.should == "otherrole1"
|
42
|
+
person_1_roles[1].text.should == "otherrole2"
|
43
|
+
end
|
44
|
+
it "should call term_value_update if the corresponding node already exists" do
|
45
|
+
@article.expects(:term_value_update).with('//oxns:titleInfo/oxns:title', 0, "My New Title")
|
46
|
+
@article.update_values( {[:title_info, :main_title] => "My New Title"} )
|
47
|
+
end
|
48
|
+
it "should call term_values_append if the corresponding node does not already exist or if the requested index is -1" do
|
49
|
+
expected_args = {
|
50
|
+
:parent_select => OM::Samples::ModsArticle.terminology.xpath_with_indexes(*[{:person=>0}]) ,
|
51
|
+
:child_index => 0,
|
52
|
+
:template => [:person, :role],
|
53
|
+
:values => "My New Role"
|
54
|
+
}
|
55
|
+
@article.expects(:term_values_append).with(expected_args).times(2)
|
56
|
+
@article.update_values( {[{:person=>0}, :role] => {"4"=>"My New Role"}} )
|
57
|
+
@article.update_values( {[{:person=>0}, :role] => {"-1"=>"My New Role"}} )
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should support updating attribute values" do
|
61
|
+
pointer = [:title_info, :language]
|
62
|
+
test_val = "language value"
|
63
|
+
@article.update_values( {pointer=>{"0"=>test_val}} )
|
64
|
+
@article.term_values(*pointer).first.should == test_val
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should not get tripped up on root nodes" do
|
68
|
+
@article.update_values([:title_info]=>{"0"=>"york", "1"=>"mangle","2"=>"mork"})
|
69
|
+
@article.term_values(*[:title_info]).should == ["york", "mangle", "mork"]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should destringify the field key/find_by_terms_and_value pointer" do
|
73
|
+
OM::Samples::ModsArticle.terminology.expects(:xpath_with_indexes).with( *[{:person=>0}, :role]).times(7).returns("//oxns:name[@type=\"personal\"][1]/oxns:role")
|
74
|
+
OM::Samples::ModsArticle.terminology.stubs(:xpath_with_indexes).with( *[{:person=>0}]).returns("//oxns:name[@type=\"personal\"][1]")
|
75
|
+
@article.update_values( { [{":person"=>"0"}, "role"]=>"the role" } )
|
76
|
+
@article.update_values( { [{"person"=>"0"}, "role"]=>"the role" } )
|
77
|
+
@article.update_values( { [{:person=>0}, :role]=>"the role" } )
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should traverse named term proxies transparently" do
|
81
|
+
@article.term_values( :journal, :issue, :start_page).should_not == ["108"]
|
82
|
+
@article.update_values( { ["journal", "issue", "start_page"]=>"108" } )
|
83
|
+
@article.term_values( :journal, :issue, :start_page).should == ["108"]
|
84
|
+
@article.term_values( :journal, :issue, :pages, :start).should == ["108"]
|
85
|
+
end
|
86
|
+
|
87
|
+
### Examples copied over form nokogiri_datastream_spec
|
88
|
+
|
89
|
+
it "should apply submitted hash to corresponding datastream field values" do
|
90
|
+
result = @article.update_values( {[{":person"=>"0"}, "first_name"]=>{"0"=>"Billy", "1"=>"Bob", "2"=>"Joe"} })
|
91
|
+
result.should == {"person_0_first_name"=>{"0"=>"Billy", "1"=>"Bob", "2"=>"Joe"}}
|
92
|
+
# xpath = ds.class.xpath_with_indexes(*field_key)
|
93
|
+
# result = ds.term_values(xpath)
|
94
|
+
@article.term_values({:person=>0}, :first_name).should == ["Billy","Bob","Joe"]
|
95
|
+
@article.term_values('//oxns:name[@type="personal"][1]/oxns:namePart[@type="given"]').should == ["Billy","Bob","Joe"]
|
96
|
+
end
|
97
|
+
it "should support single-value arguments (as opposed to a hash of values with array indexes as keys)" do
|
98
|
+
# In other words, { [:journal, :title_info]=>"dork" } should have the same effect as { [:journal, :title_info]=>{"0"=>"dork"} }
|
99
|
+
result = @article.update_values( { [{":person"=>"0"}, "role"]=>"the role" } )
|
100
|
+
result.should == {"person_0_role"=>{"0"=>"the role"}}
|
101
|
+
@article.term_values({:person=>0}, :role).first.should == "the role"
|
102
|
+
@article.term_values('//oxns:name[@type="personal"][1]/oxns:role').first.should == "the role"
|
103
|
+
end
|
104
|
+
it "should do nothing if field key is a string (must be an array or symbol). Will not accept xpath queries!" do
|
105
|
+
xml_before = @article.to_xml
|
106
|
+
@article.update_values( { "fubar"=>"the role" } ).should == {}
|
107
|
+
@article.to_xml.should == xml_before
|
108
|
+
end
|
109
|
+
it "should do nothing if there is no term corresponding to the given field key" do
|
110
|
+
xml_before = @article.to_xml
|
111
|
+
@article.update_values( { [{"fubar"=>"0"}]=>"the role" } ).should == {}
|
112
|
+
@article.to_xml.should == xml_before
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should work for text fields" do
|
116
|
+
att= {[{"person"=>"0"},"description"]=>{"-1"=>"mork", "1"=>"york"}}
|
117
|
+
result = @article.update_values(att)
|
118
|
+
result.should == {"person_0_description"=>{"0"=>"mork","1"=>"york"}}
|
119
|
+
@article.term_values({:person=>0},:description).should == ['mork', 'york']
|
120
|
+
att= {[{"person"=>"0"},"description"]=>{"-1"=>"dork"}}
|
121
|
+
result2 = @article.update_values(att)
|
122
|
+
result2.should == {"person_0_description"=>{"2"=>"dork"}}
|
123
|
+
@article.term_values({:person=>0},:description).should == ['mork', 'york', 'dork']
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should return the new index of any added values" do
|
127
|
+
@article.term_values({:title_info=>0},:main_title).should == ["ARTICLE TITLE HYDRANGEA ARTICLE 1", "TITLE OF HOST JOURNAL"]
|
128
|
+
result = @article.update_values [{"title_info"=>"0"},"main_title"]=>{"-1"=>"mork"}
|
129
|
+
result.should == {"title_info_0_main_title"=>{"2"=>"mork"}}
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should return accurate response when multiple values have been added in a single run" do
|
133
|
+
pending "THIS SHOULD BE FIXED"
|
134
|
+
att= {[:journal, :title_info]=>{"-1"=>"mork", "0"=>"york"}}
|
135
|
+
@article.update_values(att).should == {"journal_title_info"=>{"0"=>"york", "1"=>"mork"}}
|
136
|
+
@article.term_values(*att.keys.first).should == ["york", "mork"]
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should append nodes at the specified index if possible" do
|
140
|
+
@article.update_values([:journal, :title_info]=>["all", "for", "the"])
|
141
|
+
att = {[:journal, :title_info]=>{"3"=>'glory'}}
|
142
|
+
result = @article.update_values(att)
|
143
|
+
result.should == {"journal_title_info"=>{"3"=>"glory"}}
|
144
|
+
@article.term_values(:journal, :title_info).should == ["all", "for", "the", "glory"]
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should append values to the end of the array if the specified index is higher than the length of the values array" do
|
148
|
+
att = {[:journal, :issue, :pages, :end]=>{"3"=>'108'}}
|
149
|
+
@article.term_values(:journal, :issue, :pages, :end).should == []
|
150
|
+
result = @article.update_values(att)
|
151
|
+
result.should == {"journal_issue_pages_end"=>{"0"=>"108"}}
|
152
|
+
@article.term_values(:journal, :issue, :pages, :end).should == ["108"]
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should allow deleting of values and should delete values so that to_xml does not return emtpy nodes" do
|
156
|
+
att= {[:journal, :title_info]=>{"0"=>"york", "1"=>"mangle","2"=>"mork"}}
|
157
|
+
@article.update_values(att)
|
158
|
+
@article.term_values(:journal, :title_info).should == ['york', 'mangle', 'mork']
|
159
|
+
|
160
|
+
@article.update_values({[:journal, :title_info]=>{"1"=>""}})
|
161
|
+
@article.term_values(:journal, :title_info).should == ['york', 'mork']
|
162
|
+
|
163
|
+
@article.update_values({[:journal, :title_info]=>{"0"=>:delete}})
|
164
|
+
@article.term_values(:journal, :title_info).should == ['mork']
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
describe ".term_values_append" do
|
170
|
+
|
171
|
+
it "looks up the parent using :parent_select, uses :child_index to choose the parent node from the result set, uses :template to build the node(s) to be inserted, inserts the :values(s) into the node(s) and adds the node(s) to the parent" do
|
172
|
+
@sample.term_values_append(
|
173
|
+
:parent_select => [:person, {:first_name=>"Tim", :last_name=>"Berners-Lee"}] ,
|
174
|
+
:child_index => :first,
|
175
|
+
:template => [:person, :affiliation],
|
176
|
+
:values => ["my new value", "another new value"]
|
177
|
+
)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should accept parent_select and template [term_reference, find_by_terms_and_value_opts] as argument arrays for generators/find_by_terms_and_values" do
|
181
|
+
# this appends two affiliation nodes into the first person node whose name is Tim Berners-Lee
|
182
|
+
expected_result = '<ns3:name type="personal">
|
183
|
+
<ns3:namePart type="family">Berners-Lee</ns3:namePart>
|
184
|
+
<ns3:namePart type="given">Tim</ns3:namePart>
|
185
|
+
<ns3:role>
|
186
|
+
<ns3:roleTerm type="text" authority="marcrelator">creator</ns3:roleTerm>
|
187
|
+
<ns3:roleTerm type="code" authority="marcrelator">cre</ns3:roleTerm>
|
188
|
+
</ns3:role>
|
189
|
+
<ns3:affiliation>my new value</ns3:affiliation><ns3:affiliation>another new value</ns3:affiliation></ns3:name>'
|
190
|
+
|
191
|
+
@sample.term_values_append(
|
192
|
+
:parent_select => [:person, {:first_name=>"Tim", :last_name=>"Berners-Lee"}] ,
|
193
|
+
:child_index => :first,
|
194
|
+
:template => [:person, :affiliation],
|
195
|
+
:values => ["my new value", "another new value"]
|
196
|
+
).to_xml.should == expected_result
|
197
|
+
|
198
|
+
@sample.find_by_terms(:person, {:first_name=>"Tim", :last_name=>"Berners-Lee"}).first.to_xml.should == expected_result
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should support adding attribute values" do
|
202
|
+
pending
|
203
|
+
pointer = [:title_info, :language]
|
204
|
+
test_val = "language value"
|
205
|
+
@article.term_values_append(
|
206
|
+
:parent_select => :title_info,
|
207
|
+
:child_index => :first,
|
208
|
+
:template => [:title_info, :language],
|
209
|
+
:values => test_val
|
210
|
+
)
|
211
|
+
@article.term_values(*pointer).first.should == test_val
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should accept symbols as arguments for generators/find_by_terms_and_values" do
|
215
|
+
# this appends a role of "my role" into the third "person" node in the document
|
216
|
+
@sample.term_values_append(
|
217
|
+
:parent_select => :person ,
|
218
|
+
:child_index => 3,
|
219
|
+
:template => :role,
|
220
|
+
:values => "my role"
|
221
|
+
).to_xml.should #== expected_result
|
222
|
+
@sample.find_by_terms(:person)[3].search("./ns3:role[3]").first.text.should == "my role"
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should accept parent_select as an (xpath) string and template as a (template) string" do
|
226
|
+
# this uses the provided template to add a node into the first node resulting from the xpath '//oxns:name[@type="personal"]'
|
227
|
+
expected_result = "<ns3:name type=\"personal\">\n <ns3:namePart type=\"family\">Berners-Lee</ns3:namePart>\n <ns3:namePart type=\"given\">Tim</ns3:namePart>\n <ns3:role>\n <ns3:roleTerm type=\"text\" authority=\"marcrelator\">creator</ns3:roleTerm>\n <ns3:roleTerm type=\"code\" authority=\"marcrelator\">cre</ns3:roleTerm>\n </ns3:role>\n <ns3:role type=\"code\" authority=\"marcrelator\"><ns3:roleTerm>creator</ns3:roleTerm></ns3:role></ns3:name>"
|
228
|
+
|
229
|
+
@sample.ng_xml.xpath('//oxns:name[@type="personal" and position()=1]/oxns:role', @sample.ox_namespaces).length.should == 1
|
230
|
+
|
231
|
+
@sample.term_values_append(
|
232
|
+
:parent_select =>'//oxns:name[@type="personal"]',
|
233
|
+
:child_index => 0,
|
234
|
+
:template => 'xml.role { xml.roleTerm( \'#{builder_new_value}\', :type=>\'code\', :authority=>\'marcrelator\') }',
|
235
|
+
:values => "founder"
|
236
|
+
)
|
237
|
+
|
238
|
+
@sample.ng_xml.xpath('//oxns:name[@type="personal" and position()=1]/oxns:role', @sample.ox_namespaces).length.should == 2
|
239
|
+
@sample.ng_xml.xpath('//oxns:name[@type="personal" and position()=1]/oxns:role[last()]/oxns:roleTerm', @sample.ox_namespaces).first.text.should == "founder"
|
240
|
+
|
241
|
+
# @sample.find_by_terms_and_value(:person).first.to_xml.should == expected_result
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should support more complex mixing & matching" do
|
245
|
+
pending "not working because builder_template is not returning the correct template (returns builder for role instead of roleTerm)"
|
246
|
+
@sample.ng_xml.xpath('//oxns:name[@type="personal"][2]/oxns:role[1]/oxns:roleTerm', @sample.ox_namespaces).length.should == 2
|
247
|
+
@sample.term_values_append(
|
248
|
+
:parent_select =>'//oxns:name[@type="personal"][2]/oxns:role',
|
249
|
+
:child_index => 0,
|
250
|
+
:template => [ :person, :role, :text, {:attributes=>{"authority"=>"marcrelator"}} ],
|
251
|
+
:values => "foo"
|
252
|
+
)
|
253
|
+
|
254
|
+
@sample.ng_xml.xpath('//oxns:name[@type="personal"][2]/oxns:role[1]/oxns:roleTerm', @sample.ox_namespaces).length.should == 3
|
255
|
+
@sample.find_by_terms({:person=>1},:role)[0].search("./oxns:roleTerm[@type=\"text\" and @authority=\"marcrelator\"]", @sample.ox_namespaces).first.text.should == "foo"
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should raise exception if no node corresponds to the provided :parent_select and :child_index"
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
describe ".term_value_update" do
|
263
|
+
|
264
|
+
it "should accept an xpath as :parent_select" do
|
265
|
+
sample_xpath = '//oxns:name[@type="personal"][4]/oxns:role/oxns:roleTerm[@type="text"]'
|
266
|
+
@sample.term_value_update(sample_xpath,1,"artist")
|
267
|
+
@sample.ng_xml.xpath(sample_xpath, @sample.ox_namespaces)[1].text.should == "artist"
|
268
|
+
end
|
269
|
+
|
270
|
+
it "if :select is provided, should update the first node provided by that xpath statement" do
|
271
|
+
sample_xpath = '//oxns:name[@type="personal"][1]/oxns:namePart[@type="given"]'
|
272
|
+
@sample.term_value_update(sample_xpath,0,"Timmeh")
|
273
|
+
@sample.ng_xml.xpath(sample_xpath, @sample.ox_namespaces).first.text.should == "Timmeh"
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should replace the existing node if you pass a template and values" do
|
277
|
+
pending
|
278
|
+
@sample.term_value_update(
|
279
|
+
:parent_select =>'//oxns:name[@type="personal"]',
|
280
|
+
:child_index => 1,
|
281
|
+
:template => [ :person, :role, {:attributes=>{"type"=>"code", "authority"=>"marcrelator"}} ],
|
282
|
+
:value => "foo"
|
283
|
+
)
|
284
|
+
1.should == 2
|
285
|
+
end
|
286
|
+
it "should delete nodes if value is :delete or empty string" do
|
287
|
+
@article.update_values([:title_info]=>{"0"=>"york", "1"=>"mangle","2"=>"mork"})
|
288
|
+
xpath = @article.class.terminology.xpath_for(:title_info)
|
289
|
+
|
290
|
+
@article.term_value_update([:title_info], 1, "")
|
291
|
+
@article.term_values(:title_info).should == ['york', 'mork']
|
292
|
+
|
293
|
+
@article.term_value_update([:title_info], 1, :delete)
|
294
|
+
@article.term_values(:title_info).should == ['york']
|
295
|
+
end
|
296
|
+
it "should create empty nodes if value is nil" do
|
297
|
+
@article.update_values([:title_info]=>{"0"=>"york", "1"=>nil,"2"=>"mork"})
|
298
|
+
@article.term_values(:title_info).should == ['york', "", "mork"]
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe ".term_value_delete" do
|
303
|
+
it "should accept an xpath query as :select option" do
|
304
|
+
generic_xpath = '//oxns:name[@type="personal" and position()=4]/oxns:role'
|
305
|
+
specific_xpath = '//oxns:name[@type="personal" and position()=4]/oxns:role[oxns:roleTerm="visionary"]'
|
306
|
+
select_xpath = '//oxns:name[@type="personal" and position()=4]/oxns:role[last()]'
|
307
|
+
|
308
|
+
# Check that we're starting with 2 roles
|
309
|
+
# Check that the specific node we want to delete exists
|
310
|
+
@sample.find_by_terms_and_value(generic_xpath).length.should == 2
|
311
|
+
@sample.find_by_terms_and_value(specific_xpath).length.should == 1
|
312
|
+
|
313
|
+
@sample.term_value_delete(
|
314
|
+
:select =>select_xpath
|
315
|
+
)
|
316
|
+
# Check that we're finishing with 1 role
|
317
|
+
@sample.find_by_terms_and_value(generic_xpath).length.should == 1
|
318
|
+
# Check that the specific node we want to delete no longer exists
|
319
|
+
@sample.find_by_terms_and_value(specific_xpath).length.should == 0
|
320
|
+
end
|
321
|
+
it "should accept :parent_select, :parent_index and :child_index options instead of a :select" do
|
322
|
+
|
323
|
+
generic_xpath = '//oxns:name[@type="personal" and position()=4]/oxns:role/oxns:roleTerm'
|
324
|
+
specific_xpath = '//oxns:name[@type="personal" and position()=4]/oxns:role[oxns:roleTerm="visionary"]'
|
325
|
+
|
326
|
+
# Check that we're starting with 2 roles
|
327
|
+
# Check that the specific node we want to delete exists
|
328
|
+
@sample.find_by_terms_and_value(generic_xpath).length.should == 4
|
329
|
+
@sample.find_by_terms_and_value(specific_xpath).length.should == 1
|
330
|
+
|
331
|
+
# this is attempting to delete the last child (in this case roleTerm) from the 3rd role in the document.
|
332
|
+
@sample.term_value_delete(
|
333
|
+
:parent_select => [:person, :role],
|
334
|
+
:parent_index => 3,
|
335
|
+
:child_index => :last
|
336
|
+
)
|
337
|
+
|
338
|
+
# Check that we're finishing with 1 role
|
339
|
+
@sample.find_by_terms_and_value(generic_xpath).length.should == 3
|
340
|
+
# Check that the specific node we want to delete no longer exists
|
341
|
+
@sample.find_by_terms_and_value(specific_xpath).length.should == 1
|
342
|
+
end
|
343
|
+
it "should work if only :parent_select and :child_index are provided" do
|
344
|
+
generic_xpath = '//oxns:name[@type="personal"]/oxns:role'
|
345
|
+
# specific_xpath = '//oxns:name[@type="personal"]/oxns:role'
|
346
|
+
|
347
|
+
# Check that we're starting with 2 roles
|
348
|
+
# Check that the specific node we want to delete exists
|
349
|
+
@sample.find_by_terms_and_value(generic_xpath).length.should == 4
|
350
|
+
# @sample.find_by_terms_and_value(specific_xpath).length.should == 1
|
351
|
+
|
352
|
+
@sample.term_value_delete(
|
353
|
+
:parent_select => [:person, :role],
|
354
|
+
:child_index => 3
|
355
|
+
)
|
356
|
+
# Check that we're finishing with 1 role
|
357
|
+
@sample.find_by_terms_and_value(generic_xpath).length.should == 3
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::TermXpathGeneratorSpec" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
builder = OM::XML::Terminology::Builder.new do |t|
|
8
|
+
t.root(:path=>"mods")
|
9
|
+
t.name_ {
|
10
|
+
t.family_name(:path=>"namePart", :attributes=>{:type=>"family"})
|
11
|
+
t.first_name(:path=>"namePart", :attributes=>{:type=>"given"}, :label=>"first name")
|
12
|
+
}
|
13
|
+
# lookup :person, :first_name
|
14
|
+
t.person(:ref=>:name, :attributes=>{:type=>"personal"})
|
15
|
+
end
|
16
|
+
@sample_terminology = builder.build
|
17
|
+
@rootless_terminology = OM::XML::Terminology.new
|
18
|
+
end
|
19
|
+
|
20
|
+
before(:each) do
|
21
|
+
@test_term = OM::XML::Term.new(:terms_of_address, :path=>"namePart", :attributes=>{:type=>"termsOfAddress"})
|
22
|
+
@test_term_with_default_path = OM::XML::Term.new(:volume, :path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
|
23
|
+
@test_role_text = OM::XML::Term.new(:role_text, :path=>"roleTerm", :attributes=>{:type=>"text"})
|
24
|
+
@test_lang_attribute = OM::XML::Term.new(:language, :path=>{:attribute=>"lang"})
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should support terms that are pointers to attribute values" do
|
28
|
+
OM::XML::TermXpathGenerator.generate_xpath(@test_lang_attribute, :absolute).should == "//@lang"
|
29
|
+
OM::XML::TermXpathGenerator.generate_xpath(@test_lang_attribute, :relative).should == "@lang"
|
30
|
+
OM::XML::TermXpathGenerator.generate_xpath(@test_lang_attribute, :constrained).should == '//@lang[contains(., "#{constraint_value}")]'.gsub('"', '\"')
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "generate_xpath" do
|
34
|
+
it "should generate an xpath based on the given mapper and options" do
|
35
|
+
OM::XML::TermXpathGenerator.expects(:generate_absolute_xpath).with(@test_term)
|
36
|
+
OM::XML::TermXpathGenerator.generate_xpath(@test_term, :absolute)
|
37
|
+
|
38
|
+
OM::XML::TermXpathGenerator.expects(:generate_relative_xpath).with(@test_term)
|
39
|
+
OM::XML::TermXpathGenerator.generate_xpath(@test_term, :relative)
|
40
|
+
|
41
|
+
OM::XML::TermXpathGenerator.expects(:generate_constrained_xpath).with(@test_term)
|
42
|
+
OM::XML::TermXpathGenerator.generate_xpath(@test_term, :constrained)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "generate_relative_xpath" do
|
47
|
+
it "should generate a relative xpath based on the given mapper" do
|
48
|
+
OM::XML::TermXpathGenerator.generate_relative_xpath(@test_term).should == 'oxns:namePart[@type="termsOfAddress"]'
|
49
|
+
end
|
50
|
+
it "should support mappers without namespaces" do
|
51
|
+
@test_term.namespace_prefix = nil
|
52
|
+
OM::XML::TermXpathGenerator.generate_relative_xpath(@test_term).should == 'namePart[@type="termsOfAddress"]'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "generate_absolute_xpath" do
|
57
|
+
it "should generate an absolute xpath based on the given mapper" do
|
58
|
+
OM::XML::TermXpathGenerator.generate_absolute_xpath(@test_term).should == '//oxns:namePart[@type="termsOfAddress"]'
|
59
|
+
end
|
60
|
+
it "should prepend the xpath for any parent nodes" do
|
61
|
+
mock_parent_mapper = mock("Term", :xpath_absolute=>'//oxns:name[@type="conference"]/oxns:role')
|
62
|
+
@test_role_text.stubs(:parent).returns(mock_parent_mapper)
|
63
|
+
OM::XML::TermXpathGenerator.generate_absolute_xpath(@test_role_text).should == '//oxns:name[@type="conference"]/oxns:role/oxns:roleTerm[@type="text"]'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "generate_constrained_xpath" do
|
68
|
+
it "should generate a constrained xpath based on the given mapper" do
|
69
|
+
OM::XML::TermXpathGenerator.generate_constrained_xpath(@test_term).should == '//oxns:namePart[@type="termsOfAddress" and contains(., "#{constraint_value}")]'.gsub('"', '\"')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should support mappers without namespaces" do
|
74
|
+
@test_term.namespace_prefix = nil
|
75
|
+
OM::XML::TermXpathGenerator.generate_relative_xpath(@test_term).should == 'namePart[@type="termsOfAddress"]'
|
76
|
+
OM::XML::TermXpathGenerator.generate_absolute_xpath(@test_term).should == '//namePart[@type="termsOfAddress"]'
|
77
|
+
OM::XML::TermXpathGenerator.generate_constrained_xpath(@test_term).should == '//namePart[@type="termsOfAddress" and contains(., "#{constraint_value}")]'.gsub('"', '\"')
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "generate_xpath_with_indexes" do
|
81
|
+
it "should accept multiple constraints" do
|
82
|
+
generated_xpath = OM::XML::TermXpathGenerator.generate_xpath_with_indexes( @sample_terminology, :person, {:first_name=>"Tim", :family_name=>"Berners-Lee"} )
|
83
|
+
# expect an xpath that looks like this: '//oxns:name[@type="personal" and contains(oxns:namePart[@type="family"], "Berners-Lee") and contains(oxns:namePart[@type="given"], "Tim")]'
|
84
|
+
# can't use string comparison because the contains functions can arrive in any order
|
85
|
+
generated_xpath.should match( /\/\/oxns:name\[@type=\"personal\".*and contains\(oxns:namePart\[@type=\"given\"\], \"Tim\"\).*\]/ )
|
86
|
+
generated_xpath.should match( /\/\/oxns:name\[@type=\"personal\".*and contains\(oxns:namePart\[@type=\"family\"\], \"Berners-Lee\"\).*\]/ )
|
87
|
+
end
|
88
|
+
it "should support xpath queries as argument" do
|
89
|
+
OM::XML::TermXpathGenerator.generate_xpath_with_indexes(@sample_terminology, '//oxns:name[@type="personal"][1]/oxns:namePart').should == '//oxns:name[@type="personal"][1]/oxns:namePart'
|
90
|
+
end
|
91
|
+
it "should return the xpath of the terminology's root node if term pointer is nil" do
|
92
|
+
OM::XML::TermXpathGenerator.generate_xpath_with_indexes( @sample_terminology, nil ).should == @sample_terminology.root_terms.first.xpath
|
93
|
+
end
|
94
|
+
it "should return / if term pointer is nil and the terminology does not have a root term defined" do
|
95
|
+
OM::XML::TermXpathGenerator.generate_xpath_with_indexes( @rootless_terminology, nil ).should == "/"
|
96
|
+
end
|
97
|
+
it "should destringify term pointers before using them" do
|
98
|
+
generated_xpath = OM::XML::TermXpathGenerator.generate_xpath_with_indexes( @sample_terminology, {"person"=>"1"}, "first_name" ).should == '//oxns:name[@type="personal"][2]/oxns:namePart[@type="given"]'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should support mappers with default_content_path" do
|
103
|
+
pending "need to implement mapper_set first"
|
104
|
+
#@test_term_with_default_path = OM::XML::Term.new(:volume, :path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
|
105
|
+
|
106
|
+
OM::XML::TermXpathGenerator.generate_relative_xpath(@test_term_with_default_path).should == 'oxns:detail[@type="volume"]'
|
107
|
+
OM::XML::TermXpathGenerator.generate_absolute_xpath(@test_term_with_default_path).should == '//oxns:detail[@type="volume"]'
|
108
|
+
OM::XML::TermXpathGenerator.generate_constrained_xpath(@test_term_with_default_path).should == '//oxns:detail[contains(oxns:number[@type="volume"], "#{constraint_value}")]'.gsub('"', '\"')
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::Terminology::Builder" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@test_builder = OM::XML::Terminology::Builder.new
|
8
|
+
|
9
|
+
@builder_with_block = OM::XML::Terminology::Builder.new do |t|
|
10
|
+
t.root(:path=>"mods", :xmlns=>"http://www.loc.gov/mods/v3", :schema=>"http://www.loc.gov/standards/mods/v3/mods-3-2.xsd")
|
11
|
+
|
12
|
+
t.title_info(:path=>"titleInfo") {
|
13
|
+
t.main_title(:path=>"title", :label=>"title")
|
14
|
+
t.language(:path=>{:attribute=>"lang"})
|
15
|
+
}
|
16
|
+
# t.title(:path=>"titleInfo", :default_content_path=>"title") {
|
17
|
+
# t.@language(:path=>{:attribute=>"lang"})
|
18
|
+
# }
|
19
|
+
# This is a mods:name. The underscore is purely to avoid namespace conflicts.
|
20
|
+
t.name_ {
|
21
|
+
# this is a namepart
|
22
|
+
t.namePart(:index_as=>[:searchable, :displayable, :facetable, :sortable], :required=>:true, :type=>:string, :label=>"generic name")
|
23
|
+
# affiliations are great
|
24
|
+
t.affiliation
|
25
|
+
t.displayForm
|
26
|
+
t.role(:ref=>[:role])
|
27
|
+
t.description
|
28
|
+
t.date(:path=>"namePart", :attributes=>{:type=>"date"})
|
29
|
+
t.family_name(:path=>"namePart", :attributes=>{:type=>"family"})
|
30
|
+
t.given_name(:path=>"namePart", :attributes=>{:type=>"given"}, :label=>"first name")
|
31
|
+
t.terms_of_address(:path=>"namePart", :attributes=>{:type=>"termsOfAddress"})
|
32
|
+
}
|
33
|
+
# lookup :person, :first_name
|
34
|
+
t.person(:ref=>:name, :attributes=>{:type=>"personal"})
|
35
|
+
|
36
|
+
t.role {
|
37
|
+
t.text(:path=>"roleTerm",:attributes=>{:type=>"text"})
|
38
|
+
t.code(:path=>"roleTerm",:attributes=>{:type=>"code"})
|
39
|
+
}
|
40
|
+
t.journal(:path=>'relatedItem', :attributes=>{:type=>"host"}) {
|
41
|
+
t.title_info
|
42
|
+
t.origin_info(:path=>"originInfo")
|
43
|
+
t.issn(:path=>"identifier", :attributes=>{:type=>"issn"})
|
44
|
+
t.issue(:ref=>[:issue])
|
45
|
+
}
|
46
|
+
t.issue(:path=>"part") {
|
47
|
+
t.volume(:path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
|
48
|
+
t.level(:path=>"detail", :attributes=>{:type=>"number"}, :default_content_path=>"number")
|
49
|
+
t.pages(:path=>"extent", :attributes=>{:unit=>"pages"}) {
|
50
|
+
t.start
|
51
|
+
t.end
|
52
|
+
}
|
53
|
+
# t.start_page(:path=>"pages", :attributes=>{:type=>"start"})
|
54
|
+
# t.end_page(:path=>"pages", :attributes=>{:type=>"end"})
|
55
|
+
# t.start_page(:path=>"extent", :attributes=>{:unit=>"pages"}, :default_content_path => "start")
|
56
|
+
# t.end_page(:path=>"extent", :attributes=>{:unit=>"pages"}, :default_content_path => "end")
|
57
|
+
t.publication_date(:path=>"date")
|
58
|
+
# t.my_absolute_proxy(:proxy_absolute=>[:name, :role]) # this should always point to [:name, :role]
|
59
|
+
t.start_page(:proxy=>[:pages, :start])
|
60
|
+
t.start_page(:proxy=>[:pages, :end])
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# @test_full_terminology = @builder_with_block.build
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#new' do
|
69
|
+
it "should return an instance of OM::XML::Terminology::Builder" do
|
70
|
+
OM::XML::Terminology::Builder.new.should be_instance_of OM::XML::Terminology::Builder
|
71
|
+
end
|
72
|
+
it "should process the input block, creating a new Term Builder for each entry and its children" do
|
73
|
+
expected_root_terms = [:mods, :title_info, :issue, :person, :name, :journal, :role]
|
74
|
+
expected_root_terms.each do |name|
|
75
|
+
@builder_with_block.term_builders.should have_key(name)
|
76
|
+
end
|
77
|
+
@builder_with_block.term_builders.length.should == expected_root_terms.length
|
78
|
+
|
79
|
+
@builder_with_block.term_builders[:journal].should be_instance_of OM::XML::Term::Builder
|
80
|
+
@builder_with_block.term_builders[:journal].settings[:path].should == "relatedItem"
|
81
|
+
@builder_with_block.term_builders[:journal].settings[:attributes].should == {:type=>"host"}
|
82
|
+
|
83
|
+
@builder_with_block.term_builders[:journal].children[:issn].should be_instance_of OM::XML::Term::Builder
|
84
|
+
@builder_with_block.term_builders[:journal].children[:issn].settings[:path].should == "identifier"
|
85
|
+
@builder_with_block.term_builders[:journal].children[:issn].settings[:attributes].should == {:type=>"issn"}
|
86
|
+
end
|
87
|
+
it "should clip the underscore off the end of any Term names" do
|
88
|
+
@builder_with_block.term_builders[:name].should be_instance_of OM::XML::Term::Builder
|
89
|
+
@builder_with_block.term_builders[:name].name.should == :name
|
90
|
+
|
91
|
+
@builder_with_block.term_builders[:name].children[:date].should be_instance_of OM::XML::Term::Builder
|
92
|
+
@builder_with_block.term_builders[:name].children[:date].settings[:path].should == "namePart"
|
93
|
+
@builder_with_block.term_builders[:name].children[:date].settings[:attributes].should == {:type=>"date"}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#from_xml' do
|
98
|
+
it "should let you load mappings from an xml file" do
|
99
|
+
pending
|
100
|
+
vocab = OM::XML::Terminology.from_xml( fixture("sample_mappings.xml") )
|
101
|
+
vocab.should be_instance_of OM::XML::Terminology
|
102
|
+
vocab.mappers.should == {}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe ".retrieve_term_builder" do
|
107
|
+
it "should support looking up Term Builders by pointer" do
|
108
|
+
expected = @builder_with_block.term_builders[:name].children[:date]
|
109
|
+
@builder_with_block.retrieve_term_builder(:name, :date).should == expected
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "build" do
|
114
|
+
it "should generate the new Terminology, calling .build on its Term builders"
|
115
|
+
it "should resolve :refs" do
|
116
|
+
@builder_with_block.retrieve_term_builder(:name, :role).settings[:ref].should == [:role]
|
117
|
+
@builder_with_block.retrieve_term_builder(:role).children[:text].should be_instance_of OM::XML::Term::Builder
|
118
|
+
|
119
|
+
built_terminology = @builder_with_block.build
|
120
|
+
|
121
|
+
built_terminology.retrieve_term(:name, :role, :text).should be_instance_of OM::XML::Term
|
122
|
+
built_terminology.retrieve_term(:name, :role, :text).path.should == "roleTerm"
|
123
|
+
built_terminology.retrieve_term(:name, :role, :text).attributes.should == {:type=>"text"}
|
124
|
+
end
|
125
|
+
it "should put copies of the entire terminology under any root terms" do
|
126
|
+
@builder_with_block.root_term_builders.should include(@builder_with_block.retrieve_term_builder(:mods))
|
127
|
+
|
128
|
+
built_terminology = @builder_with_block.build
|
129
|
+
expected_keys = [:title_info, :issue, :person, :name, :journal, :role]
|
130
|
+
|
131
|
+
built_terminology.retrieve_term(:mods).children.length.should == expected_keys.length
|
132
|
+
expected_keys.each do |key|
|
133
|
+
built_terminology.retrieve_term(:mods).children.keys.should include(key)
|
134
|
+
end
|
135
|
+
built_terminology.retrieve_term(:mods, :name, :role, :text).should be_instance_of OM::XML::Term
|
136
|
+
built_terminology.retrieve_term(:mods, :person, :role, :text).should be_instance_of OM::XML::Term
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '.insert_term' do
|
142
|
+
it "should create a new OM::XML::Term::Builder and insert it into the class mappings hash" do
|
143
|
+
pending
|
144
|
+
|
145
|
+
result = @test_builder.insert_mapper(:name_, :namePart).index_as([:facetable, :searchable, :sortable, :displayable]).required(true).type(:text)
|
146
|
+
@test_builder.mapper_builders(:name_, :namePart).should == result
|
147
|
+
result.should be_instance_of OM::XML::Mapper::Builder
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe ".root" do
|
152
|
+
it "should accept options for the root node, such as namespace(s) and schema and those values should impact the resulting Terminology" do
|
153
|
+
root_term_builder = @test_builder.root(:path=>"mods", :xmlns => 'one:two', 'xmlns:foo' => 'bar', :schema=>"http://www.loc.gov/standards/mods/v3/mods-3-2.xsd")
|
154
|
+
root_term_builder.settings[:is_root_term].should == true
|
155
|
+
|
156
|
+
@test_builder.schema.should == "http://www.loc.gov/standards/mods/v3/mods-3-2.xsd"
|
157
|
+
@test_builder.namespaces.should == { "oxns"=>"one:two", 'xmlns' => 'one:two', 'xmlns:foo' => 'bar' }
|
158
|
+
@test_builder.term_builders[:mods].should == root_term_builder
|
159
|
+
|
160
|
+
terminology = @test_builder.build
|
161
|
+
terminology.schema.should == "http://www.loc.gov/standards/mods/v3/mods-3-2.xsd"
|
162
|
+
terminology.namespaces.should == { "oxns"=>"one:two", 'xmlns' => 'one:two', 'xmlns:foo' => 'bar' }
|
163
|
+
terminology.retrieve_term(:mods).should be_instance_of OM::XML::Term
|
164
|
+
terminology.retrieve_term(:mods).is_root_term?.should == true
|
165
|
+
end
|
166
|
+
it "should work within a builder block" do
|
167
|
+
@builder_with_block.term_builders[:mods].settings[:is_root_term].should == true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe ".root_term_builders" do
|
172
|
+
it "should return the terms that have been marked root" do
|
173
|
+
@builder_with_block.root_term_builders.length.should == 1
|
174
|
+
@builder_with_block.root_term_builders.first.should == @builder_with_block.term_builders[:mods]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|