tilia-vobject 4.0.0.pre.alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (193) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rubocop.yml +32 -0
  4. data/.simplecov +4 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.sabre.md +626 -0
  7. data/CONTRIBUTING.md +25 -0
  8. data/Gemfile +17 -0
  9. data/Gemfile.lock +68 -0
  10. data/LICENSE +27 -0
  11. data/LICENSE.sabre +27 -0
  12. data/README.md +63 -0
  13. data/Rakefile +17 -0
  14. data/bin/vobject +7 -0
  15. data/lib/tilia/v_object/birthday_calendar_generator.rb +142 -0
  16. data/lib/tilia/v_object/cli.rb +582 -0
  17. data/lib/tilia/v_object/component/available.rb +107 -0
  18. data/lib/tilia/v_object/component/v_alarm.rb +114 -0
  19. data/lib/tilia/v_object/component/v_availability.rb +128 -0
  20. data/lib/tilia/v_object/component/v_calendar.rb +468 -0
  21. data/lib/tilia/v_object/component/v_card.rb +457 -0
  22. data/lib/tilia/v_object/component/v_event.rb +127 -0
  23. data/lib/tilia/v_object/component/v_free_busy.rb +81 -0
  24. data/lib/tilia/v_object/component/v_journal.rb +75 -0
  25. data/lib/tilia/v_object/component/v_time_zone.rb +51 -0
  26. data/lib/tilia/v_object/component/v_todo.rb +147 -0
  27. data/lib/tilia/v_object/component.rb +591 -0
  28. data/lib/tilia/v_object/date_time_parser.rb +486 -0
  29. data/lib/tilia/v_object/document.rb +218 -0
  30. data/lib/tilia/v_object/element_list.rb +18 -0
  31. data/lib/tilia/v_object/eof_exception.rb +8 -0
  32. data/lib/tilia/v_object/free_busy_data.rb +149 -0
  33. data/lib/tilia/v_object/free_busy_generator.rb +465 -0
  34. data/lib/tilia/v_object/i_tip/broker.rb +909 -0
  35. data/lib/tilia/v_object/i_tip/i_tip_exception.rb +9 -0
  36. data/lib/tilia/v_object/i_tip/message.rb +109 -0
  37. data/lib/tilia/v_object/i_tip/same_organizer_for_all_components_exception.rb +13 -0
  38. data/lib/tilia/v_object/i_tip.rb +10 -0
  39. data/lib/tilia/v_object/node.rb +192 -0
  40. data/lib/tilia/v_object/parameter.rb +327 -0
  41. data/lib/tilia/v_object/parse_exception.rb +7 -0
  42. data/lib/tilia/v_object/parser/json.rb +149 -0
  43. data/lib/tilia/v_object/parser/mime_dir.rb +543 -0
  44. data/lib/tilia/v_object/parser/parser.rb +61 -0
  45. data/lib/tilia/v_object/parser/xml/element/key_value.rb +60 -0
  46. data/lib/tilia/v_object/parser/xml/element.rb +11 -0
  47. data/lib/tilia/v_object/parser/xml.rb +322 -0
  48. data/lib/tilia/v_object/parser.rb +10 -0
  49. data/lib/tilia/v_object/property/binary.rb +96 -0
  50. data/lib/tilia/v_object/property/boolean.rb +57 -0
  51. data/lib/tilia/v_object/property/flat_text.rb +52 -0
  52. data/lib/tilia/v_object/property/float_value.rb +107 -0
  53. data/lib/tilia/v_object/property/i_calendar/cal_address.rb +49 -0
  54. data/lib/tilia/v_object/property/i_calendar/date.rb +15 -0
  55. data/lib/tilia/v_object/property/i_calendar/date_time.rb +330 -0
  56. data/lib/tilia/v_object/property/i_calendar/duration.rb +65 -0
  57. data/lib/tilia/v_object/property/i_calendar/period.rb +124 -0
  58. data/lib/tilia/v_object/property/i_calendar/recur.rb +173 -0
  59. data/lib/tilia/v_object/property/i_calendar.rb +14 -0
  60. data/lib/tilia/v_object/property/integer_value.rb +60 -0
  61. data/lib/tilia/v_object/property/text.rb +352 -0
  62. data/lib/tilia/v_object/property/time.rb +85 -0
  63. data/lib/tilia/v_object/property/unknown.rb +30 -0
  64. data/lib/tilia/v_object/property/uri.rb +78 -0
  65. data/lib/tilia/v_object/property/utc_offset.rb +56 -0
  66. data/lib/tilia/v_object/property/v_card/date.rb +31 -0
  67. data/lib/tilia/v_object/property/v_card/date_and_or_time.rb +343 -0
  68. data/lib/tilia/v_object/property/v_card/date_time.rb +22 -0
  69. data/lib/tilia/v_object/property/v_card/language_tag.rb +41 -0
  70. data/lib/tilia/v_object/property/v_card/time_stamp.rb +74 -0
  71. data/lib/tilia/v_object/property/v_card.rb +13 -0
  72. data/lib/tilia/v_object/property.rb +532 -0
  73. data/lib/tilia/v_object/reader.rb +73 -0
  74. data/lib/tilia/v_object/recur/event_iterator.rb +417 -0
  75. data/lib/tilia/v_object/recur/no_instances_exception.rb +11 -0
  76. data/lib/tilia/v_object/recur/r_date_iterator.rb +138 -0
  77. data/lib/tilia/v_object/recur/r_rule_iterator.rb +717 -0
  78. data/lib/tilia/v_object/recur.rb +10 -0
  79. data/lib/tilia/v_object/settings.rb +32 -0
  80. data/lib/tilia/v_object/splitter/i_calendar.rb +95 -0
  81. data/lib/tilia/v_object/splitter/splitter_interface.rb +31 -0
  82. data/lib/tilia/v_object/splitter/v_card.rb +56 -0
  83. data/lib/tilia/v_object/splitter.rb +9 -0
  84. data/lib/tilia/v_object/string_util.rb +58 -0
  85. data/lib/tilia/v_object/time_zone_data/exchange_zones.rb +96 -0
  86. data/lib/tilia/v_object/time_zone_data/lotus_zones.rb +104 -0
  87. data/lib/tilia/v_object/time_zone_data/php_zones.rb +49 -0
  88. data/lib/tilia/v_object/time_zone_data/windows_zones.rb +121 -0
  89. data/lib/tilia/v_object/time_zone_data.rb +10 -0
  90. data/lib/tilia/v_object/time_zone_util.rb +213 -0
  91. data/lib/tilia/v_object/uuid_util.rb +51 -0
  92. data/lib/tilia/v_object/v_card_converter.rb +354 -0
  93. data/lib/tilia/v_object/version.rb +9 -0
  94. data/lib/tilia/v_object/writer.rb +56 -0
  95. data/lib/tilia/v_object.rb +45 -0
  96. data/lib/tilia/vobject.rb +1 -0
  97. data/resources/schema/xcal.rng +1192 -0
  98. data/resources/schema/xcard.rng +388 -0
  99. data/test/test_helper.rb +56 -0
  100. data/test/v_object/attach_issue_test.rb +19 -0
  101. data/test/v_object/birthday_calendar_generator_test.rb +463 -0
  102. data/test/v_object/cli_mock.rb +19 -0
  103. data/test/v_object/cli_test.rb +460 -0
  104. data/test/v_object/component/available_test.rb +59 -0
  105. data/test/v_object/component/v_alarm_test.rb +160 -0
  106. data/test/v_object/component/v_availability_test.rb +388 -0
  107. data/test/v_object/component/v_calendar_test.rb +646 -0
  108. data/test/v_object/component/v_card_test.rb +258 -0
  109. data/test/v_object/component/v_event_test.rb +85 -0
  110. data/test/v_object/component/v_free_busy_test.rb +59 -0
  111. data/test/v_object/component/v_journal_test.rb +85 -0
  112. data/test/v_object/component/v_time_zone_test.rb +47 -0
  113. data/test/v_object/component/v_todo_test.rb +172 -0
  114. data/test/v_object/component_test.rb +419 -0
  115. data/test/v_object/date_time_parser_test.rb +526 -0
  116. data/test/v_object/document_test.rb +71 -0
  117. data/test/v_object/element_list_test.rb +27 -0
  118. data/test/v_object/em_client_test.rb +53 -0
  119. data/test/v_object/empty_parameter_test.rb +65 -0
  120. data/test/v_object/empty_value_issue_test.rb +25 -0
  121. data/test/v_object/fake_component.rb +21 -0
  122. data/test/v_object/free_busy_data_test.rb +285 -0
  123. data/test/v_object/free_busy_generator_test.rb +637 -0
  124. data/test/v_object/google_colon_escaping_test.rb +27 -0
  125. data/test/v_object/i_calendar/attach_parse_test.rb +24 -0
  126. data/test/v_object/i_tip/broker_attendee_reply_test.rb +1042 -0
  127. data/test/v_object/i_tip/broker_delete_event_test.rb +175 -0
  128. data/test/v_object/i_tip/broker_new_event_test.rb +440 -0
  129. data/test/v_object/i_tip/broker_process_message_test.rb +153 -0
  130. data/test/v_object/i_tip/broker_process_reply_test.rb +402 -0
  131. data/test/v_object/i_tip/broker_tester.rb +71 -0
  132. data/test/v_object/i_tip/broker_update_event_test.rb +763 -0
  133. data/test/v_object/i_tip/evolution_test.rb +2644 -0
  134. data/test/v_object/i_tip/message_test.rb +25 -0
  135. data/test/v_object/issue153.vcf +352 -0
  136. data/test/v_object/issue153_test.rb +12 -0
  137. data/test/v_object/issue26_test.rb +25 -0
  138. data/test/v_object/issue36_work_around_test.rb +37 -0
  139. data/test/v_object/issue40_test.rb +26 -0
  140. data/test/v_object/issue64.vcf +351 -0
  141. data/test/v_object/issue64_test.rb +17 -0
  142. data/test/v_object/issue96_test.rb +22 -0
  143. data/test/v_object/issue_undefined_index_test.rb +24 -0
  144. data/test/v_object/j_cal_test.rb +150 -0
  145. data/test/v_object/j_card_test.rb +192 -0
  146. data/test/v_object/line_folding_issue_test.rb +19 -0
  147. data/test/v_object/mock_document.rb +6 -0
  148. data/test/v_object/parameter_test.rb +109 -0
  149. data/test/v_object/parser/json_test.rb +370 -0
  150. data/test/v_object/parser/mime_dir_test.rb +14 -0
  151. data/test/v_object/parser/quoted_printable_test.rb +78 -0
  152. data/test/v_object/parser/xml_test.rb +2563 -0
  153. data/test/v_object/property/binary_test.rb +12 -0
  154. data/test/v_object/property/boolean_test.rb +18 -0
  155. data/test/v_object/property/compound_test.rb +43 -0
  156. data/test/v_object/property/float_test.rb +20 -0
  157. data/test/v_object/property/i_calendar/cal_address_test.rb +26 -0
  158. data/test/v_object/property/i_calendar/date_time_test.rb +303 -0
  159. data/test/v_object/property/i_calendar/duration_test.rb +14 -0
  160. data/test/v_object/property/i_calendar/recur_test.rb +39 -0
  161. data/test/v_object/property/text_test.rb +81 -0
  162. data/test/v_object/property/v_card/date_and_or_time_test.rb +205 -0
  163. data/test/v_object/property/v_card/language_tag_test.rb +35 -0
  164. data/test/v_object/property_test.rb +338 -0
  165. data/test/v_object/reader_test.rb +403 -0
  166. data/test/v_object/recur/event_iterator/by_month_in_daily_test.rb +52 -0
  167. data/test/v_object/recur/event_iterator/by_set_pos_hang_test.rb +55 -0
  168. data/test/v_object/recur/event_iterator/expand_floating_times_test.rb +109 -0
  169. data/test/v_object/recur/event_iterator/fifth_tuesday_problem_test.rb +45 -0
  170. data/test/v_object/recur/event_iterator/incorrect_expand_test.rb +53 -0
  171. data/test/v_object/recur/event_iterator/infinite_loop_problem_test.rb +75 -0
  172. data/test/v_object/recur/event_iterator/issue48_test.rb +43 -0
  173. data/test/v_object/recur/event_iterator/issue50_test.rb +123 -0
  174. data/test/v_object/recur/event_iterator/main_test.rb +1222 -0
  175. data/test/v_object/recur/event_iterator/missing_overridden_test.rb +55 -0
  176. data/test/v_object/recur/event_iterator/no_instances_test.rb +32 -0
  177. data/test/v_object/recur/event_iterator/override_first_event_test.rb +106 -0
  178. data/test/v_object/recur/r_date_iterator_test.rb +44 -0
  179. data/test/v_object/recur/r_rule_iterator_test.rb +608 -0
  180. data/test/v_object/recurrence_iterator/UntilRespectsTimezoneTest.ics +39 -0
  181. data/test/v_object/slash_r_test.rb +15 -0
  182. data/test/v_object/splitter/i_calendar_test.rb +299 -0
  183. data/test/v_object/splitter/v_card_test.rb +173 -0
  184. data/test/v_object/string_util_test.rb +37 -0
  185. data/test/v_object/test_case.rb +42 -0
  186. data/test/v_object/time_zone_util_test.rb +271 -0
  187. data/test/v_object/uuid_util_test.rb +18 -0
  188. data/test/v_object/v_card21_test.rb +43 -0
  189. data/test/v_object/v_card_converter_test.rb +419 -0
  190. data/test/v_object/version_test.rb +15 -0
  191. data/test/v_object/writer_test.rb +33 -0
  192. data/tilia-vobject.gemspec +17 -0
  193. metadata +308 -0
@@ -0,0 +1,591 @@
1
+ module Tilia
2
+ module VObject
3
+ # Component.
4
+ #
5
+ # A component represents a group of properties, such as VCALENDAR, VEVENT, or
6
+ # VCARD.
7
+ class Component < Node
8
+ require 'tilia/v_object/component/available'
9
+ require 'tilia/v_object/component/v_alarm'
10
+ require 'tilia/v_object/component/v_availability'
11
+ require 'tilia/v_object/component/v_event'
12
+ require 'tilia/v_object/component/v_free_busy'
13
+ require 'tilia/v_object/component/v_journal'
14
+ require 'tilia/v_object/component/v_time_zone'
15
+ require 'tilia/v_object/component/v_todo'
16
+ require 'tilia/v_object/document'
17
+ require 'tilia/v_object/component/v_calendar'
18
+ require 'tilia/v_object/component/v_card'
19
+
20
+ # Component name.
21
+ #
22
+ # This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
23
+ #
24
+ # @var string
25
+ attr_accessor :name
26
+
27
+ # A list of properties and/or sub-components.
28
+ #
29
+ # @var array
30
+ # RUBY: attr_accessor :children
31
+
32
+ # Creates a new component.
33
+ #
34
+ # You can specify the children either in key=>value syntax, in which case
35
+ # properties will automatically be created, or you can just pass a list of
36
+ # Component and Property object.
37
+ #
38
+ # By default, a set of sensible values will be added to the component. For
39
+ # an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
40
+ # ensure that this does not happen, set defaults to false.
41
+ #
42
+ # @param Document root
43
+ # @param string name such as VCALENDAR, VEVENT.
44
+ # @param array children
45
+ # @param bool defaults
46
+ #
47
+ # @return void
48
+ def initialize(root, name, children = {}, defaults = true)
49
+ @children = {}
50
+ @name = name.to_s.upcase
51
+ @root = root
52
+
53
+ # Try to handle some PHP quirks
54
+ if children.is_a?(Array)
55
+ new_children = {}
56
+ children.each_with_index { |c, i| new_children[i] = c }
57
+ children = new_children
58
+ end
59
+
60
+ if defaults
61
+ # This is a terribly convoluted way to do this, but this ensures
62
+ # that the order of properties as they are specified in both
63
+ # defaults and the childrens list, are inserted in the object in a
64
+ # natural way.
65
+ list = self.defaults
66
+ nodes = []
67
+
68
+ children.each do |key, value|
69
+ if value.is_a?(Node)
70
+ list.delete value.name if list.key?(value.name)
71
+ nodes << value
72
+ else
73
+ list[key] = value
74
+ end
75
+ end
76
+
77
+ list.each do |key, value|
78
+ add(key, value)
79
+ end
80
+
81
+ nodes.each do |node|
82
+ add(node)
83
+ end
84
+ else
85
+ children.each do |k, child|
86
+ if child.is_a?(Node)
87
+ # Component or Property
88
+ add(child)
89
+ else
90
+ # Property key=>value
91
+ add(k, child)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # Adds a new property or component, and returns the new item.
98
+ #
99
+ # This method has 3 possible signatures:
100
+ #
101
+ # add(Component comp) // Adds a new component
102
+ # add(Property prop) // Adds a new property
103
+ # add(name, value, array parameters = []) // Adds a new property
104
+ # add(name, array children = []) // Adds a new component
105
+ # by name.
106
+ #
107
+ # @return Node
108
+ def add(a1, a2 = nil, a3 = nil)
109
+ if a1.is_a?(Node)
110
+ unless a2.nil?
111
+ fail ArgumentError, 'The second argument must not be specified, when passing a VObject Node'
112
+ end
113
+ a1.parent = self
114
+ new_node = a1
115
+ elsif a1.is_a?(String)
116
+ new_node = @root.create(a1, a2, a3)
117
+ new_node.parent = self
118
+ else
119
+ fail ArgumentError, 'The first argument must either be a Node or a string'
120
+ end
121
+
122
+ name = new_node.name
123
+ if @children.key?(name)
124
+ @children[name] << new_node
125
+ else
126
+ @children[name] = [new_node]
127
+ end
128
+
129
+ new_node
130
+ end
131
+
132
+ # This method removes a component or property from this component.
133
+ #
134
+ # You can either specify the item by name (like DTSTART), in which case
135
+ # all properties/components with that name will be removed, or you can
136
+ # pass an instance of a property or component, in which case only that
137
+ # exact item will be removed.
138
+ #
139
+ # @param string|Property|Component item
140
+ # @return void
141
+ def remove(item)
142
+ if item.is_a?(String)
143
+ # If there's no dot in the name, it's an exact property name and
144
+ # we can just wipe out all those properties.
145
+ #
146
+ unless item.index('.')
147
+ @children.delete(item.upcase)
148
+ return nil
149
+ end
150
+
151
+ # If there was a dot, we need to ask select to help us out and
152
+ # then we just call remove recursively.
153
+ select(item).each do |child|
154
+ remove(child)
155
+ end
156
+ else
157
+ select(item.name).each_with_index do |child, _k|
158
+ if child == item
159
+ @children[item.name].delete(item)
160
+ return nil
161
+ end
162
+ end
163
+ end
164
+
165
+ fail ArgumentError, 'The item you passed to remove was not a child of this component'
166
+ end
167
+
168
+ # Returns a flat list of all the properties and components in this
169
+ # component.
170
+ #
171
+ # @return array
172
+ def children
173
+ result = []
174
+ @children.each do |_, child_group|
175
+ result.concat(child_group)
176
+ end
177
+
178
+ result
179
+ end
180
+
181
+ # This method only returns a list of sub-components. Properties are
182
+ # ignored.
183
+ #
184
+ # @return array
185
+ def components
186
+ result = []
187
+
188
+ @children.each do |_key, child_group|
189
+ child_group.each do |child|
190
+ result << child if child.is_a?(Component)
191
+ end
192
+ end
193
+
194
+ result
195
+ end
196
+
197
+ # Returns an array with elements that match the specified name.
198
+ #
199
+ # This function is also aware of MIME-Directory groups (as they appear in
200
+ # vcards). This means that if a property is grouped as "HOME.EMAIL", it
201
+ # will also be returned when searching for just "EMAIL". If you want to
202
+ # search for a property in a specific group, you can select on the entire
203
+ # string ("HOME.EMAIL"). If you want to search on a specific property that
204
+ # has not been assigned a group, specify ".EMAIL".
205
+ #
206
+ # @param string name
207
+ # @return array
208
+ def select(name)
209
+ group = nil
210
+ name = name.upcase
211
+
212
+ (group, name) = name.split('.', 2) if name.index('.')
213
+
214
+ name = nil if name.blank?
215
+
216
+ if name
217
+ result = @children.key?(name) ? @children[name] : []
218
+
219
+ if group.nil?
220
+ return result
221
+ else
222
+ # If we have a group filter as well, we need to narrow it down
223
+ # more.
224
+ return result.select do |child|
225
+ child.is_a?(Property) && (child.group || '').upcase == group
226
+ end
227
+ end
228
+ end
229
+
230
+ # If we got to this point, it means there was no 'name' specified for
231
+ # searching, implying that this is a group-only search.
232
+ result = []
233
+ @children.each do |_, child_group|
234
+ child_group.each do |child|
235
+ if child.is_a?(Property) && (child.group || '').upcase == group
236
+ result << child
237
+ end
238
+ end
239
+ end
240
+
241
+ result
242
+ end
243
+
244
+ # Turns the object back into a serialized blob.
245
+ #
246
+ # @return string
247
+ def serialize
248
+ str = "BEGIN:#{@name}\r\n"
249
+
250
+ # Gives a component a 'score' for sorting purposes.
251
+ #
252
+ # This is solely used by the childrenSort method.
253
+ #
254
+ # A higher score means the item will be lower in the list.
255
+ # To avoid score collisions, each "score category" has a reasonable
256
+ # space to accomodate elements. The key is added to the score to
257
+ # preserve the original relative order of elements.
258
+ #
259
+ # @param int key
260
+ # @param array array
261
+ #
262
+ # @return int
263
+ sort_score = lambda do |key, array|
264
+ key = array.index(key)
265
+ if array[key].is_a?(Component)
266
+ # We want to encode VTIMEZONE first, this is a personal
267
+ # preference.
268
+ if array[key].name == 'VTIMEZONE'
269
+ score = 300_000_000
270
+ return score + key
271
+ else
272
+ score = 400_000_000
273
+ return score + key
274
+ end
275
+ else
276
+ # Properties get encoded first
277
+ # VCARD version 4.0 wants the VERSION property to appear first
278
+ if array[key].is_a?(Property)
279
+ if array[key].name == 'VERSION'
280
+ score = 100_000_000
281
+ return score + key
282
+ else
283
+ # All other properties
284
+ score = 200_000_000
285
+ return score + key
286
+ end
287
+ end
288
+ end
289
+ end
290
+
291
+ tmp = children.sort do |a, b|
292
+ s_a = sort_score.call(a, children)
293
+ s_b = sort_score.call(b, children)
294
+ s_a - s_b
295
+ end
296
+
297
+ tmp.each do |child|
298
+ str += child.serialize
299
+ end
300
+ str += "END:#{@name}\r\n"
301
+
302
+ str
303
+ end
304
+
305
+ # This method returns an array, with the representation as it should be
306
+ # encoded in JSON. This is used to create jCard or jCal documents.
307
+ #
308
+ # @return array
309
+ def json_serialize
310
+ components = []
311
+ properties = []
312
+
313
+ @children.each do |_, child_group|
314
+ child_group.each do |child|
315
+ if child.is_a?(Component)
316
+ components << child.json_serialize
317
+ else
318
+ properties << child.json_serialize
319
+ end
320
+ end
321
+ end
322
+
323
+ [
324
+ @name.downcase,
325
+ properties,
326
+ components
327
+ ]
328
+ end
329
+
330
+ # This method serializes the data into XML. This is used to create xCard or
331
+ # xCal documents.
332
+ #
333
+ # @param Xml\Writer writer XML writer.
334
+ #
335
+ # @return void
336
+ def xml_serialize(writer)
337
+ components = []
338
+ properties = []
339
+
340
+ @children.each do |_, child_group|
341
+ child_group.each do |child|
342
+ if child.is_a?(Component)
343
+ components << child
344
+ else
345
+ properties << child
346
+ end
347
+ end
348
+ end
349
+
350
+ writer.start_element(@name.downcase)
351
+
352
+ if properties.any?
353
+ writer.start_element('properties')
354
+ properties.each do |property|
355
+ property.xml_serialize(writer)
356
+ end
357
+ writer.end_element
358
+ end
359
+
360
+ if components.any?
361
+ writer.start_element('components')
362
+ components.each do |component|
363
+ component.xml_serialize(writer)
364
+ end
365
+ writer.end_element
366
+ end
367
+
368
+ writer.end_element
369
+ end
370
+
371
+ protected
372
+
373
+ # This method should return a list of default property values.
374
+ #
375
+ # @return array
376
+ def defaults
377
+ []
378
+ end
379
+
380
+ public
381
+
382
+ # Using 'get' you will either get a property or component.
383
+ #
384
+ # If there were no child-elements found with the specified name,
385
+ # null is returned.
386
+ #
387
+ # To use this, this may look something like this:
388
+ #
389
+ # event = calendar->VEVENT
390
+ #
391
+ # @param string name
392
+ #
393
+ # @return Property
394
+ def [](name)
395
+ return super(name) if name.is_a?(Fixnum)
396
+
397
+ if name == 'children'
398
+ fail 'Starting sabre/vobject 4.0 the children property is now protected. You should use the children method instead'
399
+ end
400
+
401
+ matches = select(name)
402
+
403
+ if matches.empty?
404
+ return nil
405
+ else
406
+ first_match = matches.first
407
+ # @var first_match Property
408
+ first_match.iterator = ElementList.new(matches.to_a)
409
+ return first_match
410
+ end
411
+ end
412
+
413
+ # This method checks if a sub-element with the specified name exists.
414
+ #
415
+ # @param string name
416
+ #
417
+ # @return bool
418
+ def key?(name)
419
+ matches = select(name)
420
+ matches.any?
421
+ end
422
+
423
+ # Using the setter method you can add properties or subcomponents.
424
+ #
425
+ # You can either pass a Component, Property
426
+ # object, or a string to automatically create a Property.
427
+ #
428
+ # If the item already exists, it will be removed. If you want to add
429
+ # a new item with the same name, always use the add method.
430
+ #
431
+ # @param string name
432
+ # @param mixed value
433
+ #
434
+ # @return void
435
+ def []=(name, value)
436
+ return super(name, value) if name.is_a?(Fixnum)
437
+ name = name.upcase
438
+
439
+ remove(name)
440
+
441
+ if value.is_a?(Component) || value.is_a?(Property)
442
+ add(value)
443
+ else
444
+ add(name, value)
445
+ end
446
+ end
447
+
448
+ # Removes all properties and components within this component with the
449
+ # specified name.
450
+ #
451
+ # @param string name
452
+ #
453
+ # @return void
454
+ def delete(name)
455
+ return super(name) if name.is_a?(Fixnum)
456
+ remove(name)
457
+ end
458
+
459
+ # This method is automatically called when the object is cloned.
460
+ # Specifically, this will ensure all child elements are also cloned.
461
+ #
462
+ # @return void
463
+ def initialize_copy(_other)
464
+ new_children = {}
465
+ @children.each do |child_name, child_group|
466
+ new_children[child_name] = []
467
+ child_group.each do |child|
468
+ cloned_child = child.clone
469
+ cloned_child.parent = self
470
+ # cloned_child.root = @root
471
+ new_children[child_name] << cloned_child
472
+ end
473
+ end
474
+ @children = new_children
475
+ end
476
+
477
+ # A simple list of validation rules.
478
+ #
479
+ # This is simply a list of properties, and how many times they either
480
+ # must or must not appear.
481
+ #
482
+ # Possible values per property:
483
+ # * 0 - Must not appear.
484
+ # * 1 - Must appear exactly once.
485
+ # * + - Must appear at least once.
486
+ # * * - Can appear any number of times.
487
+ # * ? - May appear, but not more than once.
488
+ #
489
+ # It is also possible to specify defaults and severity levels for
490
+ # violating the rule.
491
+ #
492
+ # See the VEVENT implementation for getValidationRules for a more complex
493
+ # example.
494
+ #
495
+ # @var array
496
+ def validation_rules
497
+ []
498
+ end
499
+
500
+ # Validates the node for correctness.
501
+ #
502
+ # The following options are supported:
503
+ # Node::REPAIR - May attempt to automatically repair the problem.
504
+ # Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
505
+ # Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
506
+ #
507
+ # This method returns an array with detected problems.
508
+ # Every element has the following properties:
509
+ #
510
+ # * level - problem level.
511
+ # * message - A human-readable string describing the issue.
512
+ # * node - A reference to the problematic node.
513
+ #
514
+ # The level means:
515
+ # 1 - The issue was repaired (only happens if REPAIR was turned on).
516
+ # 2 - A warning.
517
+ # 3 - An error.
518
+ #
519
+ # @param int options
520
+ #
521
+ # @return array
522
+ def validate(options = 0)
523
+ rules = validation_rules
524
+ defaults = self.defaults
525
+
526
+ property_counters = {}
527
+
528
+ messages = []
529
+
530
+ children.each do |child|
531
+ name = child.name.upcase
532
+
533
+ if !property_counters.key?(name)
534
+ property_counters[name] = 1
535
+ else
536
+ property_counters[name] += 1
537
+ end
538
+ messages.concat child.validate(options)
539
+ end
540
+
541
+ rules.each do |prop_name, rule|
542
+ case rule.to_s
543
+ when '0'
544
+ if property_counters.key?(prop_name)
545
+ messages << {
546
+ 'level' => 3,
547
+ 'message' => "#{prop_name} MUST NOT appear in a #{@name} component",
548
+ 'node' => self
549
+ }
550
+ end
551
+ when '1'
552
+ if !property_counters.key?(prop_name) || property_counters[prop_name] != 1
553
+ repaired = false
554
+ add(prop_name, defaults[prop_name]) if options & self.class::REPAIR > 0 && defaults.key?(prop_name)
555
+
556
+ messages << {
557
+ 'level' => repaired ? 1 : 3,
558
+ 'message' => "#{prop_name} MUST appear exactly once in a #{@name} component",
559
+ 'node' => self
560
+ }
561
+ end
562
+ when '+'
563
+ if !property_counters.key?(prop_name) || property_counters[prop_name] < 1
564
+ messages << {
565
+ 'level' => 3,
566
+ 'message' => "#{prop_name} MUST appear at least once in a #{@name} component",
567
+ 'node' => self
568
+ }
569
+ end
570
+ when '*'
571
+ when '?'
572
+ if property_counters.key?(prop_name) && property_counters[prop_name] > 1
573
+ messages << {
574
+ 'level' => 3,
575
+ 'message' => "#{prop_name} MUST NOT appear more than once in a #{@name} component",
576
+ 'node' => self
577
+ }
578
+ end
579
+ end
580
+ end
581
+
582
+ messages
583
+ end
584
+
585
+ # TODO: document
586
+ def to_s
587
+ serialize
588
+ end
589
+ end
590
+ end
591
+ end