om 0.1.10 → 1.0.0

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