om 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +3 -9
- data/History.textile +4 -0
- data/lib/om.rb +11 -6
- data/lib/om/version.rb +1 -1
- data/lib/om/xml.rb +1 -0
- data/lib/om/xml/document.rb +18 -11
- data/lib/om/xml/dynamic_node.rb +184 -0
- data/lib/om/xml/template_registry.rb +13 -0
- data/lib/om/xml/term.rb +46 -6
- data/lib/om/xml/term_value_operators.rb +2 -2
- data/lib/om/xml/term_xpath_generator.rb +6 -3
- data/lib/om/xml/terminology.rb +47 -25
- data/lib/tasks/om.rake +1 -1
- data/om.gemspec +1 -0
- data/spec/fixtures/mods_articles/hydrangea_article1.xml +2 -2
- data/spec/unit/dynamic_node_spec.rb +76 -0
- data/spec/unit/named_term_proxy_spec.rb +1 -1
- data/spec/unit/term_spec.rb +8 -1
- data/spec/unit/term_value_operators_spec.rb +6 -1
- data/spec/unit/term_xpath_generator_spec.rb +7 -0
- data/spec/unit/terminology_builder_spec.rb +3 -1
- data/spec/unit/terminology_spec.rb +9 -2
- metadata +30 -13
data/Gemfile.lock
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
om (1.
|
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
|
data/History.textile
CHANGED
data/lib/om.rb
CHANGED
@@ -34,12 +34,17 @@ module OM
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
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|
|
data/lib/om/version.rb
CHANGED
data/lib/om/xml.rb
CHANGED
data/lib/om/xml/document.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/om/xml/term.rb
CHANGED
@@ -126,12 +126,44 @@ class OM::XML::Term
|
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
|
-
#
|
130
|
-
|
131
|
-
|
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
|
-
|
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
|
-
|
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?
|
166
|
+
return if term.nil?
|
165
167
|
|
166
|
-
# If we've encountered a NamedTermProxy, insert path sections corresponding to
|
167
|
-
#
|
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|
|
data/lib/om/xml/terminology.rb
CHANGED
@@ -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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
195
|
+
term = retrieve_node( *pointers )
|
173
196
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
-
|
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.
|
data/lib/tasks/om.rake
CHANGED
@@ -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
|
data/spec/unit/term_spec.rb
CHANGED
@@ -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:
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 1.
|
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-
|
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:
|
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: *
|
66
|
+
version_requirements: *id003
|
53
67
|
- !ruby/object:Gem::Dependency
|
54
68
|
name: mocha
|
55
69
|
prerelease: false
|
56
|
-
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: *
|
82
|
+
version_requirements: *id004
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: ruby-debug
|
71
85
|
prerelease: false
|
72
|
-
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: *
|
96
|
+
version_requirements: *id005
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: equivalent-xml
|
85
99
|
prerelease: false
|
86
|
-
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: *
|
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.
|
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
|