vcard.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // exported globals
  2. var VCard;
  3. (function() {
  4. VCard = function(attributes) {
  5. this.changed = false;
  6. if(typeof(attributes) === 'object') {
  7. for(var key in attributes) {
  8. this[key] = attributes[key];
  9. this.changed = true;
  10. }
  11. }
  12. };
  13. VCard.prototype = {
  14. // Check validity of this VCard instance. Properties that can be generated,
  15. // will be generated. If any error is found, false is returned and vcard.errors
  16. // set to an Array of [attribute, errorType] arrays.
  17. // Otherwise true is returned.
  18. //
  19. // In case of multivalued properties, the "attribute" part of the error is
  20. // the attribute name, plus it's index (starting at 0). Example: email0, tel7, ...
  21. //
  22. // It is recommended to call this method even if this VCard object was imported,
  23. // as some software (e.g. Gmail) doesn't generate UIDs.
  24. validate: function() {
  25. var errors = [];
  26. function addError(attribute, type) {
  27. errors.push([attribute, type]);
  28. }
  29. if(! this.fn) { // FN is a required attribute
  30. addError("fn", "required");
  31. }
  32. // make sure multivalued properties are *always* in array form
  33. for(var key in VCard.multivaluedKeys) {
  34. if(this[key] && ! (this[key] instanceof Array)) {
  35. this[key] = [this[key]];
  36. }
  37. }
  38. // make sure compound fields have their type & value set
  39. // (to prevent mistakes such as vcard.addAttribute('email', 'foo@bar.baz')
  40. function validateCompoundWithType(attribute, values) {
  41. for(var i in values) {
  42. var value = values[i];
  43. if(typeof(value) !== 'object') {
  44. errors.push([attribute + '-' + i, "not-an-object"]);
  45. } else if(! value.type) {
  46. errors.push([attribute + '-' + i, "missing-type"]);
  47. } else if(! value.value) { // empty values are not allowed.
  48. errors.push([attribute + '-' + i, "missing-value"]);
  49. }
  50. }
  51. }
  52. if(this.email) {
  53. validateCompoundWithType('email', this.email);
  54. }
  55. if(this.tel) {
  56. validateCompoundWithType('email', this.tel);
  57. }
  58. if(! this.uid) {
  59. this.addAttribute('uid', uuidFast());
  60. }
  61. if(! this.rev) {
  62. this.addAttribute('rev', new Date());
  63. }
  64. this.errors = errors;
  65. return ! (errors.length > 0);
  66. },
  67. // generate a UID. This generates a UUID with uuid: URN namespace, as suggested
  68. // by RFC 6350, 6.7.6
  69. generateUID: function() {
  70. return 'uuid:' + Math.uuid();
  71. },
  72. // generate revision timestamp (a full ISO 8601 date/time string in basic format)
  73. generateRev: function() {
  74. return (new Date()).toISOString().replace(/[\.\:\-]/g, '');
  75. },
  76. // Set the given attribute to the given value.
  77. // This sets vcard.changed to true, so you can check later whether anything
  78. // was updated by your code.
  79. setAttribute: function(key, value) {
  80. this[key] = value;
  81. this.changed = true;
  82. },
  83. // Set the given attribute to the given value.
  84. // If the given attribute's key has cardinality > 1, instead of overwriting
  85. // the current value, an additional value is appended.
  86. addAttribute: function(key, value) {
  87. console.log('add attribute', key, value);
  88. if(! value) {
  89. return;
  90. }
  91. if(VCard.multivaluedKeys[key]) {
  92. if(this[key]) {
  93. console.log('multivalued push');
  94. this[key].push(value)
  95. } else {
  96. console.log('multivalued set');
  97. this.setAttribute(key, [value]);
  98. }
  99. } else {
  100. this.setAttribute(key, value);
  101. }
  102. },
  103. // convenience method to get a JSON serialized jCard.
  104. toJSON: function() {
  105. return JSON.stringify(this.toJCard());
  106. },
  107. // Copies all properties (i.e. all specified in VCard.allKeys) to a new object
  108. // and returns it.
  109. // Useful to serialize to JSON afterwards.
  110. toJCard: function() {
  111. var jcard = {};
  112. for(var k in VCard.allKeys) {
  113. var key = VCard.allKeys[k];
  114. if(this[key]) {
  115. jcard[key] = this[key];
  116. }
  117. }
  118. return jcard;
  119. },
  120. // synchronizes two vcards, using the mechanisms described in
  121. // RFC 6350, Section 7.
  122. // Returns a new VCard object.
  123. // If a property is present in both source vcards, and that property's
  124. // maximum cardinality is 1, then the value from the second (given) vcard
  125. // precedes.
  126. //
  127. // TODO: implement PID matching as described in 7.3.1
  128. merge: function(other) {
  129. if(typeof(other.uid) !== 'undefined' &&
  130. typeof(this.uid) !== 'undefined' &&
  131. other.uid !== this.uid) {
  132. // 7.1.1
  133. throw "Won't merge vcards without matching UIDs.";
  134. }
  135. var result = new VCard();
  136. function mergeProperty(key) {
  137. if(other[key]) {
  138. if(other[key] == this[key]) {
  139. result.setAttribute(this[key]);
  140. } else {
  141. result.addAttribute(this[key]);
  142. result.addAttribute(other[key]);
  143. }
  144. } else {
  145. result[key] = this[key];
  146. }
  147. }
  148. for(key in this) { // all properties of this
  149. mergeProperty(key);
  150. }
  151. for(key in other) { // all properties of other *not* in this
  152. if(! result[key]) {
  153. mergeProperty(key);
  154. }
  155. }
  156. }
  157. };
  158. VCard.enums = {
  159. telType: ["text", "voice", "fax", "cell", "video", "pager", "textphone"],
  160. relatedType: ["contact", "acquaintance", "friend", "met", "co-worker",
  161. "colleague", "co-resident", "neighbor", "child", "parent",
  162. "sibling", "spouse", "kin", "muse", "crush", "date",
  163. "sweetheart", "me", "agent", "emergency"],
  164. // FIXME: these aren't actually defined anywhere. just very commmon.
  165. // maybe there should be more?
  166. emailType: ["work", "home", "internet"],
  167. langType: ["work", "home"],
  168. };
  169. VCard.allKeys = [
  170. 'fn', 'n', 'nickname', 'photo', 'bday', 'anniversary', 'gender',
  171. 'tel', 'email', 'impp', 'lang', 'tz', 'geo', 'title', 'role', 'logo',
  172. 'org', 'member', 'related', 'categories', 'note', 'prodid', 'rev',
  173. 'sound', 'uid'
  174. ];
  175. VCard.multivaluedKeys = {
  176. email: true,
  177. tel: true,
  178. geo: true,
  179. title: true,
  180. role: true,
  181. logo: true,
  182. org: true,
  183. member: true,
  184. related: true,
  185. categories: true,
  186. note: true
  187. };
  188. })();
  189. module.exports = VCard;
  190. function uuidFast() {
  191. var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''), uuid = new Array(36), rnd=0, r;
  192. for (var i = 0; i < 36; i++) {
  193. if (i==8 || i==13 || i==18 || i==23) {
  194. uuid[i] = '-';
  195. } else if (i==14) {
  196. uuid[i] = '4';
  197. } else {
  198. if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0;
  199. r = rnd & 0xf;
  200. rnd = rnd >> 4;
  201. uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
  202. }
  203. }
  204. return uuid.join('');
  205. };