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,141 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::Document" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
#ModsHelpers.name_("Beethoven, Ludwig van", :date=>"1770-1827", :role=>"creator")
|
8
|
+
class DocumentTest
|
9
|
+
|
10
|
+
include OM::XML::Document
|
11
|
+
|
12
|
+
# Could add support for multiple root declarations.
|
13
|
+
# For now, assume that any modsCollections have already been broken up and fed in as individual mods documents
|
14
|
+
# root :mods_collection, :path=>"modsCollection",
|
15
|
+
# :attributes=>[],
|
16
|
+
# :subelements => :mods
|
17
|
+
|
18
|
+
set_terminology do |t|
|
19
|
+
t.root(:path=>"mods", :xmlns=>"http://www.loc.gov/mods/v3", :schema=>"http://www.loc.gov/standards/mods/v3/mods-3-2.xsd")
|
20
|
+
|
21
|
+
t.title_info(:path=>"titleInfo") {
|
22
|
+
t.main_title(:path=>"title", :label=>"title")
|
23
|
+
t.language(:path=>{:attribute=>"lang"})
|
24
|
+
}
|
25
|
+
# This is a mods:name. The underscore is purely to avoid namespace conflicts.
|
26
|
+
t.name_ {
|
27
|
+
# this is a namepart
|
28
|
+
t.namePart(:index_as=>[:searchable, :displayable, :facetable, :sortable], :required=>:true, :type=>:string, :label=>"generic name")
|
29
|
+
# affiliations are great
|
30
|
+
t.affiliation
|
31
|
+
t.displayForm
|
32
|
+
t.role(:ref=>[:role])
|
33
|
+
t.description
|
34
|
+
t.date(:path=>"namePart", :attributes=>{:type=>"date"})
|
35
|
+
t.last_name(:path=>"namePart", :attributes=>{:type=>"family"})
|
36
|
+
t.first_name(:path=>"namePart", :attributes=>{:type=>"given"}, :label=>"first name")
|
37
|
+
t.terms_of_address(:path=>"namePart", :attributes=>{:type=>"termsOfAddress"})
|
38
|
+
}
|
39
|
+
# lookup :person, :first_name
|
40
|
+
t.person(:ref=>:name, :attributes=>{:type=>"personal"})
|
41
|
+
|
42
|
+
t.role {
|
43
|
+
t.text(:path=>"roleTerm",:attributes=>{:type=>"text"})
|
44
|
+
t.code(:path=>"roleTerm",:attributes=>{:type=>"code"})
|
45
|
+
}
|
46
|
+
t.journal(:path=>'relatedItem', :attributes=>{:type=>"host"}) {
|
47
|
+
t.title_info
|
48
|
+
t.origin_info(:path=>"originInfo")
|
49
|
+
t.issn(:path=>"identifier", :attributes=>{:type=>"issn"})
|
50
|
+
t.issue(:ref=>:issue)
|
51
|
+
}
|
52
|
+
t.issue(:path=>"part") {
|
53
|
+
t.volume(:path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
|
54
|
+
t.level(:path=>"detail", :attributes=>{:type=>"number"}, :default_content_path=>"number")
|
55
|
+
t.start_page(:path=>"pages", :attributes=>{:type=>"start"})
|
56
|
+
t.end_page(:path=>"pages", :attributes=>{:type=>"end"})
|
57
|
+
# t.start_page(:path=>"extent", :attributes=>{:unit=>"pages"}, :default_content_path => "start")
|
58
|
+
# t.end_page(:path=>"extent", :attributes=>{:unit=>"pages"}, :default_content_path => "end")
|
59
|
+
t.publication_date(:path=>"date")
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
before(:each) do
|
68
|
+
@fixturemods = DocumentTest.from_xml( fixture( File.join("CBF_MODS", "ARS0025_016.xml") ) )
|
69
|
+
article_xml = fixture( File.join("mods_articles", "hydrangea_article1.xml") )
|
70
|
+
@mods_article = DocumentTest.from_xml(article_xml)
|
71
|
+
end
|
72
|
+
|
73
|
+
after(:all) do
|
74
|
+
Object.send(:remove_const, :DocumentTest)
|
75
|
+
end
|
76
|
+
|
77
|
+
describe ".ox_namespaces" do
|
78
|
+
it "should merge terminology namespaces with document namespaces" do
|
79
|
+
@fixturemods.ox_namespaces.should == {"oxns"=>"http://www.loc.gov/mods/v3", "xmlns:ns2"=>"http://www.w3.org/1999/xlink", "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance", "xmlns:ns3"=>"http://www.loc.gov/mods/v3", "xmlns"=>"http://www.loc.gov/mods/v3"}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
describe ".find_by_terms_and_value" do
|
85
|
+
it "should fail gracefully if you try to look up nodes for an undefined property" do
|
86
|
+
pending "better to get an informative error?"
|
87
|
+
@fixturemods.find_by_terms_and_value(:nobody_home).should == []
|
88
|
+
end
|
89
|
+
it "should use Nokogiri to retrieve a NodeSet corresponding to the term pointers" do
|
90
|
+
@mods_article.find_by_terms_and_value( :person ).length.should == 2
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should allow you to search by term pointer" do
|
94
|
+
@fixturemods.ng_xml.expects(:xpath).with('//oxns:name[@type="personal"]', @fixturemods.ox_namespaces)
|
95
|
+
@fixturemods.find_by_terms_and_value(:person)
|
96
|
+
end
|
97
|
+
it "should allow you to constrain your searches" do
|
98
|
+
@fixturemods.ng_xml.expects(:xpath).with('//oxns:name[@type="personal" and contains(., "Beethoven, Ludwig van")]', @fixturemods.ox_namespaces)
|
99
|
+
@fixturemods.find_by_terms_and_value(:person, "Beethoven, Ludwig van")
|
100
|
+
end
|
101
|
+
it "should allow you to use complex constraints" do
|
102
|
+
@fixturemods.ng_xml.expects(:xpath).with('//oxns:name[@type="personal"]/oxns:namePart[@type="date" and contains(., "2010")]', @fixturemods.ox_namespaces)
|
103
|
+
@fixturemods.find_by_terms_and_value(:person, :date=>"2010")
|
104
|
+
|
105
|
+
@fixturemods.ng_xml.expects(:xpath).with('//oxns:name[@type="personal"]/oxns:role[contains(., "donor")]', @fixturemods.ox_namespaces)
|
106
|
+
@fixturemods.find_by_terms_and_value(:person, :role=>"donor")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
describe ".find_by_terms" do
|
110
|
+
it "should use Nokogiri to retrieve a NodeSet corresponding to the combination of term pointers and array/nodeset indexes" do
|
111
|
+
@mods_article.find_by_terms( :person ).length.should == 2
|
112
|
+
@mods_article.find_by_terms( {:person=>1} ).first.should == @mods_article.ng_xml.xpath('//oxns:name[@type="personal"][2]', "oxns"=>"http://www.loc.gov/mods/v3").first
|
113
|
+
@mods_article.find_by_terms( {:person=>1}, :first_name ).class.should == Nokogiri::XML::NodeSet
|
114
|
+
@mods_article.find_by_terms( {:person=>1}, :first_name ).first.text.should == "Siddartha"
|
115
|
+
end
|
116
|
+
it "should support accessors whose relative_xpath is a lookup array instead of an xpath string" do
|
117
|
+
# pending "this only impacts scenarios where we want to display & edit"
|
118
|
+
DocumentTest.terminology.retrieve_term(:title_info, :language).path.should == {:attribute=>"lang"}
|
119
|
+
# @sample.retrieve( :title, 1 ).first.text.should == "Artikkelin otsikko Hydrangea artiklan 1"
|
120
|
+
@mods_article.find_by_terms( {:title_info=>1}, :language ).first.text.should == "finnish"
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should support xpath queries as the pointer" do
|
124
|
+
@mods_article.find_by_terms('//oxns:name[@type="personal"][1]/oxns:namePart[1]').first.text.should == "FAMILY NAME"
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return nil if the xpath fails to generate" do
|
128
|
+
pending "Can't decide if it's better to return nil or raise an error. Choosing informative errors for now."
|
129
|
+
@mods_article.find_by_terms( {:foo=>20}, :bar ).should == nil
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should support terms that point to attributes instead of nodes" do
|
133
|
+
@mods_article.find_by_terms( {:title_info=>1}, :language ).first.text.should == "finnish"
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should support xpath queries as the pointer" do
|
137
|
+
@mods_article.find_by_terms('//oxns:name[@type="personal"][1]/oxns:namePart[1]').first.text.should == "FAMILY NAME"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
data/spec/unit/generator_spec.rb
CHANGED
@@ -51,7 +51,13 @@ describe "OM::XML::Generator" do
|
|
51
51
|
describe '#generate' do
|
52
52
|
it "should use the corresponding builder template(s) to generate the node" do
|
53
53
|
GeneratorTest.generate(:mods, "foo").root.to_xml.should == "<mods>foo</mods>"
|
54
|
-
GeneratorTest.generate([:person,:role], "creator", {:attributes=>{"type"=>"code", "authority"=>"marcrelator"}}).root.to_xml.should == "<role
|
54
|
+
# GeneratorTest.generate([:person,:role], "creator", {:attributes=>{"type"=>"code", "authority"=>"marcrelator"}}).root.to_xml.should == "<role authority=\"marcrelator\" type=\"code\">\n <roleTerm>creator</roleTerm>\n</role>"
|
55
|
+
generated_node = GeneratorTest.generate([:person,:role], "creator", {:attributes=>{"type"=>"code", "authority"=>"marcrelator"}})
|
56
|
+
# generated_node.should have_node 'role[@authority="marcrelator"][@type="code"]' do
|
57
|
+
# with_node "roleTerm", "creator"
|
58
|
+
# end
|
59
|
+
generated_node.xpath('./role[@authority="marcrelator"][@type="code"]').xpath("./roleTerm").text.should == "creator"
|
60
|
+
|
55
61
|
end
|
56
62
|
it "should return Nokogiri Documents" do
|
57
63
|
GeneratorTest.generate(:mods, "foo").class.should == Nokogiri::XML::Document
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::NamedTermProxy" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
|
8
|
+
@test_terminology_builder = OM::XML::Terminology::Builder.new do |t|
|
9
|
+
t.parent {
|
10
|
+
t.foo {
|
11
|
+
t.bar
|
12
|
+
}
|
13
|
+
t.my_proxy(:proxy=>[:foo, :bar])
|
14
|
+
}
|
15
|
+
t.adoptive_parent(:ref=>[:parent], :attributes=>{:type=>"adoptive"})
|
16
|
+
end
|
17
|
+
|
18
|
+
@test_terminology = @test_terminology_builder.build
|
19
|
+
@test_proxy = @test_terminology.retrieve_term(:parent, :my_proxy)
|
20
|
+
@proxied_term = @test_terminology.retrieve_term(:parent, :foo, :bar)
|
21
|
+
@adoptive_parent = @test_terminology.retrieve_term(:adoptive_parent)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should proxy all extra methods to the proxied object" do
|
25
|
+
[:xpath, :xpath_relative, :xml_builder_template].each do |method|
|
26
|
+
@proxied_term.expects(method)
|
27
|
+
@test_proxy.send(method)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
it "should proxy the term specified by the builder" do
|
31
|
+
@test_proxy.proxied_term.should == @test_terminology.retrieve_term(:parent, :foo, :bar)
|
32
|
+
@test_proxy.xpath.should == "//oxns:parent/oxns:foo/oxns:bar"
|
33
|
+
end
|
34
|
+
it "should search relative to the parent term when finding the term to proxy" do
|
35
|
+
proxy2 = @test_terminology.retrieve_term(:adoptive_parent, :my_proxy)
|
36
|
+
proxy2.proxied_term.should == @test_terminology.retrieve_term(:adoptive_parent, :foo, :bar)
|
37
|
+
proxy2.xpath.should == '//oxns:parent[@type="adoptive"]/oxns:foo/oxns:bar'
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::NodeGenerator" do
|
5
|
+
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@test_mods_term = OM::XML::Term.new(:mods)
|
9
|
+
@test_volume_term = OM::XML::Term.new(:volume, :path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#generate' do
|
13
|
+
it "should use the corresponding builder template(s) to generate the node" do
|
14
|
+
OM::XML::NodeGenerator.generate(@test_mods_term, "foo").root.to_xml.should == "<mods>foo</mods>"
|
15
|
+
generated_node = OM::XML::NodeGenerator.generate(@test_volume_term, "108", {:attributes=>{"extraAttr"=>"my value"}})
|
16
|
+
generated_node.xpath('./detail[@type="volume"][@extraAttr="my value"]').xpath("./number").text.should == "108"
|
17
|
+
# Would be great if we wrote a have_node custom rspec matcher...
|
18
|
+
# generated_node.should have_node 'role[@authority="marcrelator"][@type="code"]' do
|
19
|
+
# with_node "roleTerm", "creator"
|
20
|
+
# end
|
21
|
+
end
|
22
|
+
it "should return Nokogiri Documents" do
|
23
|
+
OM::XML::NodeGenerator.generate(@test_mods_term, "foo").class.should == Nokogiri::XML::Document
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::Term::Builder" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@test_terminology_builder = OM::XML::Terminology::Builder.new do |t|
|
8
|
+
t.fruit_trees {
|
9
|
+
t.citrus(:attributes=>{"citric_acid"=>"true"}, :index_as=>[:facetable]) {
|
10
|
+
t.randomness
|
11
|
+
}
|
12
|
+
t.stone_fruit(:path=>"prunus", :attributes=>{:genus=>"Prunus"})
|
13
|
+
t.peach(:ref=>[:fruit_trees, :stone_fruit], :attributes=>{:subgenus=>"Amygdalus", :species=>"Prunus persica"})
|
14
|
+
t.nectarine(:ref=>[:fruit_trees, :peach], :attributes=>{:cultivar=>"nectarine"})
|
15
|
+
t.almond(:ref=>[:fruit_trees, :peach], :attributes=>{:species=>"Prunus dulcis"})
|
16
|
+
}
|
17
|
+
t.coconut(:ref=>:pineapple)
|
18
|
+
t.banana(:ref=>:coconut)
|
19
|
+
t.pineapple(:ref=>:banana)
|
20
|
+
end
|
21
|
+
|
22
|
+
@citrus = @test_terminology_builder.retrieve_term_builder(:fruit_trees, :citrus)
|
23
|
+
@stone_fruit = @test_terminology_builder.retrieve_term_builder(:fruit_trees, :stone_fruit)
|
24
|
+
@peach = @test_terminology_builder.retrieve_term_builder(:fruit_trees, :peach)
|
25
|
+
@nectarine = @test_terminology_builder.retrieve_term_builder(:fruit_trees, :nectarine)
|
26
|
+
@almond = @test_terminology_builder.retrieve_term_builder(:fruit_trees, :almond)
|
27
|
+
@pineapple = @test_terminology_builder.retrieve_term_builder(:pineapple)
|
28
|
+
end
|
29
|
+
|
30
|
+
before(:each) do
|
31
|
+
@test_builder = OM::XML::Term::Builder.new("term1")
|
32
|
+
@test_builder_2 = OM::XML::Term::Builder.new("term2")
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#new' do
|
36
|
+
it "should set terminology_builder attribute if provided" do
|
37
|
+
mock_terminology_builder = mock("TerminologyBuilder")
|
38
|
+
OM::XML::Term::Builder.new("term1", mock_terminology_builder).terminology_builder.should == mock_terminology_builder
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "configuration methods" do
|
43
|
+
it "should set the corresponding .settings value return the mapping object" do
|
44
|
+
[:path, :index_as, :required, :data_type, :variant_of, :path, :attributes, :default_content_path].each do |method_name|
|
45
|
+
@test_builder.send(method_name, "#{method_name.to_s}foo").should == @test_builder
|
46
|
+
@test_builder.settings[method_name].should == "#{method_name.to_s}foo"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
it "should be chainable" do
|
50
|
+
test_builder = OM::XML::Term::Builder.new("chainableTerm").index_as(:facetable, :searchable, :sortable, :displayable).required(true).data_type(:text)
|
51
|
+
resulting_settings = test_builder.settings
|
52
|
+
resulting_settings[:index_as].should == [:facetable, :searchable, :sortable, :displayable]
|
53
|
+
resulting_settings[:required].should == true
|
54
|
+
resulting_settings[:data_type].should == :text
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "settings" do
|
59
|
+
describe "defaults" do
|
60
|
+
it "should be set" do
|
61
|
+
@test_builder.settings[:required].should == false
|
62
|
+
@test_builder.settings[:data_type].should == :string
|
63
|
+
@test_builder.settings[:variant_of].should be_nil
|
64
|
+
@test_builder.settings[:attributes].should be_nil
|
65
|
+
@test_builder.settings[:default_content_path].should be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe ".add_child" do
|
71
|
+
it "should insert the given Term Builder into the current Term Builder's children" do
|
72
|
+
@test_builder.add_child(@test_builder_2)
|
73
|
+
@test_builder.children[@test_builder_2.name].should == @test_builder_2
|
74
|
+
@test_builder.ancestors.should include(@test_builder_2)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
describe ".retrieve_child" do
|
78
|
+
it "should fetch the child identified by the given name" do
|
79
|
+
@test_builder.add_child(@test_builder_2)
|
80
|
+
@test_builder.retrieve_child(@test_builder_2.name).should == @test_builder.children[@test_builder_2.name]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
describe ".children" do
|
84
|
+
it "should return a hash of Term Builders that are the children of the current object, indexed by name" do
|
85
|
+
@test_builder.add_child(@test_builder_2)
|
86
|
+
@test_builder.children[@test_builder_2.name].should == @test_builder_2
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe ".build" do
|
91
|
+
it "should build a Term with the given settings and generate its xpath values" do
|
92
|
+
test_builder = OM::XML::Term::Builder.new("requiredTextFacet").index_as([:facetable, :searchable, :sortable, :displayable]).required(true).data_type(:text)
|
93
|
+
result = test_builder.build
|
94
|
+
result.should be_instance_of OM::XML::Term
|
95
|
+
result.index_as.should == [:facetable, :searchable, :sortable, :displayable]
|
96
|
+
result.required.should == true
|
97
|
+
result.data_type.should == :text
|
98
|
+
|
99
|
+
result.xpath.should == OM::XML::TermXpathGenerator.generate_absolute_xpath(result)
|
100
|
+
result.xpath_constrained.should == OM::XML::TermXpathGenerator.generate_constrained_xpath(result)
|
101
|
+
result.xpath_relative.should == OM::XML::TermXpathGenerator.generate_relative_xpath(result)
|
102
|
+
end
|
103
|
+
it "should create proxy terms if :proxy is set" do
|
104
|
+
test_builder = OM::XML::Term::Builder.new("my_proxy").proxy([:foo, :bar])
|
105
|
+
result = test_builder.build
|
106
|
+
result.should be_kind_of OM::XML::NamedTermProxy
|
107
|
+
end
|
108
|
+
it "should set path to match name if it is empty" do
|
109
|
+
@test_builder.settings[:path].should be_nil
|
110
|
+
@test_builder.build.path.should == @test_builder.name.to_s
|
111
|
+
end
|
112
|
+
it "should work recursively, calling .build on any of its children" do
|
113
|
+
OM::XML::Term.any_instance.stubs(:generate_xpath_queries!)
|
114
|
+
built_child1 = OM::XML::Term.new("child1")
|
115
|
+
built_child2 = OM::XML::Term.new("child2")
|
116
|
+
|
117
|
+
mock1 = mock("Builder1", :build => built_child1 )
|
118
|
+
mock2 = mock("Builder2", :build => built_child2 )
|
119
|
+
mock1.stubs(:name).returns("child1")
|
120
|
+
mock2.stubs(:name).returns("child2")
|
121
|
+
|
122
|
+
@test_builder.children = {:mock1=>mock1, :mock2=>mock2}
|
123
|
+
result = @test_builder.build
|
124
|
+
result.children[:child1].should == built_child1
|
125
|
+
result.children[:child2].should == built_child2
|
126
|
+
result.children.length.should == 2
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe ".lookup_refs" do
|
131
|
+
it "should return an empty array if no refs are declared" do
|
132
|
+
@test_builder.lookup_refs.should == []
|
133
|
+
end
|
134
|
+
it "should should look up the referenced TermBuilder from the terminology_builder" do
|
135
|
+
@peach.lookup_refs.should == [@stone_fruit]
|
136
|
+
end
|
137
|
+
it "should support recursive refs" do
|
138
|
+
@almond.lookup_refs.should == [@peach, @stone_fruit]
|
139
|
+
end
|
140
|
+
it "should raise an error if the TermBuilder does not have a reference to a terminology builder" do
|
141
|
+
lambda { OM::XML::Term::Builder.new("referrer").ref("bongos").lookup_refs }.should raise_error(StandardError,"Cannot perform lookup_ref for the referrer builder. It doesn't have a reference to any terminology builder")
|
142
|
+
end
|
143
|
+
it "should raise an error if the referece points to a nonexistent term builder" do
|
144
|
+
tb = OM::XML::Term::Builder.new("mork",@test_terminology_builder).ref(:characters, :aliens)
|
145
|
+
lambda { tb.lookup_refs }.should raise_error(OM::XML::Terminology::BadPointerError,"#{tb.name} refers to a Term Builder that doesn't exist. The bad pointer is [:characters, :aliens]")
|
146
|
+
end
|
147
|
+
it "should raise an error with informative error when given circular references" do
|
148
|
+
lambda { @pineapple.lookup_refs }.should raise_error(OM::XML::Terminology::CircularReferenceError,"Circular reference in Terminology: :pineapple => :banana => :coconut => :pineapple")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe ".resolve_refs!" do
|
153
|
+
it "should do nothing if settings don't include a :ref" do
|
154
|
+
settings_pre = @test_builder.settings
|
155
|
+
children_pre = @test_builder.children
|
156
|
+
|
157
|
+
@test_builder.resolve_refs!
|
158
|
+
@test_builder.settings.should == settings_pre
|
159
|
+
@test_builder.children.should == children_pre
|
160
|
+
end
|
161
|
+
it "should should look up the referenced TermBuilder, use its settings and duplicate its children without changing the name" do
|
162
|
+
term_builder = OM::XML::Term::Builder.new("orange",@test_terminology_builder).ref(:fruit_trees, :citrus)
|
163
|
+
term_builder.resolve_refs!
|
164
|
+
# Make sure children and settings were copied
|
165
|
+
term_builder.settings.should == @citrus.settings.merge(:path=>"citrus")
|
166
|
+
term_builder.children.should == @citrus.children
|
167
|
+
|
168
|
+
# Make sure name and parent of both the term_builder and its target were left alone
|
169
|
+
term_builder.name.should == :orange
|
170
|
+
@citrus.name.should == :citrus
|
171
|
+
end
|
172
|
+
it "should set path based on the ref's path if set" do
|
173
|
+
[@peach,@almond].each { |x| x.resolve_refs! }
|
174
|
+
@peach.settings[:path].should == "prunus"
|
175
|
+
@almond.settings[:path].should == "prunus"
|
176
|
+
end
|
177
|
+
it "should set path based on the first ref's name if no path is set" do
|
178
|
+
orange_builder = OM::XML::Term::Builder.new("orange",@test_terminology_builder).ref(:fruit_trees, :citrus)
|
179
|
+
orange_builder.resolve_refs!
|
180
|
+
orange_builder.settings[:path].should == "citrus"
|
181
|
+
end
|
182
|
+
# It should not be a problem if multiple TermBuilders refer to the same child TermBuilder since the parent-child relationship is set up after calling TermBuilder.build
|
183
|
+
it "should result in clean trees of Terms after building"
|
184
|
+
|
185
|
+
it "should preserve any extra settings specific to this builder (for variant terms)" do
|
186
|
+
tb = OM::XML::Term::Builder.new("orange",@test_terminology_builder).ref(:fruit_trees, :citrus).attributes(:color=>"orange").required(true)
|
187
|
+
tb.resolve_refs!
|
188
|
+
tb.settings.should == {:path=>"citrus", :attributes=>{"citric_acid"=>"true", :color=>"orange"}, :required=>true, :data_type=>:string, :index_as=>[:facetable]}
|
189
|
+
end
|
190
|
+
it "should aggregate all settings from refs, combining them with a cascading approach" do
|
191
|
+
@almond.resolve_refs!
|
192
|
+
@almond.settings[:attributes].should == {:genus=>"Prunus",:subgenus=>"Amygdalus", :species=>"Prunus dulcis"}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require "om"
|
3
|
+
|
4
|
+
describe "OM::XML::Term" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@test_name_part = OM::XML::Term.new(:namePart, {}).generate_xpath_queries!
|
8
|
+
@test_volume = OM::XML::Term.new(:volume, :path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
|
9
|
+
@test_date = OM::XML::Term.new(:namePart, :attributes=>{:type=> "date"})
|
10
|
+
@test_affiliation = OM::XML::Term.new(:affiliation)
|
11
|
+
@test_role_code = OM::XML::Term.new(:roleTerm, :attributes=>{:type=>"code"})
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#new' do
|
15
|
+
it "should set default values" do
|
16
|
+
@test_name_part.namespace_prefix.should == "oxns"
|
17
|
+
end
|
18
|
+
it "should set path from mapper name if no path is provided" do
|
19
|
+
@test_name_part.path.should == "namePart"
|
20
|
+
end
|
21
|
+
it "should populate the xpath values if no options are provided" do
|
22
|
+
local_mapping = OM::XML::Term.new(:namePart)
|
23
|
+
local_mapping.xpath_relative.should be_nil
|
24
|
+
local_mapping.xpath.should be_nil
|
25
|
+
local_mapping.xpath_constrained.should be_nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'inner_xml' do
|
30
|
+
it "should be a kind of Nokogiri::XML::Node" do
|
31
|
+
pending
|
32
|
+
@test_mapping.inner_xml.should be_kind_of(Nokogiri::XML::Node)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#from_node' do
|
37
|
+
it "should create a mapper from a nokogiri node" do
|
38
|
+
pending "probably should do this in the Builder"
|
39
|
+
ng_builder = Nokogiri::XML::Builder.new do |xml|
|
40
|
+
xml.mapper(:name=>"person", :path=>"name") {
|
41
|
+
xml.attribute(:name=>"type", :value=>"personal")
|
42
|
+
xml.mapper(:name=>"first_name", :path=>"namePart") {
|
43
|
+
xml.attribute(:name=>"type", :value=>"given")
|
44
|
+
xml.attribute(:name=>"another_attribute", :value=>"myval")
|
45
|
+
}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
# node = Nokogiri::XML::Document.parse( '<mapper name="first_name" path="namePart"><attribute name="type" value="given"/><attribute name="another_attribute" value="myval"/></mapper>' ).root
|
49
|
+
node = ng_builder.doc.root
|
50
|
+
mapper = OM::XML::Term.from_node(node)
|
51
|
+
mapper.name.should == :person
|
52
|
+
mapper.path.should == "name"
|
53
|
+
mapper.attributes.should == {:type=>"personal"}
|
54
|
+
mapper.internal_xml.should == node
|
55
|
+
|
56
|
+
child = mapper.children[:first_name]
|
57
|
+
|
58
|
+
child.name.should == :first_name
|
59
|
+
child.path.should == "namePart"
|
60
|
+
child.attributes.should == {:type=>"given", :another_attribute=>"myval"}
|
61
|
+
child.internal_xml.should == node.xpath("./mapper").first
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ".label" do
|
66
|
+
it "should default to the mapper name with underscores converted to spaces"
|
67
|
+
end
|
68
|
+
|
69
|
+
describe ".retrieve_term" do
|
70
|
+
it "should crawl down into mapper children to find the desired term" do
|
71
|
+
mock_role = mock("mapper", :children =>{:text=>"the target"})
|
72
|
+
mock_conference = mock("mapper", :children =>{:role=>mock_role})
|
73
|
+
@test_name_part.expects(:children).returns({:conference=>mock_conference})
|
74
|
+
@test_name_part.retrieve_term(:conference, :role, :text).should == "the target"
|
75
|
+
end
|
76
|
+
it "should return an empty hash if no term can be found" do
|
77
|
+
@test_name_part.retrieve_term(:journal, :issue, :end_page).should == nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'inner_xml' do
|
82
|
+
it "should be a kind of Nokogiri::XML::Node" do
|
83
|
+
pending
|
84
|
+
@test_name_part.inner_xml.should be_kind_of(Nokogiri::XML::Node)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "getters/setters" do
|
89
|
+
it "should set the corresponding .settings value and return the current value" do
|
90
|
+
[:path, :index_as, :required, :data_type, :variant_of, :path, :attributes, :default_content_path, :namespace_prefix].each do |method_name|
|
91
|
+
@test_name_part.send(method_name.to_s+"=", "#{method_name.to_s}foo").should == "#{method_name.to_s}foo"
|
92
|
+
@test_name_part.send(method_name).should == "#{method_name.to_s}foo"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
it "should have a .terminology attribute accessor" do
|
97
|
+
@test_volume.should respond_to :terminology
|
98
|
+
@test_volume.should respond_to :terminology=
|
99
|
+
end
|
100
|
+
describe ".ancestors" do
|
101
|
+
it "should return an array of Terms that are the ancestors of the current object, ordered from the top/root of the hierarchy" do
|
102
|
+
@test_volume.set_parent(@test_name_part)
|
103
|
+
@test_volume.ancestors.should == [@test_name_part]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
describe ".parent" do
|
107
|
+
it "should retrieve the immediate parent of the given object from the ancestors array" do
|
108
|
+
# @test_name_part.expects(:ancestors).returns(["ancestor1","ancestor2","ancestor3"])
|
109
|
+
@test_name_part.ancestors = ["ancestor1","ancestor2","ancestor3"]
|
110
|
+
@test_name_part.parent.should == "ancestor3"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
describe ".children" do
|
114
|
+
it "should return a hash of Terms that are the children of the current object, indexed by name" do
|
115
|
+
@test_volume.add_child(@test_name_part)
|
116
|
+
@test_volume.children[@test_name_part.name].should == @test_name_part
|
117
|
+
end
|
118
|
+
end
|
119
|
+
describe ".retrieve_child" do
|
120
|
+
it "should fetch the child identified by the given name" do
|
121
|
+
@test_volume.add_child(@test_name_part)
|
122
|
+
@test_volume.retrieve_child(@test_name_part.name).should == @test_volume.children[@test_name_part.name]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
describe ".set_parent" do
|
126
|
+
it "should insert the mapper into the given parent" do
|
127
|
+
@test_name_part.set_parent(@test_volume)
|
128
|
+
@test_name_part.ancestors.should include(@test_volume)
|
129
|
+
@test_volume.children[@test_name_part.name].should == @test_name_part
|
130
|
+
end
|
131
|
+
end
|
132
|
+
describe ".add_child" do
|
133
|
+
it "should insert the given mapper into the current mappers children" do
|
134
|
+
@test_volume.add_child(@test_name_part)
|
135
|
+
@test_volume.children[@test_name_part.name].should == @test_name_part
|
136
|
+
@test_name_part.ancestors.should include(@test_volume)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "generate_xpath_queries!" do
|
141
|
+
it "should return the current object" do
|
142
|
+
@test_name_part.generate_xpath_queries!.should == @test_name_part
|
143
|
+
end
|
144
|
+
it "should regenerate the xpath values" do
|
145
|
+
@test_volume.xpath_relative.should be_nil
|
146
|
+
@test_volume.xpath.should be_nil
|
147
|
+
@test_volume.xpath_constrained.should be_nil
|
148
|
+
|
149
|
+
@test_volume.generate_xpath_queries!.should == @test_volume
|
150
|
+
|
151
|
+
@test_volume.xpath_relative.should == 'oxns:detail[@type="volume"]'
|
152
|
+
@test_volume.xpath.should == '//oxns:detail[@type="volume"]'
|
153
|
+
@test_volume.xpath_constrained.should == '//oxns:detail[@type="volume" and contains(oxns:number, "#{constraint_value}")]'.gsub('"', '\"')
|
154
|
+
end
|
155
|
+
it "should trigger update on any child objects" do
|
156
|
+
mock_child = mock("child term")
|
157
|
+
mock_child.expects(:generate_xpath_queries!).times(3)
|
158
|
+
@test_name_part.expects(:children).returns({1=>mock_child, 2=>mock_child, 3=>mock_child})
|
159
|
+
@test_name_part.generate_xpath_queries!
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#xml_builder_template" do
|
164
|
+
|
165
|
+
it "should generate a template call for passing into the builder block (assumes 'xml' as the argument for the block)" do
|
166
|
+
@test_date.xml_builder_template.should == 'xml.namePart( \'#{builder_new_value}\', :type=>\'date\' )'
|
167
|
+
@test_affiliation.xml_builder_template.should == 'xml.affiliation( \'#{builder_new_value}\' )'
|
168
|
+
end
|
169
|
+
it "should accept extra options" do
|
170
|
+
marcrelator_role_xml_builder_template = 'xml.roleTerm( \'#{builder_new_value}\', :type=>\'code\', :authority=>\'marcrelator\' )'
|
171
|
+
@test_role_code.xml_builder_template(:attributes=>{"authority"=>"marcrelator"}).should == marcrelator_role_xml_builder_template
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should work for nodes with default_content_path" do
|
175
|
+
@test_volume.xml_builder_template.should == "xml.detail( :type=>'volume' ) { xml.number( '\#{builder_new_value}' ) }"
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|