Permalink
Browse files

initial commit of the VObject reader/ parser

  • Loading branch information...
mikedeboer committed Jan 31, 2013
1 parent da0908c commit 5bb55bca80c30069fb3fdba6425c294a06d5ff8d
View
@@ -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;
+ }
+ }
+});
No changes.
No changes.
No changes.
No changes.
No changes.
No changes.
No changes.
No changes.
No changes.
No changes.
Oops, something went wrong.

0 comments on commit 5bb55bc

Please sign in to comment.