Class: Tilia::VObject::Component
- Defined in:
- lib/tilia/v_object/component.rb,
lib/tilia/v_object/component/v_card.rb,
lib/tilia/v_object/component/v_todo.rb,
lib/tilia/v_object/component/v_alarm.rb,
lib/tilia/v_object/component/v_event.rb,
lib/tilia/v_object/component/available.rb,
lib/tilia/v_object/component/v_journal.rb,
lib/tilia/v_object/component/v_calendar.rb,
lib/tilia/v_object/component/v_free_busy.rb,
lib/tilia/v_object/component/v_time_zone.rb,
lib/tilia/v_object/component/v_availability.rb
Overview
Component.
A component represents a group of properties, such as VCALENDAR, VEVENT, or VCARD.
Direct Known Subclasses
Available, VAlarm, VAvailability, VEvent, VFreeBusy, VJournal, VTimeZone, VTodo, Document
Defined Under Namespace
Classes: Available, VAlarm, VAvailability, VCalendar, VCard, VEvent, VFreeBusy, VJournal, VTimeZone, VTodo
Constant Summary
Constants inherited from Node
Node::PROFILE_CALDAV, Node::PROFILE_CARDDAV, Node::REPAIR
Instance Attribute Summary collapse
-
#name ⇒ String
Component name.
Attributes inherited from Node
Instance Method Summary collapse
-
#[](name) ⇒ Property
Using ‘get’ you will either get a property or component.
-
#[]=(name, value) ⇒ void
Using the setter method you can add properties or subcomponents.
-
#add(*arguments) ⇒ Node
Adds a new property or component, and returns the new item.
-
#children ⇒ array
Returns a flat list of all the properties and components in this component.
-
#components ⇒ array
This method only returns a list of sub-components.
-
#delete(name) ⇒ void
Removes all properties and components within this component with the specified name.
-
#destroy ⇒ void
Call this method on a document if you’re done using it.
-
#initialize(root, name, children = {}, defaults = true) ⇒ void
constructor
Creates a new component.
-
#initialize_copy(_original) ⇒ void
This method is automatically called when the object is cloned.
-
#json_serialize ⇒ array
This method returns an array, with the representation as it should be encoded in JSON.
-
#key?(name) ⇒ Boolean
This method checks if a sub-element with the specified name exists.
-
#remove(item) ⇒ void
This method removes a component or property from this component.
-
#select(name) ⇒ array
Returns an array with elements that match the specified name.
-
#serialize ⇒ String
Turns the object back into a serialized blob.
-
#to_s ⇒ Object
TODO: document.
-
#validate(options = 0) ⇒ array
Validates the node for correctness.
-
#validation_rules ⇒ array
A simple list of validation rules.
-
#xml_serialize(writer) ⇒ void
This method serializes the data into XML.
Methods inherited from Node
Constructor Details
#initialize(root, name, children = {}, defaults = true) ⇒ void
Creates a new component.
You can specify the children either in key=>value syntax, in which case properties will automatically be created, or you can just pass a list of Component and Property object.
By default, a set of sensible values will be added to the component. For an iCalendar object, this may be something like CALSCALE:GREGORIAN. To ensure that this does not happen, set defaults to false.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/tilia/v_object/component.rb', line 48 def initialize(root, name, children = {}, defaults = true) @children = {} @name = name.to_s.upcase @root = root # Try to handle some PHP quirks if children.is_a?(Array) new_children = {} children.each_with_index { |c, i| new_children[i] = c } children = new_children end if defaults # This is a terribly convoluted way to do this, but this ensures # that the order of properties as they are specified in both # defaults and the childrens list, are inserted in the object in a # natural way. list = self.defaults nodes = [] children.each do |key, value| if value.is_a?(Node) list.delete value.name if list.key?(value.name) nodes << value else list[key] = value end end list.each do |key, value| add(key, value) end nodes.each do |node| add(node) end else children.each do |k, child| if child.is_a?(Node) # Component or Property add(child) else # Property key=>value add(k, child) end end end end |
Instance Attribute Details
#name ⇒ String
Component name.
This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
25 26 27 |
# File 'lib/tilia/v_object/component.rb', line 25 def name @name end |
Instance Method Details
#[](name) ⇒ Property
Using ‘get’ you will either get a property or component.
If there were no child-elements found with the specified name, null is returned.
To use this, this may look something like this:
event = calendar->VEVENT
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 |
# File 'lib/tilia/v_object/component.rb', line 393 def [](name) return super(name) if name.is_a?(Fixnum) if name == 'children' fail 'Starting sabre/vobject 4.0 the children property is now protected. You should use the children method instead' end matches = select(name) if matches.empty? return nil else first_match = matches.first # @return [first_match] Property first_match.iterator = ElementList.new(matches.to_a) return first_match end end |
#[]=(name, value) ⇒ void
This method returns an undefined value.
Using the setter method you can add properties or subcomponents.
You can either pass a Component, Property object, or a string to automatically create a Property.
If the item already exists, it will be removed. If you want to add a new item with the same name, always use the add method.
434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/tilia/v_object/component.rb', line 434 def []=(name, value) return super(name, value) if name.is_a?(Fixnum) name = name.upcase remove(name) if value.is_a?(Component) || value.is_a?(Property) add(value) else add(name, value) end end |
#add(*arguments) ⇒ Node
Adds a new property or component, and returns the new item.
This method has 3 possible signatures:
add(Component comp) // Adds a new component add(Property prop) // Adds a new property add(name, value, array parameters = []) // Adds a new property add(name, array children = []) // Adds a new component by name.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/tilia/v_object/component.rb', line 108 def add(*arguments) if arguments[0].is_a?(Node) if arguments[1] fail ArgumentError, 'The second argument must not be specified, when passing a VObject Node' end arguments[0].parent = self new_node = arguments[0] elsif arguments[0].is_a?(String) new_node = @root.create(*arguments) else fail ArgumentError, 'The first argument must either be a Node or a string' end name = new_node.name if @children.key?(name) @children[name] << new_node else @children[name] = [new_node] end new_node end |
#children ⇒ array
Returns a flat list of all the properties and components in this component.
171 172 173 174 175 176 177 178 |
# File 'lib/tilia/v_object/component.rb', line 171 def children result = [] @children.each do |_, child_group| result.concat(child_group) end result end |
#components ⇒ array
This method only returns a list of sub-components. Properties are ignored.
184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/tilia/v_object/component.rb', line 184 def components result = [] @children.each do |_key, child_group| child_group.each do |child| result << child if child.is_a?(Component) end end result end |
#delete(name) ⇒ void
This method returns an undefined value.
Removes all properties and components within this component with the specified name.
453 454 455 456 |
# File 'lib/tilia/v_object/component.rb', line 453 def delete(name) return super(name) if name.is_a?(Fixnum) remove(name) end |
#destroy ⇒ void
This method returns an undefined value.
Call this method on a document if you’re done using it.
It’s intended to remove all circular references, so PHP can easily clean it up.
590 591 592 593 594 595 596 597 598 |
# File 'lib/tilia/v_object/component.rb', line 590 def destroy super @children.each do |_child_name, child_group| child_group.each(&:destroy) end @children = {} end |
#initialize_copy(_original) ⇒ void
This method returns an undefined value.
This method is automatically called when the object is cloned. Specifically, this will ensure all child elements are also cloned.
462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/tilia/v_object/component.rb', line 462 def initialize_copy(_original) new_children = {} @children.each do |child_name, child_group| new_children[child_name] = [] child_group.each do |child| cloned_child = child.clone cloned_child.parent = self # cloned_child.root = @root new_children[child_name] << cloned_child end end @children = new_children end |
#json_serialize ⇒ array
This method returns an array, with the representation as it should be encoded in JSON. This is used to create jCard or jCal documents.
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/tilia/v_object/component.rb', line 308 def json_serialize components = [] properties = [] @children.each do |_, child_group| child_group.each do |child| if child.is_a?(Component) components << child.json_serialize else properties << child.json_serialize end end end [ @name.downcase, properties, components ] end |
#key?(name) ⇒ Boolean
This method checks if a sub-element with the specified name exists.
417 418 419 420 |
# File 'lib/tilia/v_object/component.rb', line 417 def key?(name) matches = select(name) matches.any? end |
#remove(item) ⇒ void
This method returns an undefined value.
This method removes a component or property from this component.
You can either specify the item by name (like DTSTART), in which case all properties/components with that name will be removed, or you can pass an instance of a property or component, in which case only that exact item will be removed.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/tilia/v_object/component.rb', line 140 def remove(item) if item.is_a?(String) # If there's no dot in the name, it's an exact property name and # we can just wipe out all those properties. # unless item.index('.') @children.delete(item.upcase) return nil end # If there was a dot, we need to ask select to help us out and # then we just call remove recursively. select(item).each do |child| remove(child) end else select(item.name).each_with_index do |child, _k| if child == item @children[item.name].delete(item) return nil end end end fail ArgumentError, 'The item you passed to remove was not a child of this component' end |
#select(name) ⇒ array
Returns an array with elements that match the specified name.
This function is also aware of MIME-Directory groups (as they appear in vcards). This means that if a property is grouped as “HOME.EMAIL”, it will also be returned when searching for just “EMAIL”. If you want to search for a property in a specific group, you can select on the entire string (“HOME.EMAIL”). If you want to search on a specific property that has not been assigned a group, specify “.EMAIL”.
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/tilia/v_object/component.rb', line 207 def select(name) group = nil name = name.upcase (group, name) = name.split('.', 2) if name.index('.') name = nil if name.blank? if name result = @children.key?(name) ? @children[name] : [] if group.nil? return result else # If we have a group filter as well, we need to narrow it down # more. return result.select do |child| child.is_a?(Property) && (child.group || '').upcase == group end end end # If we got to this point, it means there was no 'name' specified for # searching, implying that this is a group-only search. result = [] @children.each do |_, child_group| child_group.each do |child| if child.is_a?(Property) && (child.group || '').upcase == group result << child end end end result end |
#serialize ⇒ String
Turns the object back into a serialized blob.
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/tilia/v_object/component.rb', line 246 def serialize str = "BEGIN:#{@name}\r\n" # Gives a component a 'score' for sorting purposes. # # This is solely used by the childrenSort method. # # A higher score means the item will be lower in the list. # To avoid score collisions, each "score category" has a reasonable # space to accomodate elements. The key is added to the score to # preserve the original relative order of elements. # # @param [Fixnum] key # @param [array] array # # @return [Fixnum] sort_score = lambda do |key, array| key = array.index(key) if array[key].is_a?(Component) # We want to encode VTIMEZONE first, this is a personal # preference. if array[key].name == 'VTIMEZONE' score = 300_000_000 return score + key else score = 400_000_000 return score + key end else # Properties get encoded first # VCARD version 4.0 wants the VERSION property to appear first if array[key].is_a?(Property) if array[key].name == 'VERSION' score = 100_000_000 return score + key else # All other properties score = 200_000_000 return score + key end end end end tmp = children.sort do |a, b| s_a = sort_score.call(a, children) s_b = sort_score.call(b, children) s_a - s_b end tmp.each do |child| str += child.serialize.encode('utf-8', 'utf-8') end str += "END:#{@name}\r\n" str end |
#to_s ⇒ Object
TODO: document
601 602 603 |
# File 'lib/tilia/v_object/component.rb', line 601 def to_s serialize end |
#validate(options = 0) ⇒ array
Validates the node for correctness.
The following options are supported:
Node::REPAIR - May attempt to automatically repair the problem.
Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
This method returns an array with detected problems. Every element has the following properties:
* level - problem level.
* message - A human-readable string describing the issue.
* node - A reference to the problematic node.
The level means:
1 - The issue was repaired (only happens if REPAIR was turned on).
2 - A warning.
3 - An error.
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 |
# File 'lib/tilia/v_object/component.rb', line 521 def validate( = 0) rules = validation_rules defaults = self.defaults property_counters = {} = [] children.each do |child| name = child.name.upcase if !property_counters.key?(name) property_counters[name] = 1 else property_counters[name] += 1 end .concat child.validate() end rules.each do |prop_name, rule| case rule.to_s when '0' if property_counters.key?(prop_name) << { 'level' => 3, 'message' => "#{prop_name} MUST NOT appear in a #{@name} component", 'node' => self } end when '1' if !property_counters.key?(prop_name) || property_counters[prop_name] != 1 repaired = false add(prop_name, defaults[prop_name]) if & REPAIR > 0 && defaults.key?(prop_name) << { 'level' => repaired ? 1 : 3, 'message' => "#{prop_name} MUST appear exactly once in a #{@name} component", 'node' => self } end when '+' if !property_counters.key?(prop_name) || property_counters[prop_name] < 1 << { 'level' => 3, 'message' => "#{prop_name} MUST appear at least once in a #{@name} component", 'node' => self } end when '*' when '?' if property_counters.key?(prop_name) && property_counters[prop_name] > 1 << { 'level' => 3, 'message' => "#{prop_name} MUST NOT appear more than once in a #{@name} component", 'node' => self } end end end end |
#validation_rules ⇒ array
A simple list of validation rules.
This is simply a list of properties, and how many times they either must or must not appear.
Possible values per property:
* 0 - Must not appear.
* 1 - Must appear exactly once.
* + - Must appear at least once.
* * - Can appear any number of times.
* ? - May appear, but not more than once.
It is also possible to specify defaults and severity levels for violating the rule.
See the VEVENT implementation for getValidationRules for a more complex example.
495 496 497 |
# File 'lib/tilia/v_object/component.rb', line 495 def validation_rules [] end |
#xml_serialize(writer) ⇒ void
This method returns an undefined value.
This method serializes the data into XML. This is used to create xCard or xCal documents.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/tilia/v_object/component.rb', line 335 def xml_serialize(writer) components = [] properties = [] @children.each do |_, child_group| child_group.each do |child| if child.is_a?(Component) components << child else properties << child end end end writer.start_element(@name.downcase) if properties.any? writer.start_element('properties') properties.each do |property| property.xml_serialize(writer) end writer.end_element end if components.any? writer.start_element('components') components.each do |component| component.xml_serialize(writer) end writer.end_element end writer.end_element end |