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,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
|