atom 0.1 → 0.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.
Files changed (3) hide show
  1. data/lib/atom.rb +2 -0
  2. data/lib/xmlmapping.rb +128 -92
  3. metadata +2 -2
@@ -133,6 +133,8 @@ module Atom
133
133
  has_many :category, :name => 'category', :type => Category
134
134
  has_one :content, :type => Content
135
135
  has_one :source, :type => Source
136
+
137
+ has_many :extended_elements, :name => :any, :namespace => :any, :type => :raw
136
138
  end
137
139
 
138
140
  class Feed
@@ -26,131 +26,167 @@ require 'rexml/document'
26
26
  module XMLMapping
27
27
  def self.included(mod)
28
28
  mod.extend(ClassMethods)
29
- mod.instance_variable_set("@attributes", {})
30
- mod.instance_variable_set("@elements", {})
31
- mod.instance_variable_set("@text_attribute", nil)
29
+
30
+ mod.instance_variable_set("@raw_mappings", {})
31
+ mod.instance_variable_set("@mappings", { :element => {}, :attribute => {}, :text => {}, :namespace => nil})
32
32
  end
33
33
 
34
+
34
35
  def initialize(input)
35
- if input.respond_to? :to_str
36
- root = REXML::Document.new(input).root
37
- elsif input.respond_to?(:node_type) && input.node_type == :document
38
- root = input.root
39
- elsif input.respond_to?(:node_type) && input.node_type == :element
40
- root = input
41
- else
42
- raise "Invalid input: #{input}"
43
- end
36
+ root = parse(input)
44
37
 
45
- namespace = self.class.default_namespace
46
- attributes = self.class.attributes
47
- elements = self.class.elements
48
- text_attribute = self.class.text_attribute
38
+ mappings = self.class.mappings
39
+ raw_mappings = self.class.raw_mappings
49
40
 
50
- if !text_attribute.nil?
51
- name = text_attribute[0]
52
- options = text_attribute[1]
53
- value = root.text
41
+ # initialize :many attributes
42
+ raw_mappings.values.select { |mapping| mapping[:cardinality] == :many }.each { |m|
43
+ instance_variable_set("@#{m[:attribute]}", [])
44
+ }
54
45
 
55
- if options.has_key? :transform
56
- value = options[:transform].call(value)
57
- end
46
+ root.each_element { |e|
47
+ process(e, mappings[:element])
48
+ }
49
+
50
+ root.attributes.each_attribute { |a|
51
+ process(a, mappings[:attribute])
52
+ }
58
53
 
54
+ mappings[:text].values.each { |mapping|
55
+ name = mapping[:attribute]
56
+ value = extract_value(root, mapping)
59
57
  instance_variable_set("@#{name}", value)
58
+ }
59
+
60
+ end
61
+
62
+
63
+ private
64
+ def process(e, mappings)
65
+ mapping = find_mapping(mappings, e.namespace, e.name)
66
+
67
+ if !mapping.nil?
68
+ value = extract_value(e, mapping)
69
+
70
+ attribute = mapping[:attribute]
71
+ previous = instance_variable_get("@#{attribute}")
72
+ case mapping[:cardinality]
73
+ when :one
74
+ raise "Found more than one #{e.name}" if !previous.nil?
75
+ instance_variable_set("@#{attribute}", value)
76
+ when :many
77
+ previous << value
78
+ end
79
+ end
80
+ end
81
+
82
+ def find_mapping(mappings, namespace, name)
83
+ mappings.values_at([namespace, name], [namespace, :any], [:any, :any] ).compact.first
84
+ end
85
+
86
+ def extract_value(node, mapping)
87
+ if mapping.has_key? :type
88
+ type = mapping[:type]
89
+ if type == :raw
90
+ value = node
91
+ else
92
+ value = mapping[:type].new(node)
93
+ end
94
+ elsif node.node_type == :element
95
+ value = node.texts.map { |t| t.value }.to_s
96
+ elsif node.node_type == :attribute
97
+ value = node.value
60
98
  else
61
- # initialize :many attributes
62
- elements.select { |k, v|
63
- v[:cardinality] == :many
64
- }.each { |k, v|
65
- instance_variable_set("@#{v[:attribute]}", [])
66
- }
67
-
68
- root.each_element { |e|
69
- if e.namespace == namespace && elements.has_key?(e.name.to_sym)
70
- options = elements[e.name.to_sym]
71
-
72
- if options.has_key? :type
73
- value = options[:type].new(e)
74
- else
75
- value = e.text
76
- end
77
-
78
- if options.has_key? :transform
79
- value = options[:transform].call(value)
80
- end
81
-
82
- attribute = options[:attribute]
83
-
84
- existing = instance_variable_get("@#{attribute}")
85
- case options[:cardinality]
86
- when :one
87
- raise "Found more than one #{e.name}" if !existing.nil?
88
- instance_variable_set("@#{attribute}", value)
89
- when :many
90
- existing << value
91
- end
92
- end
93
- }
99
+ raise "Unexpected node: #{node.inspect}"
94
100
  end
95
101
 
96
- root.attributes.each_attribute { |a|
97
- if a.namespace == namespace && attributes.has_key?(a.name.to_sym)
98
- options = attributes[a.name.to_sym]
102
+ if mapping.has_key? :transform
103
+ value = mapping[:transform].call(value)
104
+ end
99
105
 
100
- value = a.value
101
- if options.has_key? :transform
102
- value = options[:transform].call(value)
103
- end
106
+ value
107
+ end
104
108
 
105
- instance_variable_set("@#{a.name}", value)
106
- end
107
- }
109
+ def parse(input)
110
+ if input.respond_to? :to_str
111
+ root = REXML::Document.new(input).root
112
+ elsif input.respond_to?(:node_type) && input.node_type == :document
113
+ root = input.root
114
+ elsif input.respond_to?(:node_type) && input.node_type == :element
115
+ root = input
116
+ else
117
+ raise "Invalid input: #{input}"
118
+ end
119
+
120
+ root
108
121
  end
109
122
 
110
123
  module ClassMethods
111
- attr :attributes
112
- attr :elements
113
- attr :text_attribute
114
- attr :default_namespace
124
+ def raw_mappings
125
+ @raw_mappings || superclass.raw_mappings
126
+ end
127
+
128
+ def mappings
129
+ @mappings || superclass.mappings
130
+ end
115
131
 
116
132
  def namespace(namespace)
117
- @default_namespace = namespace
133
+ initialize_vars
134
+ @mappings[:namespace] = namespace
118
135
  end
119
136
 
120
137
  def has_one(attribute, options = {})
121
- attr attribute
122
138
  options[:cardinality] = :one
123
- options[:attribute] = attribute
124
-
125
- name = attribute
126
- if options.has_key? :name
127
- name = options[:name].to_sym
128
- end
129
-
130
- @elements[name] = options
139
+ add(attribute, :element, options)
131
140
  end
132
141
 
133
142
  def has_many(attribute, options = {})
134
- attr attribute
135
143
  options[:cardinality] = :many
136
- options[:attribute] = attribute
137
-
138
- name = attribute
139
- if options.has_key? :name
140
- name = options[:name].to_sym
141
- end
142
-
143
- @elements[name] = options
144
+ add(attribute, :element, options)
144
145
  end
145
146
 
146
147
  def has_attribute(attribute, options = {})
147
- attr attribute
148
- @attributes[attribute] = options
148
+ add(attribute, :attribute, options)
149
149
  end
150
150
 
151
151
  def text(attribute, options = {})
152
+ options[:namespace] = :any
153
+ add(attribute, :text, options)
154
+ end
155
+
156
+ private
157
+ def add(attribute, xml_type, mapping)
152
158
  attr attribute
153
- @text_attribute = [attribute, options]
159
+
160
+ initialize_vars
161
+
162
+ mapping[:namespace] ||= mappings[:namespace]
163
+ mapping[:cardinality] ||= :one
164
+ mapping[:name] ||= attribute.to_s
165
+ mapping[:attribute] = attribute
166
+
167
+ qualified_name = [mapping[:namespace], mapping[:name]]
168
+ mappings = @mappings[xml_type][qualified_name] = mapping
169
+
170
+ @raw_mappings[attribute] = mapping
171
+ end
172
+
173
+ def initialize_vars
174
+ @mappings ||= deep_clone(superclass.mappings)
175
+ @raw_mappings ||= deep_clone(superclass.raw_mappings)
176
+ end
177
+
178
+ def deep_clone(obj)
179
+ case obj
180
+ when Hash
181
+ obj.entries.inject({}) { |hash, entry|
182
+ hash[entry[0]] = deep_clone(entry[1])
183
+ hash
184
+ }
185
+ when Array
186
+ obj.map { |v| deep_clone[v] }
187
+ else
188
+ obj
189
+ end
154
190
  end
155
191
  end
156
192
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: atom
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.1"
7
- date: 2006-04-06 00:00:00 -07:00
6
+ version: "0.2"
7
+ date: 2006-04-08 00:00:00 -07:00
8
8
  summary: Ruby library for working with the Atom syndication format
9
9
  require_paths:
10
10
  - lib