objectify-xml 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +5 -0
- data/lib/objectify_xml.rb +14 -1
- data/lib/objectify_xml/atom.rb +32 -0
- data/lib/objectify_xml/document_parser.rb +15 -0
- data/lib/objectify_xml/dsl.rb +50 -13
- data/lib/objectify_xml/element_parser.rb +5 -0
- metadata +1 -1
data/History.txt
CHANGED
data/lib/objectify_xml.rb
CHANGED
@@ -7,8 +7,10 @@ require File.join(File.dirname(__FILE__), 'objectify_xml/document_parser')
|
|
7
7
|
require File.join(File.dirname(__FILE__), 'objectify_xml/element_parser')
|
8
8
|
|
9
9
|
module Objectify
|
10
|
+
# Base class inherited by the DocumentParser and ElementParser. Not intended
|
11
|
+
# for independent use.
|
10
12
|
class Xml
|
11
|
-
VERSION = '0.2.
|
13
|
+
VERSION = '0.2.2'
|
12
14
|
|
13
15
|
# When child nodes are created, they are given the name of the node
|
14
16
|
# that created them which is available here.
|
@@ -24,6 +26,10 @@ module Objectify
|
|
24
26
|
target.extend Dsl
|
25
27
|
end
|
26
28
|
|
29
|
+
# Returns the first Nokogiri::XML::Element if any in the document.
|
30
|
+
#
|
31
|
+
# The xml attribute may be a string, a File object or a Nokogiri::XML
|
32
|
+
# object.
|
27
33
|
def self.first_element(xml)
|
28
34
|
return if xml.nil?
|
29
35
|
if xml.is_a?(String) or xml.is_a?(File)
|
@@ -45,6 +51,8 @@ module Objectify
|
|
45
51
|
primary_xml_element(xml) if xml
|
46
52
|
end
|
47
53
|
|
54
|
+
# A short but informative indication of data type and which and how many
|
55
|
+
# elements are present.
|
48
56
|
def inspect
|
49
57
|
begin
|
50
58
|
attrs = (attributes || {}).map do |k,v|
|
@@ -62,6 +70,10 @@ module Objectify
|
|
62
70
|
end
|
63
71
|
end
|
64
72
|
|
73
|
+
# require 'pp'
|
74
|
+
#
|
75
|
+
# A more detailed recursive dump of the data in and associated to the
|
76
|
+
# object.
|
65
77
|
def pretty_print(q)
|
66
78
|
begin
|
67
79
|
q.object_group(self) do
|
@@ -82,6 +94,7 @@ module Objectify
|
|
82
94
|
|
83
95
|
protected
|
84
96
|
|
97
|
+
# Attempts to recognize and typecast xml element text values.
|
85
98
|
def xml_text_to_value(value)
|
86
99
|
value = value.strip
|
87
100
|
case value
|
data/lib/objectify_xml/atom.rb
CHANGED
@@ -1,25 +1,43 @@
|
|
1
1
|
module Objectify
|
2
|
+
# Given as an example of usage, but fully functional. Must be required
|
3
|
+
# separately: require 'objectify/atom'
|
2
4
|
module Atom
|
5
|
+
# attributes :rel, :type, :href
|
3
6
|
class Link < ElementParser
|
4
7
|
attributes :rel, :type, :href
|
5
8
|
end
|
6
9
|
|
7
10
|
|
11
|
+
# attributes :scheme, :term
|
8
12
|
class Category < ElementParser
|
9
13
|
attributes :scheme, :term
|
10
14
|
end
|
11
15
|
|
12
16
|
|
17
|
+
# attributes :type, :xml_lang, :xml_base, :src, :inner_html
|
13
18
|
class Content < ElementParser
|
14
19
|
attributes :type, :xml_lang, :xml_base, :src, :inner_html
|
15
20
|
end
|
16
21
|
|
17
22
|
|
23
|
+
# attributes :version, :uri, :inner_text
|
18
24
|
class Generator < ElementParser
|
19
25
|
attributes :version, :uri, :inner_text
|
20
26
|
end
|
21
27
|
|
22
28
|
|
29
|
+
# The root object of an Atom feed.
|
30
|
+
#
|
31
|
+
# attributes :id,
|
32
|
+
# :published,
|
33
|
+
# :updated,
|
34
|
+
# :title,
|
35
|
+
# :subtitle,
|
36
|
+
# :rights,
|
37
|
+
# :icon
|
38
|
+
# has_many :links, :Link, 'link'
|
39
|
+
# has_many :entries, :Entry, 'entry'
|
40
|
+
# has_one :generator, Generator, 'generator'
|
23
41
|
class Feed < DocumentParser
|
24
42
|
attributes :id,
|
25
43
|
:published,
|
@@ -34,6 +52,18 @@ module Objectify
|
|
34
52
|
end
|
35
53
|
|
36
54
|
|
55
|
+
# The feed has a collection of entries.
|
56
|
+
#
|
57
|
+
# attributes :id,
|
58
|
+
# :published,
|
59
|
+
# :updated,
|
60
|
+
# :title,
|
61
|
+
# :summary
|
62
|
+
# has_many :links, Link, 'link'
|
63
|
+
# has_one :category, Category, 'category'
|
64
|
+
# has_many :contents, Content, 'content'
|
65
|
+
# has_one :author, :Author, 'author'
|
66
|
+
# has_many :contributors, :Contributor, 'contributor'
|
37
67
|
class Entry < DocumentParser
|
38
68
|
attributes :id,
|
39
69
|
:published,
|
@@ -48,11 +78,13 @@ module Objectify
|
|
48
78
|
end
|
49
79
|
|
50
80
|
|
81
|
+
# attributes :name, :uri, :email
|
51
82
|
class Author < DocumentParser
|
52
83
|
attributes :name, :uri, :email
|
53
84
|
end
|
54
85
|
|
55
86
|
|
87
|
+
# attributes :name
|
56
88
|
class Contributor < DocumentParser
|
57
89
|
attributes :name
|
58
90
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module Objectify
|
2
|
+
#
|
2
3
|
class DocumentParser < Xml
|
3
4
|
# The entry point to the parser, normally called by initialize after the
|
4
5
|
# initialization is complete.
|
@@ -8,24 +9,32 @@ module Objectify
|
|
8
9
|
|
9
10
|
private
|
10
11
|
|
12
|
+
# Returns the given element's name as it would appear in the xml document,
|
13
|
+
# including namespace if present.
|
11
14
|
def qualified_name(x)
|
12
15
|
qn = x.name
|
13
16
|
qn = "#{ x.namespace }:#{ x.name }" if x.namespace
|
14
17
|
qn
|
15
18
|
end
|
16
19
|
|
20
|
+
# Returns the type of the element's association if one is defined, otherwise nil.
|
17
21
|
def attribute_type(x)
|
18
22
|
self.class.attribute_type qualified_name(x)
|
19
23
|
end
|
20
24
|
|
25
|
+
# Returns boolean to indicate if the children of the element should be
|
26
|
+
# treated as part of the current object's data.
|
21
27
|
def flatten?(x)
|
22
28
|
self.class.flatten?(qualified_name(x))
|
23
29
|
end
|
24
30
|
|
31
|
+
# Returns boolean to indicate if the given element is defined as being one
|
32
|
+
# member of a collection.
|
25
33
|
def collection?(x)
|
26
34
|
self.class.collection?(qualified_name(x))
|
27
35
|
end
|
28
36
|
|
37
|
+
# Returns boolean to indicate if the given element's namespace is supported.
|
29
38
|
def namespace?(x)
|
30
39
|
if x.namespace
|
31
40
|
self.class.namespace?(x.namespace)
|
@@ -34,10 +43,13 @@ module Objectify
|
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
46
|
+
# Returns the attribute name representing the given element. If the element
|
47
|
+
# is not defined, returns nil.
|
37
48
|
def attribute(x)
|
38
49
|
self.class.find_attribute(qualified_name(x), x.namespace, x.name)
|
39
50
|
end
|
40
51
|
|
52
|
+
# Parses the given xml element and all of its siblings.
|
41
53
|
def parse_xml(xml)
|
42
54
|
while xml
|
43
55
|
read_xml_element(xml)
|
@@ -45,6 +57,8 @@ module Objectify
|
|
45
57
|
end
|
46
58
|
end
|
47
59
|
|
60
|
+
# Processes the given element and stores the resultant value if it is
|
61
|
+
# a defined attribute or association.
|
48
62
|
def read_xml_element(x)
|
49
63
|
return if x.is_a? Nokogiri::XML::Text
|
50
64
|
return unless namespace?(x)
|
@@ -57,6 +71,7 @@ module Objectify
|
|
57
71
|
end
|
58
72
|
end
|
59
73
|
|
74
|
+
# Stores the value of the given attribute or association if it is defined.
|
60
75
|
def set_attribute(x)
|
61
76
|
if attr_name = attribute(x)
|
62
77
|
if collection?(x)
|
data/lib/objectify_xml/dsl.rb
CHANGED
@@ -5,6 +5,8 @@ module Objectify
|
|
5
5
|
target.init
|
6
6
|
end
|
7
7
|
|
8
|
+
# Initializes required metadata variables when the module is included in a
|
9
|
+
# class or a class is inherited.
|
8
10
|
def init
|
9
11
|
parent = ancestors[1]
|
10
12
|
unless /^Objectify::(Xml|ElementParser|DocumentParser)$/ =~ parent.name
|
@@ -24,21 +26,37 @@ module Objectify
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
29
|
+
# Define a typed association attribute. The type should be derived from
|
30
|
+
# either DocumentParser or ElementParser.
|
31
|
+
#
|
32
|
+
# If the type has not yet been defined at the point that the declaration
|
33
|
+
# is made, a String or Symbol may be used and will be looked up from
|
34
|
+
# the scope that the declaration is made in.
|
27
35
|
def has_one(name, type, qualified_name)
|
28
36
|
set_type(qualified_name, type)
|
29
37
|
attribute name, qualified_name
|
30
38
|
end
|
31
39
|
|
40
|
+
# Define an attribute as a collection of typed associations. The type
|
41
|
+
# should be derived from either DocumentParser or ElementParser.
|
42
|
+
#
|
43
|
+
# If the type has not yet been defined at the point that the declaration
|
44
|
+
# is made, a String or Symbol may be used and will be instantiated from
|
45
|
+
# the scope that the declaration is made in.
|
32
46
|
def has_many(name, type, qualified_name)
|
33
47
|
set_type(qualified_name, type)
|
34
48
|
attribute name, qualified_name, true
|
35
49
|
end
|
36
50
|
|
51
|
+
# Define a list of simple attributes.
|
37
52
|
def attributes(*names)
|
38
53
|
names.each { |n| attribute n }
|
39
54
|
@attributes + @qualified_attributes.keys
|
40
55
|
end
|
41
56
|
|
57
|
+
# Define a simple attribute or collection of simple attributes. This
|
58
|
+
# method can be used if the desired attribute name is different from the
|
59
|
+
# xml element name by specifying a qualified name.
|
42
60
|
def attribute(name, qualified_name = nil, collection = false)
|
43
61
|
name = name.to_s.underscore
|
44
62
|
@qualified_attributes[qualified_name] = name if qualified_name
|
@@ -55,6 +73,8 @@ module Objectify
|
|
55
73
|
name
|
56
74
|
end
|
57
75
|
|
76
|
+
# Used internally to find the name of an attribute based on the name of
|
77
|
+
# the xml element.
|
58
78
|
def find_attribute(qualified_name, namespace, name)
|
59
79
|
if qname = @qualified_attributes[qualified_name]
|
60
80
|
return qname
|
@@ -76,18 +96,23 @@ module Objectify
|
|
76
96
|
end
|
77
97
|
end
|
78
98
|
|
99
|
+
# Specify that an element should be recursed into while still associating
|
100
|
+
# its child elements with the current object.
|
79
101
|
def flatten(qualified_name)
|
80
102
|
@flatten << qualified_name.to_s
|
81
103
|
end
|
82
104
|
|
105
|
+
# Used internally to determine whether an element has been flattened.
|
83
106
|
def flatten?(qualified_name)
|
84
107
|
@flatten.include? qualified_name
|
85
108
|
end
|
86
109
|
|
110
|
+
# Used internally to determine whether a namespace is supported.
|
87
111
|
def namespace?(namespace)
|
88
112
|
@namespaces.keys.include? namespace
|
89
113
|
end
|
90
114
|
|
115
|
+
# Specify a list of supported namespaces.
|
91
116
|
def namespaces(*namespaces)
|
92
117
|
namespaces.each do |ns|
|
93
118
|
namespace ns
|
@@ -95,38 +120,45 @@ module Objectify
|
|
95
120
|
@namespaces
|
96
121
|
end
|
97
122
|
|
123
|
+
# Specify the url of the default namespace.
|
98
124
|
def default_namespace(url)
|
99
125
|
@namespaces[''] = url
|
100
126
|
end
|
101
127
|
|
128
|
+
# Specify a namespace with its url.
|
102
129
|
def namespace(name = nil, url = nil)
|
103
130
|
@namespaces[name.to_s] = url
|
104
131
|
end
|
105
132
|
|
133
|
+
# Return the url (if any) associated with a namespace.
|
106
134
|
def find_namespace(name = '')
|
107
135
|
@namespaces[name]
|
108
136
|
end
|
109
137
|
|
138
|
+
# Returns the attribute type associated to an element name if any.
|
139
|
+
#
|
140
|
+
# Searches up the defining object's namespace to the root namespace for
|
141
|
+
# the definition of the constant.
|
110
142
|
def attribute_type(qualified_name)
|
111
143
|
type = @types[qualified_name]
|
112
144
|
if type and not type.is_a? Class
|
113
145
|
type_name = type.to_s
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
sections
|
119
|
-
|
146
|
+
type = nil
|
147
|
+
# Try to search the current object's namespace explicitly
|
148
|
+
sections = self.name.split(/::/)
|
149
|
+
while sections.length > 1
|
150
|
+
sections.pop
|
151
|
+
begin
|
152
|
+
sections.push(type_name)
|
153
|
+
type = sections.join('::').constantize
|
154
|
+
break
|
155
|
+
rescue
|
120
156
|
sections.pop
|
121
|
-
begin
|
122
|
-
sections.push(type_name)
|
123
|
-
type = sections.join('::').constantize
|
124
|
-
break
|
125
|
-
rescue
|
126
|
-
sections.pop
|
127
|
-
end
|
128
157
|
end
|
129
158
|
end
|
159
|
+
if type.nil?
|
160
|
+
type = type_name.constantize rescue nil
|
161
|
+
end
|
130
162
|
if type.nil?
|
131
163
|
raise "Unable to instantiate the constant '#{ type_name }'."
|
132
164
|
end
|
@@ -135,14 +167,19 @@ module Objectify
|
|
135
167
|
type
|
136
168
|
end
|
137
169
|
|
170
|
+
# Sets the associated type of an attribute. Generally used only internally
|
171
|
+
# via has_one or has_many.
|
138
172
|
def set_type(qualified_name, type)
|
139
173
|
@types[qualified_name] = type
|
140
174
|
end
|
141
175
|
|
176
|
+
# Used internally to determine whether an element is represented by a
|
177
|
+
# collection.
|
142
178
|
def collection?(qualified_name)
|
143
179
|
@collections.include?(qualified_name)
|
144
180
|
end
|
145
181
|
|
182
|
+
# Return the object's metadata for testing and debugging purposes.
|
146
183
|
def metadata
|
147
184
|
{ :attributes => @attributes,
|
148
185
|
:qualified_attributes => @qualified_attributes,
|
@@ -1,5 +1,10 @@
|
|
1
1
|
module Objectify
|
2
2
|
class ElementParser < Xml
|
3
|
+
# Stores the xml element's attribute values, inner_text and inner_html if
|
4
|
+
# they are defined.
|
5
|
+
#
|
6
|
+
# Attributes can be defined either with the attribute dsl method or by
|
7
|
+
# defining a name= method in the object.
|
3
8
|
def primary_xml_element(xml)
|
4
9
|
xml.attributes.keys.each do |name|
|
5
10
|
method = "#{ name }="
|