-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #738 from owncloud/vcard-within-repo
Use vCard from the app itself
- Loading branch information
Showing
7 changed files
with
316 additions
and
9 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
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
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
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
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
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,303 @@ | ||
var vCard = (function () { | ||
var PREFIX = 'BEGIN:VCARD', | ||
POSTFIX = 'END:VCARD'; | ||
|
||
/** | ||
* Return json representation of vCard | ||
* @param {string} string raw vCard | ||
* @returns {*} | ||
*/ | ||
function parse(string) { | ||
var result = {}, | ||
lines = string.split(/\r\n|\r|\n/), | ||
count = lines.length, | ||
pieces, | ||
key, | ||
value, | ||
meta, | ||
namespace; | ||
|
||
for (var i = 0; i < count; i++) { | ||
if (lines[i] == '') { | ||
continue; | ||
} | ||
if (lines[i].toUpperCase() == PREFIX || lines[i].toUpperCase() == POSTFIX) { | ||
continue; | ||
} | ||
var data = lines[i]; | ||
|
||
/** | ||
* Check that next line continues current | ||
* @param {number} i | ||
* @returns {boolean} | ||
*/ | ||
var isValueContinued = function (i) { | ||
return i + 1 < count && (lines[i + 1][0] == ' ' || lines[i + 1][0] == '\t'); | ||
}; | ||
// handle multiline properties (i.e. photo). | ||
// next line should start with space or tab character | ||
if (isValueContinued(i)) { | ||
while (isValueContinued(i)) { | ||
data += lines[i + 1].trim(); | ||
i++; | ||
} | ||
} | ||
|
||
pieces = data.split(':'); | ||
key = pieces.shift(); | ||
value = pieces.join(':'); | ||
namespace = false; | ||
meta = {}; | ||
|
||
// meta fields in property | ||
if (key.match(/;/)) { | ||
key = key | ||
.replace(/\\;/g, 'ΩΩΩ') | ||
.replace(/\\,/, ','); | ||
var metaArr = key.split(';').map(function (item) { | ||
return item.replace(/ΩΩΩ/g, ';'); | ||
}); | ||
key = metaArr.shift(); | ||
metaArr.forEach(function (item) { | ||
var arr = item.split('='); | ||
arr[0] = arr[0].toLowerCase(); | ||
if (arr[0].length === 0) { | ||
return; | ||
} | ||
if (meta[arr[0]]) { | ||
meta[arr[0]].push(arr[1]); | ||
} else { | ||
meta[arr[0]] = [arr[1]]; | ||
} | ||
}); | ||
} | ||
|
||
// values with \n | ||
value = value | ||
.replace(/\\n/g, '\n'); | ||
|
||
value = tryToSplit(value); | ||
|
||
// Grouped properties | ||
if (key.match(/\./)) { | ||
var arr = key.split('.'); | ||
key = arr[1]; | ||
namespace = arr[0]; | ||
} | ||
|
||
var newValue = { | ||
value: value | ||
}; | ||
if (Object.keys(meta).length) { | ||
newValue.meta = meta; | ||
} | ||
if (namespace) { | ||
newValue.namespace = namespace; | ||
} | ||
|
||
if (key.indexOf('X-') !== 0) { | ||
key = key.toLowerCase(); | ||
} | ||
|
||
if (typeof result[key] === 'undefined') { | ||
result[key] = [newValue]; | ||
} else { | ||
result[key].push(newValue); | ||
} | ||
|
||
} | ||
|
||
return result; | ||
} | ||
|
||
var HAS_SEMICOLON_SEPARATOR = /[^\\];|^;/, | ||
HAS_COMMA_SEPARATOR = /[^\\],|^,/; | ||
/** | ||
* Split value by "," or ";" and remove escape sequences for this separators | ||
* @param {string} value | ||
* @returns {string|string[] | ||
*/ | ||
function tryToSplit(value) { | ||
if (value.match(HAS_SEMICOLON_SEPARATOR)) { | ||
value = value.replace(/\\,/g, ','); | ||
return splitValue(value, ';'); | ||
} else if (value.match(HAS_COMMA_SEPARATOR)) { | ||
value = value.replace(/\\;/g, ';'); | ||
return splitValue(value, ','); | ||
} else { | ||
return value | ||
.replace(/\\,/g, ',') | ||
.replace(/\\;/g, ';'); | ||
} | ||
} | ||
/** | ||
* Split vcard field value by separator | ||
* @param {string|string[]} value | ||
* @param {string} separator | ||
* @returns {string|string[]} | ||
*/ | ||
function splitValue(value, separator) { | ||
var separatorRegexp = new RegExp(separator); | ||
var escapedSeparatorRegexp = new RegExp('\\\\' + separator, 'g'); | ||
// easiest way, replace it with really rare character sequence | ||
value = value.replace(escapedSeparatorRegexp, 'ΩΩΩ'); | ||
if (value.match(separatorRegexp)) { | ||
value = value.split(separator); | ||
|
||
value = value.map(function (item) { | ||
return item.replace(/ΩΩΩ/g, separator); | ||
}); | ||
} else { | ||
value = value.replace(/ΩΩΩ/g, separator); | ||
} | ||
return value; | ||
} | ||
|
||
var guid = (function() { | ||
function s4() { | ||
return Math.floor((1 + Math.random()) * 0x10000) | ||
.toString(16) | ||
.substring(1); | ||
} | ||
return function() { | ||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + | ||
s4() + '-' + s4() + s4() + s4(); | ||
}; | ||
})(); | ||
|
||
var COMMA_SEPARATED_FIELDS = ['nickname', 'related', 'categories', 'pid']; | ||
|
||
var REQUIRED_FIELDS = ['fn']; | ||
|
||
/** | ||
* Generate vCard representation af object | ||
* @param {*} data | ||
* @param {boolean=} addRequired determine if generator should add required properties (version and uid) | ||
* @returns {string} | ||
*/ | ||
function generate(data, addRequired) { | ||
var lines = [PREFIX], | ||
line = ''; | ||
|
||
if (addRequired && !data.version) { | ||
data.version = [{value: '3.0'}]; | ||
} | ||
if (addRequired && !data.uid) { | ||
data.uid = [{value: guid()}]; | ||
} | ||
|
||
var escapeCharacters = function (v) { | ||
if (typeof v === 'undefined') { | ||
return ''; | ||
} | ||
return v | ||
.replace(/\n/g, '\\n') | ||
.replace(/;/g, '\\;') | ||
.replace(/,/g, '\\,'); | ||
}; | ||
|
||
var escapeTypeCharacters = function(v) { | ||
if (typeof v === 'undefined') { | ||
return ''; | ||
} | ||
return v | ||
.replace(/\n/g, '\\n') | ||
.replace(/;/g, '\\;'); | ||
}; | ||
|
||
Object.keys(data).forEach(function (key) { | ||
if (!data[key] || typeof data[key].forEach !== 'function') { | ||
return; | ||
} | ||
data[key].forEach(function (value) { | ||
// ignore undefined values | ||
if (typeof value.value === 'undefined') { | ||
return; | ||
} | ||
|
||
// ignore empty values (unless it's a required field) | ||
if (value.value == '' && REQUIRED_FIELDS.indexOf(key) === -1) { | ||
return; | ||
} | ||
|
||
// ignore empty array values | ||
if (value.value instanceof Array) { | ||
var empty = true; | ||
for (var i = 0; i < value.value.length; i++) { | ||
if (typeof value.value[i] !== 'undefined' && value.value[i] != '') { | ||
empty = false; | ||
break; | ||
} | ||
} | ||
if (empty) { | ||
return; | ||
} | ||
} | ||
line = ''; | ||
|
||
// add namespace if exists | ||
if (value.namespace) { | ||
line += value.namespace + '.'; | ||
} | ||
line += key.indexOf('X-') === 0 ? key : key.toUpperCase(); | ||
|
||
// add meta properties | ||
if (typeof value.meta === 'object') { | ||
Object.keys(value.meta).forEach(function (metaKey) { | ||
// values of meta tags must be an array | ||
if (typeof value.meta[metaKey].forEach !== 'function') { | ||
return; | ||
} | ||
value.meta[metaKey].forEach(function (metaValue) { | ||
if (metaKey.length > 0) { | ||
if (metaKey.toUpperCase() === 'TYPE') { | ||
// Do not escape the comma when it is the type property. This breaks a lot. | ||
line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeTypeCharacters(metaValue); | ||
} else { | ||
line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeCharacters(metaValue); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
line += ':'; | ||
|
||
|
||
|
||
if (typeof value.value === 'string') { | ||
line += escapeCharacters(value.value); | ||
} else { | ||
// list-values | ||
var separator = COMMA_SEPARATED_FIELDS.indexOf(key) !== -1 | ||
? ',' | ||
: ';'; | ||
line += value.value.map(function (item) { | ||
return escapeCharacters(item); | ||
}).join(separator); | ||
} | ||
|
||
// line-length limit. Content lines | ||
// SHOULD be folded to a maximum width of 75 octets, excluding the line break. | ||
if (line.length > 75) { | ||
var firstChunk = line.substr(0, 75), | ||
least = line.substr(75); | ||
var splitted = least.match(/.{1,74}/g); | ||
lines.push(firstChunk); | ||
splitted.forEach(function (chunk) { | ||
lines.push(' ' + chunk); | ||
}); | ||
} else { | ||
lines.push(line); | ||
} | ||
}); | ||
}); | ||
|
||
lines.push(POSTFIX); | ||
return lines.join('\r\n'); | ||
} | ||
return { | ||
parse: parse, | ||
generate: generate | ||
}; | ||
})(); |
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