ess 0.9.1

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