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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +448 -0
- data/Rakefile +5 -0
- data/ess.gemspec +21 -0
- data/lib/ess/dtd.rb +491 -0
- data/lib/ess/element.rb +266 -0
- data/lib/ess/ess.rb +8 -0
- data/lib/ess/examples.rb +432 -0
- data/lib/ess/helpers.rb +22 -0
- data/lib/ess/maker.rb +24 -0
- data/lib/ess/postprocessing.rb +69 -0
- data/lib/ess/pusher.rb +63 -0
- data/lib/ess/validation.rb +797 -0
- data/lib/ess/version.rb +3 -0
- data/lib/ess.rb +13 -0
- data/spec/ess/element_spec.rb +796 -0
- data/spec/ess/full_spec.rb +47 -0
- data/spec/ess/maker_spec.rb +30 -0
- data/spec/spec_helper.rb +5 -0
- metadata +88 -0
data/lib/ess/element.rb
ADDED
@@ -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
|
+
|