om 0.1.10 → 1.0.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/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
|