Skip to content
Browse files

initial commit of the VObject reader/ parser

  • Loading branch information...
1 parent da0908c commit 5bb55bca80c30069fb3fdba6425c294a06d5ff8d @mikedeboer committed Jan 30, 2013
View
385 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;
+ }
+ }
+});
View
0 lib/VObject/component/VAlarm.js
No changes.
View
0 lib/VObject/component/VCalendar.js
No changes.
View
0 lib/VObject/component/VCard.js
No changes.
View
0 lib/VObject/component/VEvent.js
No changes.
View
0 lib/VObject/component/VFreeBusy.js
No changes.
View
0 lib/VObject/component/VJournal.js
No changes.
View
0 lib/VObject/component/VTodo.js
No changes.
View
0 lib/VObject/dateTimeParser.js
No changes.
View
0 lib/VObject/elementList.js
No changes.
View
0 lib/VObject/freeBusyGenerator.js
No changes.
View
56 lib/VObject/node.js
@@ -0,0 +1,56 @@
+/*
+ * @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 Base = require("../shared/base");
+
+/**
+ * Base class for all nodes
+ */
+var jsVObject_Node = module.exports = Base.extend({
+ /**
+ * The following constants are used by the validate() method.
+ */
+ REPAIR: 1,
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ serialize: function() {},
+
+ /**
+ * A link to the parent node
+ *
+ * @var Node
+ */
+ parent: null,
+
+ /**
+ * 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;
+ return [];
+ }
+});
View
78 lib/VObject/parameter.js
@@ -0,0 +1,78 @@
+/*
+ * @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_Node = require("./node");
+
+var Util = require("../shared/util");
+
+/**
+ * VObject Parameter
+ *
+ * This class represents a parameter. A parameter is always tied to a property.
+ * In the case of:
+ * DTSTART;VALUE=DATE:20101108
+ * VALUE=DATE would be the parameter name and value.
+ */
+var jsVObject_Parameter = module.exports = jsVObject_Node.extend({
+ /**
+ * Parameter name
+ *
+ * @var string
+ */
+ name: null,
+
+ /**
+ * Parameter value
+ *
+ * @var string
+ */
+ value: null,
+
+ /**
+ * Sets up the object
+ *
+ * @param string name
+ * @param string value
+ */
+ initialize: function(name, value) {
+ value = value || null;
+ if (!Util.isScalar(value) && value !== null)
+ throw new Error("The value argument must be a scalar value or null");
+
+ this.name = name.toUpperCase();
+ this.value = value;
+ },
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ serialize: function() {
+ if (this.value === null)
+ return this.name;
+
+ var value = this.value
+ .replace("\\", "\\\\")
+ .replace("\n", "\\n")
+ .replace(";", "\\;")
+ .replace(",", "\\,");
+
+ return this.name + "=" + value;
+ },
+
+ /**
+ * Called when this object is being cast to a string
+ *
+ * @return string
+ */
+ toString: function() {
+ return this.value;
+ }
+});
View
0 lib/VObject/parseException.js
No changes.
View
297 lib/VObject/property.js
@@ -0,0 +1,297 @@
+/*
+ * @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_Node = require("./node");
+var jsVObject_Property_DateTime = require("./property/dateTime");
+var jsVObject_Property_MultiDateTime = require("./property/multiDateTime");
+var jsVObject_Property_Compound = require("./property/compound");
+var jsVObject_Parameter = require("./parameter");
+
+var Util = require("../shared/util");
+
+/**
+ * VObject Property
+ *
+ * A property in VObject is usually in the form PARAMNAME:paramValue.
+ * An example is : SUMMARY:Weekly meeting
+ *
+ * Properties can also have parameters:
+ * SUMMARY;LANG=en:Weekly meeting.
+ */
+var jsVObject_Property = module.exports = jsVObject_Node.extend({
+ /**
+ * Propertyname
+ *
+ * @var string
+ */
+ name: null,
+
+ /**
+ * Group name
+ *
+ * This may be something like 'HOME' for vcards.
+ *
+ * @var string
+ */
+ group: null,
+
+ /**
+ * Property parameters
+ *
+ * @var array
+ */
+ parameters: [],
+
+ /**
+ * Property value
+ *
+ * @var string
+ */
+ value: null,
+
+ /**
+ * If properties 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: {
+ "COMPLETED" : jsVObject_Property_DateTime,
+ "CREATED" : jsVObject_Property_DateTime,
+ "DTEND" : jsVObject_Property_DateTime,
+ "DTSTAMP" : jsVObject_Property_DateTime,
+ "DTSTART" : jsVObject_Property_DateTime,
+ "DUE" : jsVObject_Property_DateTime,
+ "EXDATE" : jsVObject_Property_MultiDateTime,
+ "LAST-MODIFIED": jsVObject_Property_DateTime,
+ "RECURRENCE-ID": jsVObject_Property_DateTime,
+ "TRIGGER" : jsVObject_Property_DateTime,
+ "N" : jsVObject_Property_Compound,
+ "ORG" : jsVObject_Property_Compound,
+ "ADR" : jsVObject_Property_Compound,
+ "CATEGORIES" : jsVObject_Property_Compound
+ },
+
+ /**
+ * Creates the new property by name, but in addition will also see if
+ * there's a class mapped to the property name.
+ *
+ * Parameters can be specified with the optional third argument. Parameters
+ * must be a key->value map of the parameter name, and value. If the value
+ * is specified as an array, it is assumed that multiple parameters with
+ * the same name should be added.
+ *
+ * @param string name
+ * @param string value
+ * @param object parameters
+ * @return Property
+ */
+ create: function(name, value, parameters) {
+ value = value || null;
+ parameters = parameters || {};
+
+ name = name.toUpperCase();
+ var shortName = name;
+ var group = null;
+ if (shortName.indexOf(".") > -1) {
+ var parts = shortName.split(".");
+ group = parts[0];
+ shortName = parts[1];
+ }
+
+ if (this.classMap[shortName])
+ return this.classMap[shortName].new(name, value, parameters);
+ else
+ return jsVObject_Property.new(name, value, parameters);
+ },
+
+ /**
+ * Creates a new property object
+ *
+ * Parameters can be specified with the optional third argument. Parameters
+ * must be a key->value map of the parameter name, and value. If the value
+ * is specified as an array, it is assumed that multiple parameters with
+ * the same name should be added.
+ *
+ * @param string name
+ * @param string value
+ * @param object parameters
+ */
+ initialize: function(name, value, parameters) {
+ value = value || null;
+ parameters = parameters || {};
+ if (!Util.isScalar(value) && value !== null)
+ throw new Error("The value argument must be scalar or null");
+
+ name = name.toUpperCase();
+ var group = null;
+ if (name.indexOf(".") > -1) {
+ var parts = name.split(".");
+ group = parts[0]
+ name = parts[1];
+ }
+ this.name = name;
+ this.group = group;
+ this.setValue(value);
+
+ var paramValues, i, l;
+ for (var paramName in parameters) {
+ paramValues = parameters[paramName];
+ if (!Array.isArray(paramValues))
+ paramValues = [paramValues];
+
+ for (i = 0, l = paramValues.length; i < l; ++i)
+ this.add(paramName, paramValues[i]);
+ }
+ },
+
+ /**
+ * Updates the internal value
+ *
+ * @param string value
+ * @return void
+ */
+ setValue: function(value) {
+ this.value = value;
+ },
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ serialize: function() {
+ var str = this.name;
+ if (this.group)
+ str = this.group + "." + this.name;
+
+ this.parameters.forEach(function(param) {
+ str += ";" + param.serialize();
+ });
+
+ str += ":" + this.value
+ .replace("\\", "\\\\")
+ .replace("\n", "\\n");
+
+ var out = "";
+ while (str.length > 0) {
+ if (str.length > 75) {
+ out += str.substr(0,75) + "\r\n";
+ str = " " + str.substr(75, str.length);
+ }
+ else {
+ out += str + "\r\n";
+ str = "";
+ break;
+ }
+ }
+
+ return out;
+ },
+
+ /**
+ * Adds a new componenten or element
+ *
+ * You can call this method with the following syntaxes:
+ *
+ * add(Parameter element)
+ * add(string name, value)
+ *
+ * The first version adds an Parameter
+ * The second adds a property as a string.
+ *
+ * @param mixed item
+ * @param mixed itemValue
+ * @return void
+ */
+ add: function(item, itemValue) {
+ itemValue = itemValue || null;
+
+ if (item.hasFeature(jsVObject_Parameter)) {
+ if (itemValue !== null)
+ throw new Error("The second argument must not be specified, when passing a VObject");
+ item.parent = this;
+ this.parameters.push(item);
+ }
+ else if(typeof item == "string") {
+ var parameter = new jsVObject_Parameter(item, itemValue);
+ parameter.parent = this;
+ this.parameters.push(parameter);
+ }
+ else
+ throw new Error("The first argument must either be a Node a string");
+ },
+
+ /**
+ * Called when this object is being cast to a string
+ *
+ * @return string
+ */
+ toString: function() {
+ return this.value.toString();
+ },
+
+ /**
+ * 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.parameters.length; i < l; ++i) {
+ this.parameters[i] = this.parameters[i].clone();
+ this.parameters[i].parent = this;
+ }
+ },
+
+ /**
+ * 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 warnings = [];
+
+ // Checking if the propertyname does not contain any invalid bytes.
+ if (!/^([A-Z0-9\-]+)/.test(this.name)) {
+ warnings.push({
+ "level": 1,
+ "message": "The propertyname: " + this.name + " contains invalid characters. Only A-Z, 0-9 and - are allowed",
+ "node": this
+ });
+ if (options & this.REPAIR) {
+ // Uppercasing and converting underscores to dashes.
+ this.name = this.name.replace("_", "-").toUpperCase();
+ // Removing every other invalid character
+ this.name = this.name.replace(/([^A-Z0-9\-])/g, "");
+ }
+ }
+
+ // Validating inner parameters
+ this.parameters.forEach(function(param) {
+ warnings = warnings.concat(param.validate(options));
+ });
+
+ return warnings;
+ }
+});
View
0 lib/VObject/property/compound.js
No changes.
View
0 lib/VObject/property/dateTime.js
No changes.
View
0 lib/VObject/property/multiDateTime.js
No changes.
View
202 lib/VObject/reader.js
@@ -0,0 +1,202 @@
+/*
+ * @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_Component = require("./component");
+var jsVObject_Parameter = require("./parameter");
+var jsVObject_Property = require("./property");
+
+var Base = require("./../shared/base");
+var Exc = require("./../shared/exceptions");
+var Util = require("./../shared/util");
+
+/**
+ * VCALENDAR/VCARD reader
+ *
+ * This class reads the vobject file, and returns a full element tree.
+ */
+var jsVObject_Reader = module.exports = Base.extend({
+ /**
+ * If this option is passed to the reader, it will be less strict about the
+ * validity of the lines.
+ *
+ * Currently using this option just means, that it will accept underscores
+ * in property names.
+ */
+ OPTION_FORGIVING: 1,
+
+ /**
+ * If this option is turned on, any lines we cannot parse will be ignored
+ * by the reader.
+ */
+ OPTION_IGNORE_INVALID_LINES: 2,
+
+ /**
+ * Parses the file and returns the top component
+ *
+ * The options argument is a bitfield. Pass any of the OPTIONS constant to
+ * alter the parsers' behaviour.
+ *
+ * @param string data
+ * @param int options
+ * @return Node
+ */
+ read: function(data, options) {
+ options = options || 0;
+ // Normalizing newlines
+ data = data.replace(/(\r|\n\n)/g, "\n");
+
+ var lines = data.split(/\n/);
+
+ // Unfolding lines
+ var lines2 = [];
+ for (var line, i = 0, l = lines.length; i < l; ++i) {
+ line = lines[i];
+ // Skipping empty lines
+ if (!line)
+ continue;
+
+ if (line.charAt(0) === " " || line.charAt(0) === "\t")
+ lines2[lines2.length - 1] += line.substr(1);
+ else
+ lines2.push(line);
+ }
+
+ this.lines = lines2;
+ this.currentLine = 0;
+
+ return this.readLine(lines2, options);
+ },
+
+ /**
+ * Reads and parses a single line.
+ *
+ * This method receives the full array of lines. The array pointer is used
+ * to traverse.
+ *
+ * This method returns null if an invalid line was encountered, and the
+ * IGNORE_INVALID_LINES option was turned on.
+ *
+ * @param array lines
+ * @param int options See the OPTIONS constants.
+ * @return Node
+ */
+ readLine: function(lines, options) {
+ options = options || 0;
+ var line = lines[this.currentLine || 0];
+ var lineNr = this.currentLine;
+ ++this.currentLine;
+
+ var obj;
+ // Components
+ if (line.substr(0, 6).toUpperCase() == "BEGIN:") {
+ var componentName = line.substr(6).toUpperCase();
+ obj = jsVObject_Component.create(componentName);
+
+ var nextLine = lines[this.currentLine];
+
+ var parsedLine;
+ while (nextLine.substr(0,4).toUpperCase() != "END:") {
+ parsedLine = this.readLine(lines, options);
+ nextLine = lines[this.currentLine];
+
+ if (!parsedLine)
+ continue;
+ obj.add(parsedLine);
+
+ if (!nextLine)
+ throw new SyntaxError("Invalid VObject. Document ended prematurely.");
+ }
+
+ // Checking component name of the 'END:' line.
+ if (nextLine.substr(4) !== obj.name)
+ throw new SyntaxError("Invalid VObject, expected: 'END:" + obj.name + "' got: '" + nextLine + "'");
+
+ ++this.currentLine;
+
+ return obj;
+ }
+
+ // Properties
+ //result = preg_match('/(?P<name>[A-Z0-9-]+)(?:;(?P<parameters>^(?<!:):))(.*)/',line,matches);
+
+ var token = options & this.OPTION_FORGIVING
+ ? "[A-Z0-9-\._]+"
+ : "[A-Z0-9-\.]+"
+ //var parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?";
+ var parameters = "(?:;((?:[^:^\"]|\"(?:[^\"]*)\")*))?";
+ //regex = "/^(?P<name>token)parameters:(?P<value>.*)/i";
+ var regex = "^(" + token + ")" + parameters + ":(.*)";
+
+ //result = preg_match(regex,line,matches);
+ var matches = line.match(new RegExp(regex, "i"));
+ if (!matches) {
+ if (options & this.OPTION_IGNORE_INVALID_LINES)
+ return null;
+ else
+ throw new SyntaxError("Invalid VObject, line " + (lineNr + 1) + " did not follow the icalendar/vcard format");
+ }
+
+ var propertyName = matches[1];
+ var propertyValue = matches[matches.length - 1].replace(/(\\\\(\\\\|N|n))/g, function(m, full, denote) {
+ if (denote == "n" || denote == "N")
+ return "\n";
+ else
+ return denote;
+ });
+
+ obj = jsVObject_Property.create(propertyName, propertyValue);
+
+ if (matches.length === 4 && matches[2]) {
+ this.readParameters(matches[2]).forEach(function(param) {
+ obj.add(param);
+ });
+ }
+
+ return obj;
+ },
+
+ /**
+ * Reads a parameter list from a property
+ *
+ * This method returns an array of Parameter
+ *
+ * @param string parameters
+ * @return array
+ */
+ readParameters: function(parameters) {
+ var token = "[A-Z0-9-]+";
+ var paramValue = '([^\"^;]*|"[^"]*")';
+ //$regex = "/(?<=^|;)(?P<paramName>$token)(=$paramValue(?=$|;))?/i";
+ var regex = "(?<=^|;)(" + token + ")(=" + paramValue + "(?=|;))?";
+
+ var params = [];
+ parameters.replace(new RegExp(regex, "gi"), function(m, pfx, paramName, param, paramValue, sfx) {
+ var value = paramValue ? paramValue : null;
+
+ if (value && value.charAt(0)) {
+ // Stripping quotes, if needed
+ if (value.charAt(0) === '"')
+ value = value.substr(1, value.length - 2);
+ }
+ else
+ value = "";
+
+ value = value.replace(/(\\\\(\\\\|N|n))/g, function(m, full, denote) {
+ if (denote == "n" || denote == "N")
+ return "\n";
+ else
+ return denote;
+ });
+
+ params.push(jsVObject_Parameter.new(paramName, value));
+ });
+
+ return params;
+ }
+});
View
0 lib/VObject/recurrenceIterator.js
No changes.
View
0 lib/VObject/splitter/VCard.js
No changes.
View
0 lib/VObject/splitter/iCalendar.js
No changes.
View
0 lib/VObject/splitter/splitterInterface.js
No changes.
View
4 lib/shared/util.js
@@ -224,6 +224,10 @@ exports.isFalse = function(c){
return (c === false || c === "false" || c === "off" || c === 0 || c === "0");
};
+exports.isScalar = function(mixed) {
+ return (/boolean|number|string/).test(typeof mixed);
+};
+
/**
* Returns the 'dirname' and 'basename' for a path.
*

0 comments on commit 5bb55bc

Please sign in to comment.
Something went wrong with that request. Please try again.