atom 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
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