ess 0.9.1

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.
@@ -0,0 +1,266 @@
1
+ require 'builder'
2
+ require 'ess/helpers'
3
+ require 'ess/pusher'
4
+
5
+ module ESS
6
+ class Element
7
+ include ESS::Helpers
8
+
9
+ attr_reader :dtd
10
+
11
+ def initialize name, dtd
12
+ @name, @dtd = name, dtd
13
+ end
14
+
15
+ def name!
16
+ @name
17
+ end
18
+
19
+ def text! text=nil
20
+ return @text ||= "" if text.nil?
21
+ @text = do_text_postprocessing_of text
22
+ end
23
+
24
+ def inspect
25
+ "#<#{self.class}:#{object_id} text=\"#{@text}\">"
26
+ end
27
+
28
+ def validate
29
+ run_tag_validators
30
+ check_attributes
31
+ check_child_tags
32
+ return nil # if no errors found, i.e. no exceptions have been raised
33
+ end
34
+
35
+ def valid?
36
+ begin
37
+ validate
38
+ rescue
39
+ return false
40
+ end
41
+ return true
42
+ end
43
+
44
+ def to_xml! xml=nil
45
+ convert_to_string = true if xml.nil?
46
+ xml = Builder::XmlMarkup.new if xml.nil?
47
+ if @name == :ess
48
+ xml.instruct! :xml, :encoding => "UTF-8"
49
+ xml.declare! :DOCTYPE, :ess, :PUBLIC, "-//ESS//DTD", "http://essfeed.org/history/0.9/index.dtd"
50
+ end
51
+ xml.tag! @name, attributes do |p|
52
+ if !@text.nil?
53
+ if @dtd[:cdata]
54
+ p.cdata! @text
55
+ else
56
+ p.text! @text
57
+ end
58
+ end
59
+ child_tags.values.each { |tag_list| tag_list.each { |tag| tag.to_xml!(p) } }
60
+ end
61
+ xml.target! if convert_to_string
62
+ end
63
+
64
+ def push_to_aggregators options={}
65
+ raise RuntimeError, "only ESS root element can be pushed to aggregators" if @name != :ess
66
+ options[:data] = self.to_xml!
67
+ Pusher::push_to_aggregators options
68
+ end
69
+
70
+ def to_s
71
+ to_xml!
72
+ end
73
+
74
+ def method_missing m, *args, &block
75
+ if method_name_is_tag_name? m
76
+ return assign_tag(m, args, &block)
77
+ elsif method_name_is_tag_adder_method? m
78
+ return extend_tag_list(m, args, &block)
79
+ elsif method_name_is_tag_list_method? m
80
+ return child_tags[m[0..-6].to_sym] ||= []
81
+ elsif method_name_is_attr_accessor_method? m
82
+ return assign_attribute(m[0..-6].to_sym, args, &block)
83
+ end
84
+ super(m, *args, &block)
85
+ end
86
+
87
+ private
88
+
89
+ def method_name_is_tag_name? method_name
90
+ available_tags.include? method_name
91
+ end
92
+
93
+ def method_name_is_tag_adder_method? method_name
94
+ method_name.to_s.start_with?("add_") && available_tags.include?(method_name[4..-1].to_sym)
95
+ end
96
+
97
+ def method_name_is_tag_list_method? method_name
98
+ method_name.to_s.end_with?("_list") && available_tags.include?(method_name[0..-6].to_sym)
99
+ end
100
+
101
+ def method_name_is_attr_accessor_method? method_name
102
+ method_name.to_s.end_with?("_attr") && available_attributes.include?(method_name[0..-6].to_sym)
103
+ end
104
+
105
+ def assign_attribute attr_name, args, &block
106
+ if args.any?
107
+ attr_value = args[0].to_s
108
+ check_attribute_value attr_name, attr_value
109
+ attributes[attr_name] = attr_value
110
+ else
111
+ attributes[attr_name] || ""
112
+ end
113
+ end
114
+
115
+ def check_attribute_value attr_name, attr_value
116
+ attr_desc = @dtd[:attributes][attr_name]
117
+ if attr_value && attr_desc.has_key?(:valid_values)
118
+ valid_values = attr_desc[:valid_values]
119
+ if !valid_values.include? attr_value
120
+ unless attr_value.split(",").map { |value| valid_values.include? value }.all?
121
+ raise DTD::InvalidValueError, "Invalid attribute value \"#{attr_value}\" for \"#{attr_name}\" attribute"
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def assign_tag m, args, &block
128
+ arg_hash = {}
129
+ args.each { |arg| arg_hash = arg if arg.class == Hash }
130
+ tag_list = child_tags[m] ||= [Element.new(m, @dtd[:tags][m][:dtd])]
131
+ if args.length > 0 && args[0].class != Hash
132
+ if @dtd[:tags][m].include? :valid_values
133
+ unless @dtd[:tags][m][:valid_values].include?(args[0])
134
+ raise InvalidValueError, "\"#{args[0]}\" is not a valid value for the #{m} tag"
135
+ end
136
+ end
137
+ tag_list[0].text!(args[0])
138
+ end
139
+ arg_hash.each_pair { |key, value| tag_list[0].send([key, "_attr"].join("").to_sym, value) }
140
+ block.call tag_list[0] if block
141
+ run_post_processing tag_list[0]
142
+ return tag_list[0]
143
+ end
144
+
145
+ def extend_tag_list m, args, &block
146
+ arg_hash = {}
147
+ args.each { |arg| arg_hash = arg if arg.class == Hash }
148
+ tag_name = m[4..-1].to_sym
149
+ new_tag = Element.new(tag_name, @dtd[:tags][tag_name][:dtd])
150
+ new_tag.text!(args[0]) if args.any? && args[0].class == String
151
+ arg_hash.each_pair { |key, value| new_tag.send([key, "_attr"].join("").to_sym, value) }
152
+ block.call new_tag if block
153
+ (child_tags[tag_name] ||= []).push new_tag
154
+ run_post_processing new_tag
155
+ return new_tag
156
+ end
157
+
158
+ def run_post_processing tag
159
+ if @dtd[:tags][tag.name!].keys.include? :postprocessing
160
+ @dtd[:tags][tag.name!][:postprocessing].each { |processor| processor.process(self, tag) }
161
+ end
162
+ end
163
+
164
+ def child_tags
165
+ @child_tags ||= {}
166
+ end
167
+
168
+ def attributes
169
+ @attributes ||= {}
170
+ end
171
+
172
+ def do_text_postprocessing_of text
173
+ text = text.to_s if text.class != String
174
+ if @dtd.include? :postprocessing_text
175
+ @dtd[:postprocessing_text].each do |processor|
176
+ text = processor.process text
177
+ end
178
+ end
179
+ text
180
+ end
181
+
182
+ def available_tags
183
+ return [] if @dtd[:tags].nil?
184
+ @dtd[:tags].keys
185
+ end
186
+
187
+ def available_attributes
188
+ return [] if @dtd[:attributes].nil?
189
+ @dtd[:attributes].keys
190
+ end
191
+
192
+
193
+ # Validation helpers:
194
+
195
+ def run_tag_validators
196
+ if @dtd.include? :validation
197
+ @dtd[:validation].each { |validator| validator.validate self }
198
+ end
199
+ end
200
+
201
+ def check_attributes
202
+ if @dtd.include? :attributes
203
+ @dtd[:attributes].each_pair do |attr_name, attr_desc|
204
+ check_mandatory_attribute(attr_name) if attr_desc[:mandatory]
205
+ check_attribute_cardinality(attr_name, attr_desc[:max_occurs])
206
+ end
207
+ end
208
+ end
209
+
210
+ def check_mandatory_attribute attr_name
211
+ attr_value = attributes[attr_name]
212
+ if attr_value.nil? || attr_value == ""
213
+ raise Validation::ValidationError, "missing mandatory attribute #{attr_name} from <#{@name}> element"
214
+ end
215
+ end
216
+
217
+ def check_attribute_cardinality attr_name, max_occurs
218
+ unless max_occurs.nil?
219
+ unless max_occurs == :inf
220
+ attr_value = attributes[attr_name]
221
+ unless attr_value.nil? || attr_value.split(",").length <= max_occurs.to_i
222
+ raise Validation::ValidationError, "too many values for attribute #{attr_name}: #{attr_value}"
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ def check_child_tags
229
+ if @dtd.include? :tags
230
+ @dtd[:tags].each_pair do |tag_name, tag_desc|
231
+ check_mandatory_child_tag(tag_name) if tag_desc[:mandatory]
232
+ check_child_tag_cardinality(tag_name, tag_desc[:max_occurs])
233
+ end
234
+ run_validate_for_each_child_tag
235
+ end
236
+ end
237
+
238
+ def check_mandatory_child_tag tag_name
239
+ unless child_tags.include?(tag_name) && child_tags[tag_name].length > 0
240
+ raise Validation::ValidationError, "<#{tag_name}> is a mandatory tag in <#{@name}>"
241
+ end
242
+ end
243
+
244
+ def check_child_tag_cardinality tag_name, max_occurs
245
+ if max_occurs != :inf
246
+ if child_tags.include?(tag_name)
247
+ if child_tags[tag_name].length > 0
248
+ if child_tags[tag_name].length > max_occurs.to_i
249
+ raise Validation::ValidationError, "< #{tag_name} > can occur maximally #{max_occurs.to_i} times in < #{@name} >"
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ def run_validate_for_each_child_tag
257
+ child_tags.values.each do |a_tags_list|
258
+ a_tags_list.each do |one_tag|
259
+ one_tag.validate
260
+ end
261
+ end
262
+ end
263
+
264
+ end
265
+ end
266
+
data/lib/ess/ess.rb ADDED
@@ -0,0 +1,8 @@
1
+ module ESS
2
+ class ESS < Element
3
+ def initialize
4
+ super :ess, DTD::ESS
5
+ end
6
+ end
7
+ end
8
+