om 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.textile CHANGED
@@ -1,3 +1,17 @@
1
+ 1.1.0
2
+
3
+ HYDRA-371: Provide a way to specify a term that points to nodes where an attribute is not set
4
+
5
+ Add support for this syntax in Terminologies, where an attribute value can be :none. When an attribute's value is set to :none, a not() predicate is used in the resulting xpath
6
+
7
+ t.computing_id(:path=>"namePart", :attributes=>{:type=>:none})
8
+
9
+ will result in an xpath that looks like:
10
+
11
+ //namePart[not(@type)]
12
+
13
+ namePart[not(@type)]
14
+
1
15
  1.0.1
2
16
 
3
17
  HYDRA-329: Allow for NamedTermProxies at root of Terminology
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.2
1
+ 1.1.0
@@ -10,7 +10,10 @@ class OM::Samples::ModsArticle
10
10
  t.language(:path=>{:attribute=>"lang"})
11
11
  }
12
12
  t.abstract
13
- t.topic_tag(:path=>"subject", :default_content_path=>"topic")
13
+ t.subject{
14
+ t.topic
15
+ }
16
+ t.topic_tag(:ref=>[:subject, :topic])
14
17
  # This is a mods:name. The underscore is purely to avoid namespace conflicts.
15
18
  t.name_ {
16
19
  # this is a namepart
@@ -24,6 +27,7 @@ class OM::Samples::ModsArticle
24
27
  t.last_name(:path=>"namePart", :attributes=>{:type=>"family"})
25
28
  t.first_name(:path=>"namePart", :attributes=>{:type=>"given"}, :label=>"first name")
26
29
  t.terms_of_address(:path=>"namePart", :attributes=>{:type=>"termsOfAddress"})
30
+ t.name_content(:path=>"text()")
27
31
  }
28
32
  # lookup :person, :first_name
29
33
  t.person(:ref=>:name, :attributes=>{:type=>"personal"})
@@ -61,4 +65,4 @@ class OM::Samples::ModsArticle
61
65
  # renamed family_name => last_name
62
66
  # start_page & end_page now accessible as [:journal, :issue, :pages, :start] (etc.)
63
67
 
64
- end
68
+ end
data/lib/om/xml.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  require "om/xml/container"
2
- require "om/xml/accessors"
3
2
  require "om/xml/validation"
4
- require "om/xml/properties"
5
- require "om/xml/property_value_operators"
6
- require "om/xml/generator"
7
3
 
8
4
  require "om/xml/terminology"
9
5
  require "om/xml/term"
@@ -49,13 +45,8 @@ module OM::XML
49
45
 
50
46
  def self.included(klass)
51
47
  klass.extend(ClassMethods)
52
-
53
48
  klass.send(:include, OM::XML::Container)
54
- klass.send(:include, OM::XML::Accessors)
55
49
  klass.send(:include, OM::XML::Validation)
56
- klass.send(:include, OM::XML::Properties)
57
- klass.send(:include, OM::XML::PropertyValueOperators)
58
- klass.send(:include, OM::XML::Generator)
59
50
  end
60
51
 
61
52
 
@@ -1,6 +1,13 @@
1
1
  module OM::XML::NodeGenerator
2
2
 
3
3
  # Module Methods -- These methods can be called directly on the Module itself
4
+ # @param OM::XML::Term term The term to generate a node based on
5
+ # @param String builder_new_value The new value to insert into the generated node
6
+ # @returns Nokogiri::XML::Document
7
+ #
8
+ # Ex.
9
+ # term = t.retrieve_term(:person, :first_name)
10
+ # OM::XML::NodeGenerator.generate(term, "John")
4
11
  def self.generate(term, builder_new_value, opts={})
5
12
  template = term.xml_builder_template(opts)
6
13
  builder_call_body = eval('"' + template + '"')
data/lib/om/xml/term.rb CHANGED
@@ -129,6 +129,9 @@ class OM::XML::Term
129
129
 
130
130
  include OM::TreeNode
131
131
 
132
+ # h2. Namespaces
133
+ # By default, OM assumes that all terms in a Terminology have the namespace set in the root of the document. If you want to set a different namespace for a Term, pass :namespasce_prefix into its initializer (or call .namespace_prefix= on its builder)
134
+ # If a node has _no_ namespace, you must explicitly set namespace_prefix to nil.
132
135
  def initialize(name, opts={})
133
136
  opts = {:namespace_prefix=>"oxns", :ancestors=>[], :children=>{}}.merge(opts)
134
137
  [:children, :ancestors,:path, :index_as, :required, :type, :variant_of, :path, :attributes, :default_content_path, :namespace_prefix].each do |accessor_name|
@@ -7,7 +7,10 @@ module OM::XML::TermValueOperators
7
7
  # Retrieves all of the nodes from the current document that match +term_pointer+ and returns an array of their values
8
8
  def term_values(*term_pointer)
9
9
  result = []
10
- find_by_terms(*term_pointer).each {|node| result << node.text }
10
+ xpath = self.class.terminology.xpath_with_indexes(*term_pointer)
11
+ #if value is on line by itself sometimes does not trim leading and trailing whitespace for a text node so will detect and fix it
12
+ trim_text = !xpath.nil? && !xpath.index("text()").nil?
13
+ find_by_terms(*term_pointer).each {|node| result << (trim_text ? node.text.strip : node.text) }
11
14
  # find_by_terms(*OM.destringify(term_pointer)).each {|node| result << node.text }
12
15
  return result
13
16
  end
@@ -128,6 +131,9 @@ module OM::XML::TermValueOperators
128
131
  template_args = OM.pointers_to_flat_array(template_args,false)
129
132
  template = self.class.terminology.xml_builder_template( *template_args )
130
133
  end
134
+
135
+ #if there is an xpath element pointing to text() need to change to just 'text' so it references the text method for the parent node
136
+ template.gsub!(/text\(\)/, 'text')
131
137
 
132
138
  builder = Nokogiri::XML::Builder.with(parent_node) do |xml|
133
139
  new_values.each do |builder_new_value|
@@ -240,4 +246,4 @@ module OM::XML::TermValueOperators
240
246
 
241
247
  private :node_from_set
242
248
 
243
- end
249
+ end
@@ -1,32 +1,50 @@
1
1
  module OM::XML::TermXpathGenerator
2
2
 
3
- def self.generate_relative_xpath(mapper)
3
+ # Generate relative xpath for a term
4
+ # @param [OM::XML::Term] term that you want to generate relative xpath for
5
+ #
6
+ # In most cases, the resulting xpath will be the Term's path with the appropriate namespace appended to it.
7
+ # If the Term specifies any attributes,
8
+ # Special Case: attribute Terms
9
+ # If the Term's path is set to {:attribute=>attr_name}, the resulting xpath will points to a node attribute named attr_name
10
+ # ie. a path fo {:attribute=>"lang"} will result in a relative xpath of "@lang"
11
+ # Special Case: xpath functions
12
+ # If the Term's path variable is text(), it will be treated as an xpath function (no namespace) and turned into "text()[normalize-space(.)]"
13
+ def self.generate_relative_xpath(term)
4
14
  template = ""
5
15
  predicates = []
6
16
 
7
- if mapper.namespace_prefix.nil?
17
+ if term.namespace_prefix.nil?
8
18
  complete_prefix = ""
9
19
  else
10
- complete_prefix = mapper.namespace_prefix + ":"
20
+ complete_prefix = term.namespace_prefix + ":"
11
21
  end
12
22
 
13
- if mapper.path.kind_of?(Hash)
14
- if mapper.path.has_key?(:attribute)
15
- base_path = "@"+mapper.path[:attribute]
23
+ if term.path.kind_of?(Hash)
24
+ if term.path.has_key?(:attribute)
25
+ base_path = "@"+term.path[:attribute]
16
26
  else
17
- raise "#{mapper.path} is an invalid path for an OM::XML::Term. You should provide either a string or {:attributes=>XXX}"
27
+ raise "#{term.path} is an invalid path for an OM::XML::Term. You should provide either a string or {:attributes=>XXX}"
18
28
  end
19
29
  else
20
- unless mapper.namespace_prefix.nil?
21
- template << complete_prefix
30
+ if term.path == "text()"
31
+ base_path = "#{term.path}[normalize-space(.)]"
32
+ else
33
+ unless term.namespace_prefix.nil?
34
+ template << complete_prefix
35
+ end
36
+ base_path = term.path
22
37
  end
23
- base_path = mapper.path
24
38
  end
25
39
  template << base_path
26
40
 
27
- unless mapper.attributes.nil?
28
- mapper.attributes.each_pair do |attr_name, attr_value|
29
- predicates << "@#{attr_name}=\"#{attr_value}\""
41
+ unless term.attributes.nil?
42
+ term.attributes.each_pair do |attr_name, attr_value|
43
+ if attr_value == :none
44
+ predicates << "not(@#{attr_name})"
45
+ else
46
+ predicates << "@#{attr_name}=\"#{attr_value}\""
47
+ end
30
48
  end
31
49
  end
32
50
 
@@ -37,29 +55,33 @@ module OM::XML::TermXpathGenerator
37
55
  return template
38
56
  end
39
57
 
40
- def self.generate_absolute_xpath(mapper)
41
- relative = generate_relative_xpath(mapper)
42
- if mapper.parent.nil?
58
+ # Generate absolute xpath for a Term
59
+ # @param [OM::XML::Term] term that you want to generate absolute xpath for
60
+ #
61
+ # Absolute xpaths always begin with "//". They are generated by relying on the Term's relative xpath and the absolute xpath of its parent node.
62
+ def self.generate_absolute_xpath(term)
63
+ relative = generate_relative_xpath(term)
64
+ if term.parent.nil?
43
65
  return "//#{relative}"
44
66
  else
45
- return mapper.parent.xpath_absolute + "/" + relative
67
+ return term.parent.xpath_absolute + "/" + relative
46
68
  end
47
69
  end
48
70
 
49
- def self.generate_constrained_xpath(mapper)
50
- if mapper.namespace_prefix.nil?
71
+ def self.generate_constrained_xpath(term)
72
+ if term.namespace_prefix.nil?
51
73
  complete_prefix = ""
52
74
  else
53
- complete_prefix = mapper.namespace_prefix + ":"
75
+ complete_prefix = term.namespace_prefix + ":"
54
76
  end
55
77
 
56
- absolute = generate_absolute_xpath(mapper)
78
+ absolute = generate_absolute_xpath(term)
57
79
  constraint_predicates = []
58
80
 
59
81
  arguments_for_contains_function = []
60
82
 
61
- if !mapper.default_content_path.nil?
62
- arguments_for_contains_function << "#{complete_prefix}#{mapper.default_content_path}"
83
+ if !term.default_content_path.nil?
84
+ arguments_for_contains_function << "#{complete_prefix}#{term.default_content_path}"
63
85
  end
64
86
 
65
87
  # If no subelements have been specified to search within, set contains function to search within the current node
@@ -75,20 +97,25 @@ module OM::XML::TermXpathGenerator
75
97
  return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }.gsub('"', '\"')
76
98
  end
77
99
 
78
- def self.generate_xpath(mapper, type)
100
+ # Generate an xpath of the chosen +type+ for the given Term.
101
+ # @param [OM::XML::Term] term that you want to generate relative xpath for
102
+ # @param [Symbol] the type of xpath to generate, :relative, :abolute, or :constrained
103
+ def self.generate_xpath(term, type)
79
104
  case type
80
105
  when :relative
81
- self.generate_relative_xpath(mapper)
106
+ self.generate_relative_xpath(term)
82
107
  when :absolute
83
- self.generate_absolute_xpath(mapper)
108
+ self.generate_absolute_xpath(term)
84
109
  when :constrained
85
- self.generate_constrained_xpath(mapper)
110
+ self.generate_constrained_xpath(term)
86
111
  end
87
112
  end
88
113
 
89
114
  # Use the given +terminology+ to generate an xpath with (optional) node indexes for each of the term pointers.
90
115
  # Ex. OM::XML::TermXpathGenerator.xpath_with_indexes(my_terminology, {:conference=>0}, {:role=>1}, :text )
91
116
  # will yield an xpath similar to this: '//oxns:name[@type="conference"][1]/oxns:role[2]/oxns:roleTerm[@type="text"]'
117
+ # @param [OM::XML::Terminology] terminology to generate xpath based on
118
+ # @param [String -- OM term pointer] pointers identifying the node to generate xpath for
92
119
  def self.generate_xpath_with_indexes(terminology, *pointers)
93
120
  if pointers.first.nil?
94
121
  root_term = terminology.root_terms.first
@@ -183,7 +210,9 @@ module OM::XML::TermXpathGenerator
183
210
  return xpath
184
211
  end
185
212
 
186
-
213
+ # Turns an Array into a String containing values separated by a delimiter. Defaults to comma as a delimiter.
214
+ # @param [Array] values_array to convert
215
+ # @param [String] delimiter. Default: ", "
187
216
  def self.delimited_list( values_array, delimiter=", ")
188
217
  result = values_array.collect{|a| a + delimiter}.to_s.chomp(delimiter)
189
218
  end
@@ -224,4 +253,4 @@ module OM::XML::TermXpathGenerator
224
253
  return modified_query
225
254
  end
226
255
 
227
- end
256
+ end
data/om.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{om}
8
- s.version = "1.0.2"
8
+ s.version = "1.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Matt Zumwalt"]
12
- s.date = %q{2010-12-15}
12
+ s.date = %q{2011-03-05}
13
13
  s.description = %q{OM (Opinionated Metadata): A library to help you tame sprawling XML schemas like MODS. Wraps Nokogiri documents in objects with miscellaneous helper methods for doing things like retrieve generated xpath queries or look up properties based on a simplified DSL}
14
14
  s.email = %q{matt.zumwalt@yourmediashelf.com}
15
15
  s.extra_rdoc_files = [
@@ -33,14 +33,10 @@ Gem::Specification.new do |s|
33
33
  "lib/om/samples/mods_article.rb",
34
34
  "lib/om/tree_node.rb",
35
35
  "lib/om/xml.rb",
36
- "lib/om/xml/accessors.rb",
37
36
  "lib/om/xml/container.rb",
38
37
  "lib/om/xml/document.rb",
39
- "lib/om/xml/generator.rb",
40
38
  "lib/om/xml/named_term_proxy.rb",
41
39
  "lib/om/xml/node_generator.rb",
42
- "lib/om/xml/properties.rb",
43
- "lib/om/xml/property_value_operators.rb",
44
40
  "lib/om/xml/term.rb",
45
41
  "lib/om/xml/term_value_operators.rb",
46
42
  "lib/om/xml/term_xpath_generator.rb",
@@ -56,15 +52,11 @@ Gem::Specification.new do |s|
56
52
  "spec/integration/rights_metadata_integration_example_spec.rb",
57
53
  "spec/spec.opts",
58
54
  "spec/spec_helper.rb",
59
- "spec/unit/accessors_spec.rb",
60
55
  "spec/unit/container_spec.rb",
61
56
  "spec/unit/document_spec.rb",
62
- "spec/unit/generator_spec.rb",
63
57
  "spec/unit/named_term_proxy_spec.rb",
64
58
  "spec/unit/node_generator_spec.rb",
65
59
  "spec/unit/om_spec.rb",
66
- "spec/unit/properties_spec.rb",
67
- "spec/unit/property_value_operators_spec.rb",
68
60
  "spec/unit/term_builder_spec.rb",
69
61
  "spec/unit/term_spec.rb",
70
62
  "spec/unit/term_value_operators_spec.rb",
@@ -81,15 +73,11 @@ Gem::Specification.new do |s|
81
73
  s.test_files = [
82
74
  "spec/integration/rights_metadata_integration_example_spec.rb",
83
75
  "spec/spec_helper.rb",
84
- "spec/unit/accessors_spec.rb",
85
76
  "spec/unit/container_spec.rb",
86
77
  "spec/unit/document_spec.rb",
87
- "spec/unit/generator_spec.rb",
88
78
  "spec/unit/named_term_proxy_spec.rb",
89
79
  "spec/unit/node_generator_spec.rb",
90
80
  "spec/unit/om_spec.rb",
91
- "spec/unit/properties_spec.rb",
92
- "spec/unit/property_value_operators_spec.rb",
93
81
  "spec/unit/term_builder_spec.rb",
94
82
  "spec/unit/term_spec.rb",
95
83
  "spec/unit/term_value_operators_spec.rb",
@@ -11,9 +11,11 @@
11
11
  </titleInfo>
12
12
 
13
13
  <name type="personal">
14
+ Describes a person
14
15
  <namePart type="family">FAMILY NAME</namePart>
15
16
  <namePart type="given">GIVEN NAMES</namePart>
16
17
  <namePart type="termsOfAddress">DR.</namePart>
18
+ <namePart>PERSON_ID</namePart>
17
19
  <displayForm>NAME AS IT APPEARS</displayForm>
18
20
  <affiliation>FACULTY, UNIVERSITY</affiliation>
19
21
  <role>
@@ -28,6 +30,7 @@
28
30
  <namePart type="family">Gautama</namePart>
29
31
  <namePart type="given">Siddartha</namePart>
30
32
  <namePart type="termsOfAddress">Prince</namePart>
33
+ <namePart>123987</namePart>
31
34
  <affiliation>Nirvana</affiliation>
32
35
  <role>
33
36
  <roleTerm authority="marcrelator" type="text">teacher</roleTerm>
@@ -35,6 +35,7 @@ describe "OM::XML::Document" do
35
35
  t.last_name(:path=>"namePart", :attributes=>{:type=>"family"})
36
36
  t.first_name(:path=>"namePart", :attributes=>{:type=>"given"}, :label=>"first name")
37
37
  t.terms_of_address(:path=>"namePart", :attributes=>{:type=>"termsOfAddress"})
38
+ t.person_id(:path=>"namePart", :attributes=>{:type=>:none})
38
39
  }
39
40
  # lookup :person, :first_name
40
41
  t.person(:ref=>:name, :attributes=>{:type=>"personal"})
@@ -113,6 +114,9 @@ describe "OM::XML::Document" do
113
114
  @mods_article.find_by_terms( {:person=>1}, :first_name ).class.should == Nokogiri::XML::NodeSet
114
115
  @mods_article.find_by_terms( {:person=>1}, :first_name ).first.text.should == "Siddartha"
115
116
  end
117
+ it "should find a NodeSet where a terminology attribute has been set to :none" do
118
+ @mods_article.find_by_terms( {:person=>1}, :person_id).first.text.should == "123987"
119
+ end
116
120
  it "should support accessors whose relative_xpath is a lookup array instead of an xpath string" do
117
121
  # pending "this only impacts scenarios where we want to display & edit"
118
122
  DocumentTest.terminology.retrieve_term(:title_info, :language).path.should == {:attribute=>"lang"}
@@ -128,7 +132,7 @@ describe "OM::XML::Document" do
128
132
  pending "Can't decide if it's better to return nil or raise an error. Choosing informative errors for now."
129
133
  @mods_article.find_by_terms( {:foo=>20}, :bar ).should == nil
130
134
  end
131
-
135
+
132
136
  it "should support terms that point to attributes instead of nodes" do
133
137
  @mods_article.find_by_terms( {:title_info=>1}, :language ).first.text.should == "finnish"
134
138
  end
@@ -16,6 +16,10 @@ describe "OM::XML::TermValueOperators" do
16
16
  result.length.should == expected_values.length
17
17
  expected_values.each {|v| result.should include(v)}
18
18
  end
19
+
20
+ it "should ignore whitespace elements for a term pointing to a text() node for an element that contains children" do
21
+ @article.term_values(:name, :name_content).should == ["Describes a person"]
22
+ end
19
23
 
20
24
  end
21
25
 
@@ -72,7 +76,7 @@ describe "OM::XML::TermValueOperators" do
72
76
  end
73
77
 
74
78
  it "should destringify the field key/find_by_terms_and_value pointer" do
75
- OM::Samples::ModsArticle.terminology.expects(:xpath_with_indexes).with( *[{:person=>0}, :role]).times(7).returns("//oxns:name[@type=\"personal\"][1]/oxns:role")
79
+ OM::Samples::ModsArticle.terminology.expects(:xpath_with_indexes).with( *[{:person=>0}, :role]).times(10).returns("//oxns:name[@type=\"personal\"][1]/oxns:role")
76
80
  OM::Samples::ModsArticle.terminology.stubs(:xpath_with_indexes).with( *[{:person=>0}]).returns("//oxns:name[@type=\"personal\"][1]")
77
81
  @article.update_values( { [{":person"=>"0"}, "role"]=>"the role" } )
78
82
  @article.update_values( { [{"person"=>"0"}, "role"]=>"the role" } )
@@ -183,6 +187,13 @@ describe "OM::XML::TermValueOperators" do
183
187
  @article.update_values({[:journal, :title_info]=>{"0"=>:delete}})
184
188
  @article.term_values(:journal, :title_info).should == ['mork']
185
189
  end
190
+
191
+ it "should retain other child nodes when updating a text content term and shoud not append an additional text node but update text in place" do
192
+ @article.term_values(:name,:name_content).should == ["Describes a person"]
193
+ @article.update_values({[:name, :name_content]=>"Test text"})
194
+ @article.term_values(:name,:name_content).should == ["Test text"]
195
+ @article.find_by_terms(:name).children.length().should == 30
196
+ end
186
197
 
187
198
  end
188
199
 
@@ -406,4 +417,4 @@ describe "OM::XML::TermValueOperators" do
406
417
  end
407
418
  end
408
419
 
409
- end
420
+ end
@@ -22,6 +22,8 @@ describe "OM::XML::TermXpathGeneratorSpec" do
22
22
  @test_term_with_default_path = OM::XML::Term.new(:volume, :path=>"detail", :attributes=>{:type=>"volume"}, :default_content_path=>"number")
23
23
  @test_role_text = OM::XML::Term.new(:role_text, :path=>"roleTerm", :attributes=>{:type=>"text"})
24
24
  @test_lang_attribute = OM::XML::Term.new(:language, :path=>{:attribute=>"lang"})
25
+ @test_none_attribute_value = OM::XML::Term.new(:person_id, :path=>"namePart", :attributes=>{:type=>:none})
26
+
25
27
  end
26
28
 
27
29
  it "should support terms that are pointers to attribute values" do
@@ -51,6 +53,14 @@ describe "OM::XML::TermXpathGeneratorSpec" do
51
53
  @test_term.namespace_prefix = nil
52
54
  OM::XML::TermXpathGenerator.generate_relative_xpath(@test_term).should == 'namePart[@type="termsOfAddress"]'
53
55
  end
56
+ it "should not use a namespace for a path set to text() and should include normalize-space to ignore white space" do
57
+ text_term = OM::XML::Term.new(:title_content, :path=>"text()")
58
+ OM::XML::TermXpathGenerator.generate_relative_xpath(text_term).should == 'text()[normalize-space(.)]'
59
+ end
60
+ it "should set a 'not' predicate if the attribute value is :none" do
61
+ OM::XML::TermXpathGenerator.generate_relative_xpath(@test_none_attribute_value).should == 'oxns:namePart[not(@type)]'
62
+ end
63
+
54
64
  end
55
65
 
56
66
  describe "generate_absolute_xpath" do
@@ -108,4 +118,4 @@ describe "OM::XML::TermXpathGeneratorSpec" do
108
118
  OM::XML::TermXpathGenerator.generate_constrained_xpath(@test_term_with_default_path).should == '//oxns:detail[contains(oxns:number[@type="volume"], "#{constraint_value}")]'.gsub('"', '\"')
109
119
  end
110
120
 
111
- end
121
+ end