om 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- om (1.2.4)
4
+ om (1.3.0)
5
+ mediashelf-loggable
5
6
  nokogiri (>= 1.4.2)
6
- om
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
@@ -12,15 +12,10 @@ GEM
12
12
  columnize (0.3.2)
13
13
  equivalent-xml (0.2.6)
14
14
  nokogiri (>= 1.4.3)
15
- git (1.2.5)
16
- jeweler (1.5.2)
17
- bundler (~> 1.0.0)
18
- git (>= 1.2.5)
19
- rake
20
15
  linecache (0.43)
16
+ mediashelf-loggable (0.4.7)
21
17
  mocha (0.9.12)
22
18
  nokogiri (1.4.4)
23
- rake (0.8.7)
24
19
  rcov (0.9.9)
25
20
  rspec (1.3.1)
26
21
  ruby-debug (0.10.4)
@@ -36,7 +31,6 @@ PLATFORMS
36
31
  DEPENDENCIES
37
32
  RedCloth
38
33
  equivalent-xml (>= 0.2.4)
39
- jeweler
40
34
  mocha (>= 0.9.8)
41
35
  om!
42
36
  rcov
@@ -1,3 +1,7 @@
1
+ h3. 1.4.0
2
+
3
+ Added dynamic node access DSL. Added a warning when calling an index on a proxy term.
4
+
1
5
  h3. 1.3.0
2
6
 
3
7
  Document automatically includes Validation module, meaning that you can now call .validate on any document
data/lib/om.rb CHANGED
@@ -34,12 +34,17 @@ module OM
34
34
  end
35
35
  end
36
36
 
37
- # @pointers pointers array that you would pass into other Accessor methods
38
- # @include_indices (default: true) if set to false, parent indices will be excluded from the array
39
- # Converts an array of accessor pointers into a flat array.
40
- # ie. [{:conference=>0}, {:role=>1}, :text] becomes [:conference, 0, :role, 1, :text]
41
- # if include_indices is set to false,
42
- # [{:conference=>0}, {:role=>1}, :text] becomes [:conference, :role, :text]
37
+ # Convert a Term pointer into a flat array without Hashes.
38
+ # If include_indices is set to false, node indices will be removed.
39
+ #
40
+ # @param [Array] pointers array that you would pass into other Accessor methods
41
+ # @param [Boolean] include_indices (default: true) if set to false, parent indices will be excluded from the array
42
+ # @example Turn a pointer into a flat array with node indices preserved
43
+ # OM.pointers_to_flat_array( [{:conference=>0}, {:role=>1}, :text] )
44
+ # => [:conference, 0, :role, 1, :text]
45
+ # @example Remove node indices by setting include_indices to false
46
+ # OM.pointers_to_flat_array( [{:conference=>0}, {:role=>1}, :text], false )
47
+ # => [:conference, :role, :text]
43
48
  def self.pointers_to_flat_array(pointers, include_indices=true)
44
49
  flat_array = []
45
50
  pointers.each do |pointer|
@@ -1,3 +1,3 @@
1
1
  module Om
2
- VERSION = "1.3.0"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -8,6 +8,7 @@ require "om/xml/node_generator"
8
8
  require "om/xml/term_value_operators"
9
9
  require "om/xml/named_term_proxy"
10
10
  require "om/xml/document"
11
+ require "om/xml/dynamic_node"
11
12
 
12
13
  require "om/xml/template_registry"
13
14
 
@@ -44,15 +44,26 @@ module OM::XML::Document
44
44
  klass.send(:include, OM::XML::TermValueOperators)
45
45
  klass.send(:include, OM::XML::Validation)
46
46
  end
47
+
48
+ def method_missing(name, *args)
49
+ term = self.class.terminology.retrieve_term(name)
50
+ if (term)
51
+ OM::XML::DynamicNode.new(name, args.first, self, term)
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+
58
+ def find_by_xpath(xpath)
59
+ ng_xml.xpath(xpath, ox_namespaces)
60
+ end
61
+
47
62
 
48
63
  # Applies the property's corresponding xpath query, returning the result Nokogiri::XML::NodeSet
49
64
  def find_by_terms_and_value(*term_pointer)
50
65
  xpath = self.class.terminology.xpath_for(*term_pointer)
51
- if xpath.nil?
52
- return nil
53
- else
54
- return ng_xml.xpath(xpath, ox_namespaces)
55
- end
66
+ find_by_xpath(xpath) unless xpath.nil?
56
67
  end
57
68
 
58
69
 
@@ -64,11 +75,7 @@ module OM::XML::Document
64
75
  # Currently, indexes must be integers.
65
76
  def find_by_terms(*term_pointer)
66
77
  xpath = self.class.terminology.xpath_with_indexes(*term_pointer)
67
- if xpath.nil?
68
- return nil
69
- else
70
- return ng_xml.xpath(xpath, ox_namespaces)
71
- end
78
+ find_by_xpath(xpath) unless xpath.nil?
72
79
  end
73
80
 
74
81
  # Test whether the document has a node corresponding to the given term_pointer
@@ -146,4 +153,4 @@ module OM::XML::Document
146
153
  end
147
154
  template_registry.send(method, target, *args, &block)
148
155
  end
149
- end
156
+ end
@@ -0,0 +1,184 @@
1
+ module OM
2
+ module XML
3
+ #
4
+ # Provides a natural syntax for using OM Terminologies to access values from xml Documents
5
+ #
6
+ # @example Return an array of the value(s) "start page" node(s) from the second issue node within the first journal node
7
+ # # Using DynamicNode syntax:
8
+ # @article.journal(0).issue(1).pages.start
9
+ # # Other ways to perform this query:
10
+ # @article.find_by_terms({:journal => 0}, {:issue => 1}, :pages, :start)
11
+ # @article.xpath("//oxns:relatedItem[@type=\"host\"]/oxns:part[2]/extent[@unit="pages"]")
12
+ #
13
+ # @example Return an NodeSet of the _first titles_ of all journal nodes
14
+ # # Using DynamicNode syntax:
15
+ # @article.journal.title(1)
16
+ # # Other ways to perform this query:
17
+ # @article.find_by_terms(:journal, {:title => 1})
18
+ # @article.xpath("//oxns:relatedItem[@type=\"host\"]/oxns:titleInfo/oxns:title[1]")
19
+ #
20
+ # @example Find all of the titles from all journals & return the first title Node from that NodeSet
21
+ # # Using DynamicNode syntax:
22
+ # @article.journal.title[1]
23
+ # # Other ways to perform this query:
24
+ # @article.find_by_terms(:journal, :title)[1]
25
+ # @article.xpath("//oxns:relatedItem[@type=\"host\"]/oxns:titleInfo/oxns:title")[1]
26
+ #
27
+ class DynamicNode
28
+ attr_accessor :key, :index, :parent, :addressed_node, :term
29
+ def initialize(key, index, document, term, parent=nil) ##TODO a real term object in here would make it easier to lookup
30
+ self.key = key
31
+ self.index = index
32
+ @document = document
33
+ self.term = term
34
+ self.parent = parent
35
+ end
36
+
37
+ def method_missing (name, *args)
38
+ if /=$/.match(name.to_s)
39
+ new_update_node(name, args)
40
+ elsif args.length > 1
41
+ new_update_node_with_index(name, args)
42
+ else
43
+ child = term_child_by_name(term.nil? ? parent.term : term, name)
44
+ if child
45
+ OM::XML::DynamicNode.new(name, args.first, @document, child, self)
46
+ else
47
+ val.send(name, *args)
48
+ end
49
+ end
50
+ end
51
+
52
+ def new_update_node(name, args)
53
+ modified_name = name.to_s.chop.to_sym
54
+ child = term.retrieve_term(modified_name)
55
+ node = OM::XML::DynamicNode.new(modified_name, nil, @document, child, self)
56
+ node.val=args
57
+ end
58
+
59
+ def new_update_node_with_index(name, args)
60
+ index = args.shift
61
+ child = term.retrieve_term(name)
62
+ node = OM::XML::DynamicNode.new(name, index, @document, child, self)
63
+ node.val=args
64
+ end
65
+
66
+ def val=(args)
67
+ new_values = sanitize_new_values(args.first)
68
+ new_values.each do |y,z|
69
+ ## If we pass something that already has an index on it, we should be able to add it.
70
+ if @document.find_by_xpath(xpath)[y.to_i].nil? || y.to_i == -1
71
+ @document.term_values_append(:parent_select=> parent.to_pointer,:parent_index=>0,:template=>to_pointer,:values=>z)
72
+ else
73
+ @document.term_value_update(xpath, y.to_i, z)
74
+ end
75
+ end
76
+ end
77
+
78
+ def sanitize_new_values(new_values)
79
+ # Sanitize new_values to always be a hash with indexes
80
+ case new_values
81
+ when Hash
82
+ when Array
83
+ nv = new_values.dup
84
+ new_values = {}
85
+ nv.each {|v| new_values[nv.index(v).to_s] = v}
86
+ else
87
+ new_values = {"0"=>new_values}
88
+ end
89
+ new_values
90
+ end
91
+
92
+ def term_child_by_name(term, name)
93
+ if (term.kind_of? NamedTermProxy)
94
+ @document.class.terminology.retrieve_node(*(term.proxy_pointer.dup << name))
95
+ else
96
+ term.retrieve_term(name)
97
+ end
98
+ end
99
+
100
+ def val
101
+ query = xpath
102
+ trim_text = !query.index("text()").nil?
103
+ return @document.find_by_xpath(query).collect {|node| (trim_text ? node.text.strip : node.text) }
104
+ end
105
+
106
+ def nodeset
107
+ query = xpath
108
+ trim_text = !query.index("text()").nil?
109
+ return @document.find_by_xpath(query)
110
+ end
111
+
112
+ def inspect
113
+ val.inspect
114
+ end
115
+
116
+ def ==(other)
117
+ val == other
118
+ end
119
+
120
+ def eql?(other)
121
+ self == other
122
+ end
123
+
124
+ def to_pointer
125
+ if self.index
126
+ parent.nil? ? [{key => index}] : parent.to_pointer << {key => index}
127
+ else ### A pointer
128
+ parent.nil? ? [key] : parent.to_pointer << key
129
+ end
130
+ end
131
+
132
+ def xpath
133
+ if parent.nil?
134
+ @document.class.terminology.xpath_with_indexes(*(to_pointer << {})) ### last element is always filters
135
+ else
136
+ chain = retrieve_addressed_node( )
137
+ '//' + chain.map { |n| n.xpath}.join('/')
138
+ end
139
+
140
+ end
141
+
142
+
143
+ class AddressedNode
144
+ attr_accessor :xpath, :key, :pointer
145
+ def initialize (pointer, xpath, key)
146
+ self.xpath = xpath
147
+ self.key = key
148
+ self.pointer = pointer
149
+ end
150
+ end
151
+
152
+ ##
153
+ # This is very similar to Terminology#retrieve_term, however it expands proxy paths out into their cannonical paths
154
+ def retrieve_addressed_node()
155
+ chain = []
156
+
157
+ if parent
158
+ chain += parent.retrieve_addressed_node()
159
+ end
160
+ if (self.index)
161
+ ### This is an index
162
+ node = AddressedNode.new(key, term.xpath_relative, self)
163
+ node.xpath = OM::XML::TermXpathGenerator.add_node_index_predicate(node.xpath, index)
164
+ chain << node
165
+ elsif (term.kind_of? NamedTermProxy)
166
+ proxy = term.proxy_pointer.dup
167
+ first = proxy.shift
168
+ p = @document.class.terminology.retrieve_node(*first)
169
+ chain << AddressedNode.new(p, p.xpath_relative, self)
170
+ while !proxy.empty?
171
+ first = proxy.shift
172
+ p = p.retrieve_term(first)
173
+ chain << AddressedNode.new(p, p.xpath_relative, self)
174
+ end
175
+ else
176
+ chain << AddressedNode.new(key, term.xpath_relative, self)
177
+ end
178
+ chain
179
+ end
180
+
181
+
182
+ end
183
+ end
184
+ end
@@ -140,6 +140,12 @@ class OM::XML::TemplateRegistry
140
140
  end
141
141
 
142
142
  private
143
+
144
+ # Create a new Nokogiri::XML::Node based on the template for +node_type+
145
+ #
146
+ # @param [Nokogiri::XML::Node] builder_node The node to use as starting point for building the node using Nokogiri::XML::Builder.with(builder_node). This provides namespace info, etc for constructing the new Node object. If nil, defaults to {OM::XML::TemplateRegistry#empty_root_node}. This is just used to create the new node and will not be included in the response.
147
+ # @param node_type a pointer to the template to use when creating the node
148
+ # @param [Array] args any additional args
143
149
  def create_detached_node(builder_node, node_type, *args)
144
150
  proc = @templates[node_type]
145
151
  if proc.nil?
@@ -155,6 +161,13 @@ class OM::XML::TemplateRegistry
155
161
  builder_node.elements.last.remove
156
162
  end
157
163
 
164
+ # Create a new XML node of type +node_type+ and attach it to +target_node+ using the specified +method+
165
+ #
166
+ # @param [Symbol] method name that should be called on +target_node+, usually a Nokogiri::XML::Node instance method
167
+ # @param [Nokogiri::XML::Node or Nokogiri::XML::NodeSet with only one Node in it] target_node
168
+ # @param [Symbol] builder_node_offset Indicates node to use as the starting point for _constructing_ the new node using {OM::XML::TemplateRegistry#create_detached_node}. If this is set to :parent, target_node.parent will be used. Otherwise, target_node will be used.
169
+ # @param node_type
170
+ # @param [Array] args any additional arguments for creating the node
158
171
  def attach_node(method, target_node, builder_node_offset, node_type, *args, &block)
159
172
  if target_node.is_a?(Nokogiri::XML::NodeSet) and target_node.length == 1
160
173
  target_node = target_node.first
@@ -126,12 +126,44 @@ class OM::XML::Term
126
126
  end
127
127
  end
128
128
 
129
- # Term Class Definition
130
-
131
- attr_accessor :name, :xpath, :xpath_constrained, :xpath_relative, :path, :index_as, :required, :data_type, :variant_of, :path, :attributes, :default_content_path, :namespace_prefix, :is_root_term
129
+ #
130
+ # Class Definition for Term
131
+ #
132
+
133
+ include OM::TreeNode
134
+
135
+ attr_accessor :name, :xpath, :xpath_constrained, :xpath_relative, :path, :index_as, :required, :data_type, :variant_of, :path, :default_content_path, :is_root_term
132
136
  attr_accessor :children, :internal_xml, :terminology
133
137
 
134
- include OM::TreeNode
138
+ # Any XML attributes that qualify the Term.
139
+ #
140
+ # @example Declare a Term that has a given attribute (ie. //title[@xml:lang='eng'])
141
+ # t.english_title(:path=>"title", :attributes=>{"xml:lang"=>"eng"}
142
+ # @example Use nil to point to nodes that do not have a given attribute (ie. //title[not(@xml:lang)])
143
+ # t.title_without_lang_attribute(:path=>"title", :attributes=>{"xml:lang"=>nil})
144
+ attr_accessor :attributes
145
+
146
+ # Namespace Prefix (xmlns) for the Term.
147
+ #
148
+ # 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 :namespace_prefix into its initializer (or call .namespace_prefix= on its builder)
149
+ # If a node has _no_ namespace, you must explicitly set namespace_prefix to nil. Currently you have to do this on _each_ term, you can't set namespace_prefix to nil for an entire Terminology.
150
+ #
151
+ # @example
152
+ # # For xml like this
153
+ # <foo xmlns="http://foo.com/schemas/fooschema" xmlns:bar="http://bar.com/schemas/barschema">
154
+ # <address>1400 Pennsylvania Avenue</address>
155
+ # <bar:latitude>56</bar:latitude>
156
+ # </foo>
157
+ #
158
+ # # The Terminology would look like this
159
+ # OM::XML::Terminology::Builder.new do |t|
160
+ # t.root(:name=>:foo, :path=>"foo", :xmlns=>"http://foo.com/schemas/fooschema", "xmlns:bar"=>"http://bar.com/schemas/barschema")
161
+ # t.address
162
+ # t.latitude(:namespace_prefix=>"bar")
163
+ # end
164
+ #
165
+ attr_accessor :namespace_prefix
166
+
135
167
 
136
168
  # h2. Namespaces
137
169
  # 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)
@@ -215,7 +247,15 @@ class OM::XML::Term
215
247
  node_options << "\'#{k}\'=>\'#{v}\'"
216
248
  end
217
249
  end
218
- template = "xml.#{self.path}( #{OM::XML.delimited_list(node_options)} )" + node_child_template
250
+ if self.path.include?(":")
251
+ ns_prefix = self.path[0..path.index(":")-1]
252
+ path_name = self.path[path.index(":")+1..-1]
253
+ template = "xml[\"#{ns_prefix}\"].#{path_name}( #{OM::XML.delimited_list(node_options)} )" + node_child_template
254
+ elsif !self.namespace_prefix.nil? and self.namespace_prefix != 'oxns'
255
+ template = "xml[\"#{self.namespace_prefix}\"].#{self.path}( #{OM::XML.delimited_list(node_options)} )" + node_child_template
256
+ else
257
+ template = "xml.#{self.path}( #{OM::XML.delimited_list(node_options)} )" + node_child_template
258
+ end
219
259
  return template.gsub( /:::(.*?):::/ ) { '#{'+$1+'}' }
220
260
  end
221
261
 
@@ -285,4 +325,4 @@ class OM::XML::Term
285
325
 
286
326
  # private :update_xpath_values
287
327
 
288
- end
328
+ end
@@ -162,7 +162,7 @@ module OM::XML::TermValueOperators
162
162
  to_build = [parent_select.pop] + to_build
163
163
  starting_point = find_by_terms(*parent_select)
164
164
  if starting_point.empty? && parent_select.empty?
165
- raise OM::XML::TemplateMissingException, "Cannot insert nodes into the document because it is empty."
165
+ raise OM::XML::TemplateMissingException, "Cannot insert nodes into the document because it is empty. Try defining self.xml_template on the #{self.class} class."
166
166
  end
167
167
  end
168
168
  to_build.each do |term_pointer|
@@ -187,7 +187,7 @@ module OM::XML::TermValueOperators
187
187
  starting_point = find_by_terms(*parent_select+[{}])
188
188
  # If pointers in parent_select don't match with the indexes of built ancestors, correct the hash
189
189
  if starting_point.empty?
190
- raise StandardError "Oops. Something went wrong adding #{term_pointer} to #{parent_select} while building ancestors"
190
+ raise ::StandardError, "Oops. Something went wrong adding #{term_pointer.inspect} to #{parent_select.inspect} while building ancestors. Expected to find something at #{self.class.terminology.xpath_for(*parent_select)}. The current xml is\n #{self.to_xml}"
191
191
  end
192
192
  end
193
193
  if parent_index > starting_point.length
@@ -1,4 +1,6 @@
1
+ require 'loggable'
1
2
  module OM::XML::TermXpathGenerator
3
+ include Loggable
2
4
 
3
5
  # Generate relative xpath for a term
4
6
  # @param [OM::XML::Term] term that you want to generate relative xpath for
@@ -161,11 +163,12 @@ module OM::XML::TermXpathGenerator
161
163
 
162
164
  term = terminology.retrieve_term(*keys)
163
165
  # Return nil if there is no term to work with
164
- if term.nil? then return nil end
166
+ return if term.nil?
165
167
 
166
- # If we've encountered a NamedTermProxy, insert path sections corresponding to
167
- # terms corresponding to each entry in its proxy_pointer rather than just the final term that it points to.
168
+ # If we've encountered a NamedTermProxy, insert path sections corresponding to each entry in its proxy_pointer (rather than just the final term that it points to).
169
+ # TODO Looks like this only works if the last key is a NamedTermProxy, what if we cross proxies on the way there?
168
170
  if term.kind_of? OM::XML::NamedTermProxy
171
+ logger.warn "You attempted to call an index value of #{index} on the term \"#{k.inspect}\". However \"#{k.inspect}\" is a proxy so we are ignoring the index. See https://jira.duraspace.org/browse/HYDRA-643" if index
169
172
  current_location = term.parent.nil? ? term.terminology : term.parent
170
173
  relative_path = ""
171
174
  term.proxy_pointer.each_with_index do |proxy_pointer, proxy_pointer_index|
@@ -140,6 +140,7 @@ class OM::XML::Terminology
140
140
  end
141
141
 
142
142
  # Returns the Term corresponding to the given _pointer_.
143
+ # Proxies are not expanded
143
144
  def retrieve_term(*args)
144
145
  args_cp = args.dup
145
146
  current_term = terms[args_cp.delete_at(0)]
@@ -155,40 +156,61 @@ class OM::XML::Terminology
155
156
  end
156
157
  return current_term
157
158
  end
158
-
159
+
160
+ def retrieve_node_subsequent(args, context)
161
+ current_term = context.children[args.shift]
162
+ if current_term.kind_of? OM::XML::NamedTermProxy
163
+ args = (current_term.proxy_pointer + args).flatten
164
+ current_term = context.children[args.shift]
165
+ end
166
+ args.empty? ? current_term : retrieve_node_subsequent(args, current_term)
167
+ end
168
+
169
+
170
+ ##
171
+ # This is very similar to retrieve_term, however it expands proxy paths out into their cannonical paths
172
+ def retrieve_node(*args)
173
+ current_term = terms[args.shift]
174
+ if current_term.kind_of? OM::XML::NamedTermProxy
175
+ args = (current_term.proxy_pointer + args).flatten
176
+ current_term = terms[args.shift]
177
+ end
178
+ args.empty? ? current_term : retrieve_node_subsequent(args, current_term)
179
+ end
180
+
181
+
159
182
  # Return the appropriate xpath query for retrieving nodes corresponding to the term identified by +pointers+.
160
183
  # If the last argument is a String or a Hash, it will be used to add +constraints+ to the resulting xpath query.
161
184
  # If you provide an xpath query as the argument, it will be returne untouched.
162
185
  def xpath_for(*pointers)
163
186
  if pointers.length == 1 && pointers.first.instance_of?(String)
164
- xpath_query = pointers.first
165
- else
166
- query_constraints = nil
167
-
168
- if pointers.length > 1 && !pointers.last.kind_of?(Symbol)
169
- query_constraints = pointers.pop
170
- end
187
+ return pointers.first
188
+ end
189
+ query_constraints = nil
190
+
191
+ if pointers.length > 1 && !pointers.last.kind_of?(Symbol)
192
+ query_constraints = pointers.pop
193
+ end
171
194
 
172
- term = retrieve_term( *pointers )
195
+ term = retrieve_node( *pointers )
173
196
 
174
- if !term.nil?
175
- if query_constraints.kind_of?(String)
176
- constraint_value = query_constraints
177
- xpath_template = term.xpath_constrained
178
- xpath_query = eval( '"' + xpath_template + '"' )
179
- elsif query_constraints.kind_of?(Hash) && !query_constraints.empty?
180
- key_value_pair = query_constraints.first
181
- constraint_value = key_value_pair.last
182
- xpath_template = term.children[key_value_pair.first].xpath_constrained
183
- xpath_query = eval( '"' + xpath_template + '"' )
184
- else
185
- xpath_query = term.xpath
186
- end
187
- else
188
- xpath_query = nil
197
+ if !term.nil?
198
+ if query_constraints.kind_of?(String)
199
+ constraint_value = query_constraints
200
+ xpath_template = term.xpath_constrained
201
+ xpath_query = eval( '"' + xpath_template + '"' )
202
+ elsif query_constraints.kind_of?(Hash) && !query_constraints.empty?
203
+ key_value_pair = query_constraints.first
204
+ constraint_value = key_value_pair.last
205
+ xpath_template = term.children[key_value_pair.first].xpath_constrained
206
+ xpath_query = eval( '"' + xpath_template + '"' )
207
+ else
208
+ xpath_query = term.xpath
189
209
  end
210
+ else
211
+ xpath_query = nil
190
212
  end
191
- return xpath_query
213
+ xpath_query
192
214
  end
193
215
 
194
216
  # Use the current terminology to generate an xpath with (optional) node indexes for each of the term pointers.
@@ -40,7 +40,7 @@ namespace :om do
40
40
  yt.files = Dir.glob(File.join(project_root, 'lib', '**', '*.rb')) + textile_docs
41
41
  # [ File.join(project_root, 'README.textile') ]
42
42
  # [ File.join(project_root, 'README.textile'),'-', File.join(project_root,'GETTING_STARTED.textile') ]
43
- yt.options = ['--output-dir', doc_destination, '--readme', readme_filename]
43
+ yt.options = ['--private', '--protected', '--output-dir', doc_destination, '--readme', readme_filename]
44
44
  end
45
45
  rescue LoadError
46
46
  desc "Generate YARD Documentation"
data/om.gemspec CHANGED
@@ -13,6 +13,7 @@ Gem::Specification.new do |s|
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
 
15
15
  s.add_dependency('nokogiri', ">= 1.4.2")
16
+ s.add_dependency('mediashelf-loggable')
16
17
  s.add_development_dependency "rspec", "<2.0.0"
17
18
  s.add_development_dependency "mocha", ">= 0.9.8"
18
19
  s.add_development_dependency "ruby-debug"
@@ -3,7 +3,7 @@
3
3
 
4
4
  <titleInfo>
5
5
  <nonSort>THE</nonSort>
6
- <title>ARTICLE TITLE HYDRANGEA ARTICLE 1</title>
6
+ <title xml:lang="eng">ARTICLE TITLE HYDRANGEA ARTICLE 1</title>
7
7
  <subTitle>SUBTITLE</subTitle>
8
8
  </titleInfo>
9
9
  <titleInfo lang="finnish">
@@ -89,4 +89,4 @@
89
89
  <accessCondition type="restrictionOnAccess">EMBARGO NOTE</accessCondition>
90
90
  <accessCondition type="use and reproduction">OPEN ACCESS</accessCondition>
91
91
 
92
- </mods>
92
+ </mods>
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require "om"
3
+
4
+ describe "OM::XML::DynamicNode" 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 "dynamically created nodes" 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.person.last_name
16
+ result.length.should == expected_values.length
17
+ expected_values.each {|v| result.should include(v)}
18
+ end
19
+
20
+ it "should find elements two deep" do
21
+ #TODO reimplement so that method_missing with name is only called once. Create a new method for name.
22
+ @article.name.name_content.val.should == ["Describes a person"]
23
+ @article.name.name_content.should == ["Describes a person"]
24
+ @article.name.name_content(0).should == ["Describes a person"]
25
+ end
26
+
27
+ it "should not find elements that don't exist" do
28
+ lambda {@article.name.hedgehog}.should raise_exception NoMethodError
29
+ end
30
+
31
+ it "should allow you to call methods on the return value" do
32
+ @article.name.name_content.first.should == "Describes a person"
33
+ end
34
+
35
+ it "Should work with proxies" do
36
+ @article.title.should == ["ARTICLE TITLE HYDRANGEA ARTICLE 1", "Artikkelin otsikko Hydrangea artiklan 1", "TITLE OF HOST JOURNAL"]
37
+ @article.title.main_title_lang.should == ['eng']
38
+
39
+ @article.title(1).to_pointer.should == [{:title => 1}]
40
+
41
+ @article.journal_title.xpath.should == "//oxns:relatedItem[@type=\"host\"]/oxns:titleInfo/oxns:title"
42
+ @article.journal_title.should == ["TITLE OF HOST JOURNAL"]
43
+ end
44
+
45
+ it "Should be addressable to a specific node" do
46
+ @article.update_values( {[{:journal=>0}, {:issue=>3}, :pages, :start]=>{"0"=>"434"} })
47
+
48
+ @article.subject.topic(1).to_pointer == [:subject, {:topic => 1}]
49
+ @article.journal(0).issue.length.should == 2
50
+ @article.journal(0).issue(1).pages.to_pointer == [{:journal=>0}, {:issue=>1}, :pages]
51
+ @article.journal(0).issue(1).pages.length.should == 1
52
+ @article.journal(0).issue(1).pages.start.length.should == 1
53
+ @article.journal(0).issue(1).pages.start.first.should == "434"
54
+
55
+ @article.subject.topic(1).should == ["TOPIC 2"]
56
+ @article.subject.topic(1).xpath.should == "//oxns:subject/oxns:topic[2]"
57
+ end
58
+
59
+ describe ".nodeset" do
60
+ it "should return a Nokogiri NodeSet" do
61
+ @article.update_values( {[{:journal=>0}, {:issue=>3}, :pages, :start]=>{"0"=>"434"} })
62
+ nodeset = @article.journal(0).issue(1).pages.start.nodeset
63
+ nodeset.should be_kind_of Nokogiri::XML::NodeSet
64
+ nodeset.length.should == @article.journal(0).issue(1).pages.start.length
65
+ nodeset.first.text.should == @article.journal(0).issue(1).pages.start.first
66
+ end
67
+ end
68
+
69
+ it "should append nodes at the specified index if possible" do
70
+ @article.journal.title_info = ["all", "for", "the"]
71
+ @article.journal.title_info(3, 'glory')
72
+ @article.term_values(:journal, :title_info).should == ["all", "for", "the", "glory"]
73
+ end
74
+
75
+ end
76
+ end
@@ -53,4 +53,4 @@ describe "OM::XML::NamedTermProxy" do
53
53
  document.update_values([:parentfoobarproxy] => "FOObar!")
54
54
  document.term_values(:parentfoobarproxy).should == ["FOObar!"]
55
55
  end
56
- end
56
+ end
@@ -171,6 +171,13 @@ describe "OM::XML::Term" do
171
171
  @test_role_code.xml_builder_template(:attributes=>{"authority"=>"marcrelator"}).should == marcrelator_role_xml_builder_template
172
172
  end
173
173
 
174
+ it "should work for namespaced nodes" do
175
+ @ical_date = OM::XML::Term.new(:ical_date, :path=>"ical:date")
176
+ @ical_date.xml_builder_template.should == "xml[\"ical\"].date( '\#{builder_new_value}' )"
177
+ @ical_date = OM::XML::Term.new(:ical_date, :path=>"date", :namespace_prefix=>"ical")
178
+ @ical_date.xml_builder_template.should == "xml[\"ical\"].date( '\#{builder_new_value}' )"
179
+ end
180
+
174
181
  it "should work for nodes with default_content_path" do
175
182
  @test_volume.xml_builder_template.should == "xml.detail( \'type\'=>'volume' ) { xml.number( '\#{builder_new_value}' ) }"
176
183
  end
@@ -194,4 +201,4 @@ describe "OM::XML::Term" do
194
201
 
195
202
  end
196
203
 
197
- end
204
+ end
@@ -105,6 +105,9 @@ describe "OM::XML::TermValueOperators" do
105
105
  @article.find_by_terms({:journal=>0}, {:issue=>1}, :pages).length.should == 1
106
106
  @article.find_by_terms({:journal=>0}, {:issue=>1}, :pages, :start).length.should == 1
107
107
  @article.find_by_terms({:journal=>0}, {:issue=>1}, :pages, :start).first.text.should == "434"
108
+ #Last argument is a filter, we must explicitly pass no filter
109
+ @article.class.terminology.xpath_with_indexes(:subject, {:topic=>1}, {}).should == '//oxns:subject/oxns:topic[2]'
110
+ @article.find_by_terms(:subject, {:topic => 1}, {}).text.should == "TOPIC 2"
108
111
  end
109
112
 
110
113
  it "should accommodate appending term values with apostrophes in them" do
@@ -129,6 +132,8 @@ describe "OM::XML::TermValueOperators" do
129
132
  pending "HYDRA-415"
130
133
  @sample.update_values({['title_info', 'main_title', 'main_title_lang']=>'eng'})
131
134
  @sample.term_values('title_info', 'main_title', 'main_title_lang').should == ['eng']
135
+ ## After a proxy
136
+ @article.term_values(:title,:main_title_lang).should == ['eng']
132
137
  end
133
138
 
134
139
  ### Examples copied over form nokogiri_datastream_spec
@@ -442,7 +447,7 @@ describe "OM::XML::TermValueOperators" do
442
447
 
443
448
  describe "build_ancestors" do
444
449
  it "should raise an error if it cant find a starting point for building from" do
445
- lambda { @empty_sample.build_ancestors( [:journal, :issue], 0) }.should raise_error(OM::XML::TemplateMissingException, "Cannot insert nodes into the document because it is empty.")
450
+ lambda { @empty_sample.build_ancestors( [:journal, :issue], 0) }.should raise_error(OM::XML::TemplateMissingException, "Cannot insert nodes into the document because it is empty. Try defining self.xml_template on the OM::Samples::ModsArticle class.")
446
451
  end
447
452
  end
448
453
 
@@ -12,6 +12,7 @@ describe "OM::XML::TermXpathGeneratorSpec" do
12
12
  }
13
13
  # lookup :person, :first_name
14
14
  t.person(:ref=>:name, :attributes=>{:type=>"personal"})
15
+ t.family_name(:proxy=>[:name, :family_name])
15
16
  end
16
17
  @sample_terminology = builder.build
17
18
  @rootless_terminology = OM::XML::Terminology.new
@@ -106,6 +107,12 @@ describe "OM::XML::TermXpathGeneratorSpec" do
106
107
  end
107
108
  it "should destringify term pointers before using them" do
108
109
  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"]'
110
+ ### Last argument is a filter, we are passing no filters
111
+ @sample_terminology.xpath_with_indexes(:name, {:family_name=>1},{}).should == '//oxns:name/oxns:namePart[@type="family"][2]'
112
+ end
113
+ it "should warn about indexes on a proxy" do
114
+ Logger.any_instance.expects(:warn).with("You attempted to call an index value of 1 on the term \":family_name\". However \":family_name\" is a proxy so we are ignoring the index. See https://jira.duraspace.org/browse/HYDRA-643")
115
+ @sample_terminology.xpath_with_indexes({:family_name=>1}).should == "//oxns:name/oxns:namePart[@type=\"family\"]"
109
116
  end
110
117
  end
111
118
 
@@ -79,6 +79,8 @@ describe "OM::XML::Terminology::Builder" do
79
79
  terminology.retrieve_term(:title).should be_kind_of OM::XML::NamedTermProxy
80
80
  terminology.xpath_for(:title).should == '//oxns:titleInfo/oxns:title'
81
81
  terminology.xpath_with_indexes({:title=>0}).should == "//oxns:titleInfo/oxns:title"
82
+ # @builder_with_block.build.xpath_for_pointer(:issue).should == '//oxns:part'
83
+ # terminology.xpath_for_pointer(:title).should == '//oxns:titleInfo/oxns:title'
82
84
  end
83
85
 
84
86
  describe '#new' do
@@ -198,4 +200,4 @@ describe "OM::XML::Terminology::Builder" do
198
200
  end
199
201
  end
200
202
 
201
- end
203
+ end
@@ -17,7 +17,9 @@ describe "OM::XML::Terminology" do
17
17
  t.root(:path=>"mods", :xmlns=>"http://www.loc.gov/mods/v3", :schema=>"http://www.loc.gov/standards/mods/v3/mods-3-2.xsd")
18
18
 
19
19
  t.title_info(:path=>"titleInfo") {
20
- t.main_title(:path=>"title", :label=>"title")
20
+ t.main_title(:path=>"title", :label=>"title") {
21
+ t.main_title_lang(:path=>{:attribute=> "xml:lang"})
22
+ }
21
23
  t.language(:path=>{:attribute=>"lang"})
22
24
  }
23
25
  # t.title(:path=>"titleInfo", :default_content_path=>"title") {
@@ -65,6 +67,7 @@ describe "OM::XML::Terminology" do
65
67
  t.start_page(:proxy=>[:pages, :start])
66
68
  t.end_page(:proxy=>[:pages, :end])
67
69
  }
70
+ t.title(:proxy=>[:title_info, :main_title])
68
71
  end
69
72
 
70
73
  @test_full_terminology = @builder_with_block.build
@@ -82,6 +85,10 @@ describe "OM::XML::Terminology" do
82
85
  @test_full_terminology.retrieve_term(:person, :person_id).xpath_relative.should == 'oxns:namePart[not(@type)]'
83
86
  end
84
87
 
88
+ it "should expand proxy and get sub terms" do
89
+ @test_full_terminology.retrieve_node(:title, :main_title_lang).xpath.should == '//oxns:titleInfo/oxns:title/@xml:lang'
90
+ end
91
+
85
92
  it "constructs templates for value-driven searches" do
86
93
  @test_full_terminology.retrieve_term(:name).xpath_constrained.should == '//oxns:name[contains(., "#{constraint_value}")]'.gsub('"', '\"')
87
94
  @test_full_terminology.retrieve_term(:person).xpath_constrained.should == '//oxns:name[@type="personal" and contains(., "#{constraint_value}")]'.gsub('"', '\"')
@@ -324,4 +331,4 @@ describe "OM::XML::Terminology" do
324
331
  end
325
332
  end
326
333
 
327
- end
334
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: om
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 1.3.0
10
+ version: 1.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matt Zumwalt
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-07-26 00:00:00 -05:00
18
+ date: 2011-08-29 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -35,9 +35,23 @@ dependencies:
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
38
- name: rspec
38
+ name: mediashelf-loggable
39
39
  prerelease: false
40
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rspec
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
41
55
  none: false
42
56
  requirements:
43
57
  - - <
@@ -49,11 +63,11 @@ dependencies:
49
63
  - 0
50
64
  version: 2.0.0
51
65
  type: :development
52
- version_requirements: *id002
66
+ version_requirements: *id003
53
67
  - !ruby/object:Gem::Dependency
54
68
  name: mocha
55
69
  prerelease: false
56
- requirement: &id003 !ruby/object:Gem::Requirement
70
+ requirement: &id004 !ruby/object:Gem::Requirement
57
71
  none: false
58
72
  requirements:
59
73
  - - ">="
@@ -65,11 +79,11 @@ dependencies:
65
79
  - 8
66
80
  version: 0.9.8
67
81
  type: :development
68
- version_requirements: *id003
82
+ version_requirements: *id004
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: ruby-debug
71
85
  prerelease: false
72
- requirement: &id004 !ruby/object:Gem::Requirement
86
+ requirement: &id005 !ruby/object:Gem::Requirement
73
87
  none: false
74
88
  requirements:
75
89
  - - ">="
@@ -79,11 +93,11 @@ dependencies:
79
93
  - 0
80
94
  version: "0"
81
95
  type: :development
82
- version_requirements: *id004
96
+ version_requirements: *id005
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: equivalent-xml
85
99
  prerelease: false
86
- requirement: &id005 !ruby/object:Gem::Requirement
100
+ requirement: &id006 !ruby/object:Gem::Requirement
87
101
  none: false
88
102
  requirements:
89
103
  - - ">="
@@ -95,7 +109,7 @@ dependencies:
95
109
  - 4
96
110
  version: 0.2.4
97
111
  type: :development
98
- version_requirements: *id005
112
+ version_requirements: *id006
99
113
  description: "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"
100
114
  email: matt.zumwalt@yourmediashelf.com
101
115
  executables: []
@@ -131,6 +145,7 @@ files:
131
145
  - lib/om/xml.rb
132
146
  - lib/om/xml/container.rb
133
147
  - lib/om/xml/document.rb
148
+ - lib/om/xml/dynamic_node.rb
134
149
  - lib/om/xml/named_term_proxy.rb
135
150
  - lib/om/xml/node_generator.rb
136
151
  - lib/om/xml/template_registry.rb
@@ -153,6 +168,7 @@ files:
153
168
  - spec/spec_helper.rb
154
169
  - spec/unit/container_spec.rb
155
170
  - spec/unit/document_spec.rb
171
+ - spec/unit/dynamic_node_spec.rb
156
172
  - spec/unit/named_term_proxy_spec.rb
157
173
  - spec/unit/node_generator_spec.rb
158
174
  - spec/unit/nokogiri_sanity_spec.rb
@@ -197,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
213
  requirements: []
198
214
 
199
215
  rubyforge_project:
200
- rubygems_version: 1.6.2
216
+ rubygems_version: 1.5.2
201
217
  signing_key:
202
218
  specification_version: 3
203
219
  summary: "OM (Opinionated Metadata): A library to help you tame sprawling XML schemas like MODS."
@@ -213,6 +229,7 @@ test_files:
213
229
  - spec/spec_helper.rb
214
230
  - spec/unit/container_spec.rb
215
231
  - spec/unit/document_spec.rb
232
+ - spec/unit/dynamic_node_spec.rb
216
233
  - spec/unit/named_term_proxy_spec.rb
217
234
  - spec/unit/node_generator_spec.rb
218
235
  - spec/unit/nokogiri_sanity_spec.rb