forked from kewisch/ical.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
helpers.js
377 lines (342 loc) · 10.6 KB
/
helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http:https://mozilla.org/MPL/2.0/.
* Portions Copyright (C) Philipp Kewisch, 2011-2015 */
/**
* The number of characters before iCalendar line folding should occur
* @type {Number}
* @default 75
*/
ICAL.foldLength = 75;
/**
* The character(s) to be used for a newline. The default value is provided by
* rfc5545.
* @type {String}
* @default "\r\n"
*/
ICAL.newLineChar = '\r\n';
/**
* Helper functions used in various places within ical.js
* @namespace
*/
ICAL.helpers = {
/**
* Compiles a list of all referenced TZIDs in all subcomponents and
* removes any extra VTIMEZONE subcomponents. In addition, if any TZIDs
* are referenced by a component, but a VTIMEZONE does not exist,
* an attempt will be made to generate a VTIMEZONE using ICAL.TimezoneService.
*
* @param {ICAL.Component} vcal The top-level VCALENDAR component.
* @return {ICAL.Component} The ICAL.Component that was passed in.
*/
updateTimezones: function(vcal) {
var allsubs, properties, vtimezones, reqTzid, i, tzid;
if (!vcal || vcal.name !== "vcalendar") {
//not a top-level vcalendar component
return vcal;
}
//Store vtimezone subcomponents in an object reference by tzid.
//Store properties from everything else in another array
allsubs = vcal.getAllSubcomponents();
properties = [];
vtimezones = {};
for (i = 0; i < allsubs.length; i++) {
if (allsubs[i].name === "vtimezone") {
tzid = allsubs[i].getFirstProperty("tzid").getFirstValue();
vtimezones[tzid] = allsubs[i];
} else {
properties = properties.concat(allsubs[i].getAllProperties());
}
}
//create an object with one entry for each required tz
reqTzid = {};
for (i = 0; i < properties.length; i++) {
if ((tzid = properties[i].getParameter("tzid"))) {
reqTzid[tzid] = true;
}
}
//delete any vtimezones that are not on the reqTzid list.
for (i in vtimezones) {
if (vtimezones.hasOwnProperty(i) && !reqTzid[i]) {
vcal.removeSubcomponent(vtimezones[i]);
}
}
//create any missing, but registered timezones
for (i in reqTzid) {
if (
reqTzid.hasOwnProperty(i) &&
!vtimezones[i] &&
ICAL.TimezoneService.has(i)
) {
vcal.addSubcomponent(ICAL.TimezoneService.get(i).component);
}
}
return vcal;
},
/**
* Checks if the given type is of the number type and also NaN.
*
* @param {Number} number The number to check
* @return {Boolean} True, if the number is strictly NaN
*/
isStrictlyNaN: function(number) {
return typeof(number) === 'number' && isNaN(number);
},
/**
* Parses a string value that is expected to be an integer, when the valid is
* not an integer throws a decoration error.
*
* @param {String} string Raw string input
* @return {Number} Parsed integer
*/
strictParseInt: function(string) {
var result = parseInt(string, 10);
if (ICAL.helpers.isStrictlyNaN(result)) {
throw new Error(
'Could not extract integer from "' + string + '"'
);
}
return result;
},
/**
* Creates or returns a class instance of a given type with the initialization
* data if the data is not already an instance of the given type.
*
* @example
* var time = new ICAL.Time(...);
* var result = ICAL.helpers.formatClassType(time, ICAL.Time);
*
* (result instanceof ICAL.Time)
* // => true
*
* result = ICAL.helpers.formatClassType({}, ICAL.Time);
* (result isntanceof ICAL.Time)
* // => true
*
*
* @param {Object} data object initialization data
* @param {Object} type object type (like ICAL.Time)
* @return {?} An instance of the found type.
*/
formatClassType: function formatClassType(data, type) {
if (typeof(data) === 'undefined') {
return undefined;
}
if (data instanceof type) {
return data;
}
return new type(data);
},
/**
* Identical to indexOf but will only match values when they are not preceded
* by a backslash character.
*
* @param {String} buffer String to search
* @param {String} search Value to look for
* @param {Number} pos Start position
* @return {Number} The position, or -1 if not found
*/
unescapedIndexOf: function(buffer, search, pos) {
while ((pos = buffer.indexOf(search, pos)) !== -1) {
if (pos > 0 && buffer[pos - 1] === '\\') {
pos += 1;
} else {
return pos;
}
}
return -1;
},
/**
* Find the index for insertion using binary search.
*
* @param {Array} list The list to search
* @param {?} seekVal The value to insert
* @param {function(?,?)} cmpfunc The comparison func, that can
* compare two seekVals
* @return {Number} The insert position
*/
binsearchInsert: function(list, seekVal, cmpfunc) {
if (!list.length)
return 0;
var low = 0, high = list.length - 1,
mid, cmpval;
while (low <= high) {
mid = low + Math.floor((high - low) / 2);
cmpval = cmpfunc(seekVal, list[mid]);
if (cmpval < 0)
high = mid - 1;
else if (cmpval > 0)
low = mid + 1;
else
break;
}
if (cmpval < 0)
return mid; // insertion is displacing, so use mid outright.
else if (cmpval > 0)
return mid + 1;
else
return mid;
},
/**
* Convenience function for debug output
* @private
*/
dumpn: /* istanbul ignore next */ function() {
if (!ICAL.debug) {
return;
}
if (typeof (console) !== 'undefined' && 'log' in console) {
ICAL.helpers.dumpn = function consoleDumpn(input) {
console.log(input);
};
} else {
ICAL.helpers.dumpn = function geckoDumpn(input) {
dump(input + '\n');
};
}
ICAL.helpers.dumpn(arguments[0]);
},
/**
* Clone the passed object or primitive. By default a shallow clone will be
* executed.
*
* @param {*} aSrc The thing to clone
* @param {Boolean=} aDeep If true, a deep clone will be performed
* @return {*} The copy of the thing
*/
clone: function(aSrc, aDeep) {
if (!aSrc || typeof aSrc != "object") {
return aSrc;
} else if (aSrc instanceof Date) {
return new Date(aSrc.getTime());
} else if ("clone" in aSrc) {
return aSrc.clone();
} else if (Array.isArray(aSrc)) {
var arr = [];
for (var i = 0; i < aSrc.length; i++) {
arr.push(aDeep ? ICAL.helpers.clone(aSrc[i], true) : aSrc[i]);
}
return arr;
} else {
var obj = {};
for (var name in aSrc) {
// uses prototype method to allow use of Object.create(null);
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(aSrc, name)) {
if (aDeep) {
obj[name] = ICAL.helpers.clone(aSrc[name], true);
} else {
obj[name] = aSrc[name];
}
}
}
return obj;
}
},
/**
* Performs iCalendar line folding. A line ending character is inserted and
* the next line begins with a whitespace.
*
* @example
* SUMMARY:This line will be fold
* ed right in the middle of a word.
*
* @param {String} aLine The line to fold
* @return {String} The folded line
*/
foldline: function foldline(aLine) {
var result = "";
var line = aLine || "", pos = 0, line_length = 0;
//pos counts position in line for the UTF-16 presentation
//line_length counts the bytes for the UTF-8 presentation
while (line.length) {
var cp = line.codePointAt(pos);
if (cp < 128) ++line_length;
else if (cp < 2048) line_length += 2;//needs 2 UTF-8 bytes
else if (cp < 65536) line_length += 3;
else line_length += 4; //cp is less than 1114112
if (line_length < ICAL.foldLength + 1)
pos += cp > 65535 ? 2 : 1;
else {
result += ICAL.newLineChar + " " + line.substring(0, pos);
line = line.substring(pos);
pos = line_length = 0;
}
}
return result.substr(ICAL.newLineChar.length + 1);
},
/**
* Pads the given string or number with zeros so it will have at least two
* characters.
*
* @param {String|Number} data The string or number to pad
* @return {String} The number padded as a string
*/
pad2: function pad(data) {
if (typeof(data) !== 'string') {
// handle fractions.
if (typeof(data) === 'number') {
data = parseInt(data);
}
data = String(data);
}
var len = data.length;
switch (len) {
case 0:
return '00';
case 1:
return '0' + data;
default:
return data;
}
},
/**
* Truncates the given number, correctly handling negative numbers.
*
* @param {Number} number The number to truncate
* @return {Number} The truncated number
*/
trunc: function trunc(number) {
return (number < 0 ? Math.ceil(number) : Math.floor(number));
},
/**
* Poor-man's cross-browser inheritance for JavaScript. Doesn't support all
* the features, but enough for our usage.
*
* @param {Function} base The base class constructor function.
* @param {Function} child The child class constructor function.
* @param {Object} extra Extends the prototype with extra properties
* and methods
*/
inherits: function(base, child, extra) {
function F() {}
F.prototype = base.prototype;
child.prototype = new F();
if (extra) {
ICAL.helpers.extend(extra, child.prototype);
}
},
/**
* Poor-man's cross-browser object extension. Doesn't support all the
* features, but enough for our usage. Note that the target's properties are
* not overwritten with the source properties.
*
* @example
* var child = ICAL.helpers.extend(parent, {
* "bar": 123
* });
*
* @param {Object} source The object to extend
* @param {Object} target The object to extend with
* @return {Object} Returns the target.
*/
extend: function(source, target) {
for (var key in source) {
var descr = Object.getOwnPropertyDescriptor(source, key);
if (descr && !Object.getOwnPropertyDescriptor(target, key)) {
Object.defineProperty(target, key, descr);
}
}
return target;
}
};