Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit of the VObject reader/ parser
- Loading branch information
1 parent
da0908c
commit 5bb55bc
Showing
24 changed files
with
1,022 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Oops, something went wrong.