Skip to content

Commit

Permalink
initial commit of the VObject reader/ parser
Browse files Browse the repository at this point in the history
  • Loading branch information
mikedeboer committed Jan 31, 2013
1 parent da0908c commit 5bb55bc
Show file tree
Hide file tree
Showing 24 changed files with 1,022 additions and 0 deletions.
385 changes: 385 additions & 0 deletions lib/VObject/component.js
@@ -0,0 +1,385 @@
/*
* @package jsDAV
* @subpackage VObject
* @copyright Copyright(c) 2013 Mike de Boer. <info AT mikedeboer DOT nl>
* @author Mike de Boer <info AT mikedeboer DOT nl>
* @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
*/
"use strict";

var jsVObject_ElementList = require("./elementList");
var jsVObject_Node = require("./node");
var jsVObject_Property = require("./property");
var jsVObject_VAlarm = require("./component/VAlarm");
var jsVObject_VCalendar = require("./component/VCalendar");
var jsVObject_VCard = require("./component/VCard");
var jsVObject_VEvent = require("./component/VEvent");
var jsVObject_VJournal = require("./component/VJournal");
var jsVObject_VTodo = require("./component/VTodo");
var jsVObject_VFreeBusy = require("./component/VFreeBusy");

var Exc = require("./../shared/exceptions");
var Util = require("./../shared/util");

/**
* VObject Component
*
* This class represents a VCALENDAR/VCARD component. A component is for example
* VEVENT, VTODO and also VCALENDAR. It starts with BEGIN:COMPONENTNAME and
* ends with END:COMPONENTNAME
*/
var jsVObject_Component = module.exports = jsVObject_Node.extend({
/**
* Name, for example VEVENT
*
* @var string
*/
name: null,

/**
* Children properties and components
*
* @var array
*/
children: [],

/**
* If components are added to this map, they will be automatically mapped
* to their respective classes, if parsed by the reader or constructed with
* the 'create' method.
*
* @var array
*/
classMap: {
"VALARM" : jsVObject_VAlarm,
"VCALENDAR" : jsVObject_VCalendar,
"VCARD" : jsVObject_VCard,
"VEVENT" : jsVObject_VEvent,
"VJOURNAL" : jsVObject_VJournal,
"VTODO" : jsVObject_VTodo,
"VFREEBUSY" : jsVObject_VFreeBusy
},

/**
* Creates the new component by name, but in addition will also see if
* there's a class mapped to the property name.
*
* @param string name
* @param string value
* @return Component
*/
create: function(name, value) {
name = name.toUpperCase();
if (this.classMap[name])
return this.classMap[name].new(name, value);
else
return jsVObject_Component.new(name, value);
},

/**
* Creates a new component.
*
* By default this object will iterate over its own children, but this can
* be overridden with the iterator argument
*
* @param string name
* @param ElementList iterator
*/
initialize: function(name, iterator) {
this.name = name.toUpperCase();
if (iterator)
this.iterator = iterator;
},

/**
* Turns the object back into a serialized blob.
*
* @return string
*/
serialize: function() {
var aStr = ["BEGIN:" + this.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 int key
* @param array array
* @return int
*/
function sortScore(key, array) {
var score;
if (array[key].hasFeature(jsVObject_Component)) {
// We want to encode VTIMEZONE first, this is a personal
// preference.
if (array[key].name == "VTIMEZONE") {
score = 300000000;
return score + key;
}
else {
score = 400000000;
return score + key;
}
}
else {
// Properties get encoded first
// VCARD version 4.0 wants the VERSION property to appear first
if (array[key].hasFeature(jsVObject_Property)) {
if (array[key].name == "VERSION") {
score = 100000000;
return score + key;
}
else {
// All other properties
score = 200000000;
return score + key;
}
}
}
}

var tmp = [].concat(this.children);
this.children.sort(function(a, b) {
var sA = sortScore(tmp.indexOf(a), tmp);
var sB = sortScore(tmp.indexOf(b), tmp);

if (sA === sB)
return 0;

return (sA < sB) ? -1 : 1;
}).forEach(function(child) {
aStr.push(child.serialize());
});

return aStr.join("") + "END:" + this.name + "\r\n";
},

/**
* Adds a new component or element
*
* You can call this method with the following syntaxes:
*
* add(Node node)
* add(string name, value, array parameters = array())
*
* The first version adds an Element
* The second adds a property as a string.
*
* @param mixed item
* @param mixed itemValue
* @return void
*/
add: function(item, itemValue, parameters) {
parameters = parameters || {};
if (item.hasFeature(jsVObject_Node)) {
if (itemValue)
throw new Error("The second argument must not be specified, when passing a VObject Node");
item.parent = this;
this.children.push(item);
}
else if (typeof item == "string") {
item = jsVObject_Property.create(item, itemValue, parameters);
item.parent = this;
this.children.push(item);
}
else {
throw new Error("The first argument must either be a jsVObject_Node or a string");
}
},

/**
* Returns an iterable list of children
*
* @return ElementList
*/
getChildren: function() {
return jsVObject_ElementList.new(this.children);
},

/**
* 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".
*
* Keys are retained from the 'children' array, which may be confusing in
* certain cases.
*
* @param string name
* @return array
*/
select: function(name) {
var group = null;
name = name.toUpperCase();
if (name.indexOf(".") > -1) {
var parts = name.split(".");
group = parts[0];
name = parts[1];
}

var result = [];
this.children.forEach(function(child, key) {
if (
child.name.toUpperCase() === name &&
(!group || (child.hasFeature(jsVObject_Property) && child.group.toUpperCase() === group))
) {
result[key] = child;
}
});

return result;
},

/**
* This method only returns a list of sub-components. Properties are
* ignored.
*
* @return array
*/
getComponents: function() {
var result = [];
this.children.forEach(function(child) {
if (child.hasFeature(jsVObject_Component))
result.push(child);
})

return result;
},

/**
* Validates the node for correctness.
*
* The following options are supported:
* - Node::REPAIR - If something is broken, and automatic repair may
* be attempted.
*
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @param int options
* @return array
*/
validate: function(options) {
options = options || 0;
var result = [];
this.children.forEach(function(child) {
result = result.concat(child.validate(options));
});
return result;
},

/* Magic property accessors {{{ */

/**
* Using 'get' you will either get a property or component,
*
* If there were no child-elements found with the specified name,
* null is returned.
*
* @param string name
* @return Property
*/
__get: function(name) {
var matches = this.select(name);
if (matches.length === 0) {
return null;
}
else {
var firstMatch = matches[0];
/** @var firstMatch Property */
firstMatch.setIterator(jsVObject_ElementList.new(matches));
return firstMatch;
}
},

/**
* This method checks if a sub-element with the specified name exists.
*
* @param string name
* @return bool
*/
__isset: function(name) {
var matches = this.select(name);
return matches.length > 0;
},

/**
* 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.
*
* @param string name
* @param mixed value
* @return void
*/
__set: function(name, value) {
var matches = this.select(name);
var overWrite = matches.length ? this.children.indexOf(matches[0]) : null;

if (value.hasFeature(jsVObject_Component) || value.hasFeature(jsVObject_Property)) {
value.parent = this;
if (overWrite !== null)
this.children[overWrite] = value;
else
this.children.push(value);
}
else if (Util.isScalar(value)) {
var property = jsVObject_Property.create(name, value);
property.parent = this;
if (overWrite !== null)
this.children[overWrite] = property;
else
this.children.push(property);
}
else {
throw new Error("You must pass a jsVObject_Component, jsVObject_Property or scalar type");
}
},

/**
* Removes all properties and components within this component.
*
* @param string name
* @return void
*/
__unset: function(name) {
var matches = this.select(name);
for (var i = matches.length - 1; i >= 0; --i) {
this.children.splice(this.children.indexOf(matches[i]), 1);
matches[i].parent = null;
}
},

/* }}} */

/**
* This method is automatically called when the object is cloned.
* Specifically, this will ensure all child elements are also cloned.
*
* @return void
*/
__clone: function() {
for (var i = 0, l = this.children.length; i < l; ++i) {
this.children[i] = this.children[i].clone();
this.children[i].parent = this;
}
}
});
Empty file added lib/VObject/component/VAlarm.js
Empty file.
Empty file.
Empty file added lib/VObject/component/VCard.js
Empty file.
Empty file added lib/VObject/component/VEvent.js
Empty file.
Empty file.
Empty file.
Empty file added lib/VObject/component/VTodo.js
Empty file.
Empty file added lib/VObject/dateTimeParser.js
Empty file.
Empty file added lib/VObject/elementList.js
Empty file.
Empty file.

0 comments on commit 5bb55bc

Please sign in to comment.