Auto backup before evolver run - 2026年 03月 06日 星期五 19:00:05 CST
This commit is contained in:
+302
@@ -0,0 +1,302 @@
|
||||
/* 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://mozilla.org/MPL/2.0/.
|
||||
* Portions Copyright (C) Philipp Kewisch */
|
||||
|
||||
import design from "./design.js";
|
||||
import { foldline } from "./helpers.js";
|
||||
|
||||
/**
|
||||
* This lets typescript resolve our custom types in the
|
||||
* generated d.ts files (jsdoc typedefs are converted to typescript types).
|
||||
* Ignore prevents the typedefs from being documented more than once.
|
||||
*
|
||||
* @ignore
|
||||
* @typedef {import("./types.js").designSet} designSet
|
||||
* Imports the 'designSet' type from the "types.js" module
|
||||
*/
|
||||
|
||||
const LINE_ENDING = '\r\n';
|
||||
const DEFAULT_VALUE_TYPE = 'unknown';
|
||||
const RFC6868_REPLACE_MAP = { '"': "^'", "\n": "^n", "^": "^^" };
|
||||
|
||||
/**
|
||||
* Convert a full jCal/jCard array into a iCalendar/vCard string.
|
||||
*
|
||||
* @function ICAL.stringify
|
||||
* @variation function
|
||||
* @param {Array} jCal The jCal/jCard document
|
||||
* @return {String} The stringified iCalendar/vCard document
|
||||
*/
|
||||
export default function stringify(jCal) {
|
||||
if (typeof jCal[0] == "string") {
|
||||
// This is a single component
|
||||
jCal = [jCal];
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let len = jCal.length;
|
||||
let result = '';
|
||||
|
||||
for (; i < len; i++) {
|
||||
result += stringify.component(jCal[i]) + LINE_ENDING;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an jCal component array into a ICAL string.
|
||||
* Recursive will resolve sub-components.
|
||||
*
|
||||
* Exact component/property order is not saved all
|
||||
* properties will come before subcomponents.
|
||||
*
|
||||
* @function ICAL.stringify.component
|
||||
* @param {Array} component
|
||||
* jCal/jCard fragment of a component
|
||||
* @param {designSet} designSet
|
||||
* The design data to use for this component
|
||||
* @return {String} The iCalendar/vCard string
|
||||
*/
|
||||
stringify.component = function(component, designSet) {
|
||||
let name = component[0].toUpperCase();
|
||||
let result = 'BEGIN:' + name + LINE_ENDING;
|
||||
|
||||
let props = component[1];
|
||||
let propIdx = 0;
|
||||
let propLen = props.length;
|
||||
|
||||
let designSetName = component[0];
|
||||
// rfc6350 requires that in vCard 4.0 the first component is the VERSION
|
||||
// component with as value 4.0, note that 3.0 does not have this requirement.
|
||||
if (designSetName === 'vcard' && component[1].length > 0 &&
|
||||
!(component[1][0][0] === "version" && component[1][0][3] === "4.0")) {
|
||||
designSetName = "vcard3";
|
||||
}
|
||||
designSet = designSet || design.getDesignSet(designSetName);
|
||||
|
||||
for (; propIdx < propLen; propIdx++) {
|
||||
result += stringify.property(props[propIdx], designSet) + LINE_ENDING;
|
||||
}
|
||||
|
||||
// Ignore subcomponents if none exist, e.g. in vCard.
|
||||
let comps = component[2] || [];
|
||||
let compIdx = 0;
|
||||
let compLen = comps.length;
|
||||
|
||||
for (; compIdx < compLen; compIdx++) {
|
||||
result += stringify.component(comps[compIdx], designSet) + LINE_ENDING;
|
||||
}
|
||||
|
||||
result += 'END:' + name;
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a single jCal/jCard property to a iCalendar/vCard string.
|
||||
*
|
||||
* @function ICAL.stringify.property
|
||||
* @param {Array} property
|
||||
* jCal/jCard property array
|
||||
* @param {designSet} designSet
|
||||
* The design data to use for this property
|
||||
* @param {Boolean} noFold
|
||||
* If true, the line is not folded
|
||||
* @return {String} The iCalendar/vCard string
|
||||
*/
|
||||
stringify.property = function(property, designSet, noFold) {
|
||||
let name = property[0].toUpperCase();
|
||||
let jsName = property[0];
|
||||
let params = property[1];
|
||||
|
||||
if (!designSet) {
|
||||
designSet = design.defaultSet;
|
||||
}
|
||||
|
||||
let groupName = params.group;
|
||||
let line;
|
||||
if (designSet.propertyGroups && groupName) {
|
||||
line = groupName.toUpperCase() + "." + name;
|
||||
} else {
|
||||
line = name;
|
||||
}
|
||||
|
||||
for (let [paramName, value] of Object.entries(params)) {
|
||||
if (designSet.propertyGroups && paramName == 'group') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let paramDesign = designSet.param[paramName];
|
||||
let multiValue = paramDesign && paramDesign.multiValue;
|
||||
if (multiValue && Array.isArray(value)) {
|
||||
value = value.map(function(val) {
|
||||
val = stringify._rfc6868Unescape(val);
|
||||
val = stringify.paramPropertyValue(val, paramDesign.multiValueSeparateDQuote);
|
||||
return val;
|
||||
});
|
||||
value = stringify.multiValue(value, multiValue, "unknown", null, designSet);
|
||||
} else {
|
||||
value = stringify._rfc6868Unescape(value);
|
||||
value = stringify.paramPropertyValue(value);
|
||||
}
|
||||
|
||||
line += ';' + paramName.toUpperCase() + '=' + value;
|
||||
}
|
||||
|
||||
if (property.length === 3) {
|
||||
// If there are no values, we must assume a blank value
|
||||
return line + ':';
|
||||
}
|
||||
|
||||
let valueType = property[2];
|
||||
|
||||
let propDetails;
|
||||
let multiValue = false;
|
||||
let structuredValue = false;
|
||||
let isDefault = false;
|
||||
|
||||
if (jsName in designSet.property) {
|
||||
propDetails = designSet.property[jsName];
|
||||
|
||||
if ('multiValue' in propDetails) {
|
||||
multiValue = propDetails.multiValue;
|
||||
}
|
||||
|
||||
if (('structuredValue' in propDetails) && Array.isArray(property[3])) {
|
||||
structuredValue = propDetails.structuredValue;
|
||||
}
|
||||
|
||||
if ('defaultType' in propDetails) {
|
||||
if (valueType === propDetails.defaultType) {
|
||||
isDefault = true;
|
||||
}
|
||||
} else {
|
||||
if (valueType === DEFAULT_VALUE_TYPE) {
|
||||
isDefault = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (valueType === DEFAULT_VALUE_TYPE) {
|
||||
isDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
// push the VALUE property if type is not the default
|
||||
// for the current property.
|
||||
if (!isDefault) {
|
||||
// value will never contain ;/:/, so we don't escape it here.
|
||||
line += ';VALUE=' + valueType.toUpperCase();
|
||||
}
|
||||
|
||||
line += ':';
|
||||
|
||||
if (multiValue && structuredValue) {
|
||||
line += stringify.multiValue(
|
||||
property[3], structuredValue, valueType, multiValue, designSet, structuredValue
|
||||
);
|
||||
} else if (multiValue) {
|
||||
line += stringify.multiValue(
|
||||
property.slice(3), multiValue, valueType, null, designSet, false
|
||||
);
|
||||
} else if (structuredValue) {
|
||||
line += stringify.multiValue(
|
||||
property[3], structuredValue, valueType, null, designSet, structuredValue
|
||||
);
|
||||
} else {
|
||||
line += stringify.value(property[3], valueType, designSet, false);
|
||||
}
|
||||
|
||||
return noFold ? line : foldline(line);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles escaping of property values that may contain:
|
||||
*
|
||||
* COLON (:), SEMICOLON (;), or COMMA (,)
|
||||
*
|
||||
* If any of the above are present the result is wrapped
|
||||
* in double quotes.
|
||||
*
|
||||
* @function ICAL.stringify.paramPropertyValue
|
||||
* @param {String} value Raw property value
|
||||
* @param {boolean} force If value should be escaped even when unnecessary
|
||||
* @return {String} Given or escaped value when needed
|
||||
*/
|
||||
stringify.paramPropertyValue = function(value, force) {
|
||||
if (!force &&
|
||||
(value.indexOf(',') === -1) &&
|
||||
(value.indexOf(':') === -1) &&
|
||||
(value.indexOf(';') === -1)) {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return '"' + value + '"';
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an array of ical values into a single
|
||||
* string based on a type and a delimiter value (like ",").
|
||||
*
|
||||
* @function ICAL.stringify.multiValue
|
||||
* @param {Array} values List of values to convert
|
||||
* @param {String} delim Used to join the values (",", ";", ":")
|
||||
* @param {String} type Lowecase ical value type
|
||||
* (like boolean, date-time, etc..)
|
||||
* @param {?String} innerMulti If set, each value will again be processed
|
||||
* Used for structured values
|
||||
* @param {designSet} designSet
|
||||
* The design data to use for this property
|
||||
*
|
||||
* @return {String} iCalendar/vCard string for value
|
||||
*/
|
||||
stringify.multiValue = function(values, delim, type, innerMulti, designSet, structuredValue) {
|
||||
let result = '';
|
||||
let len = values.length;
|
||||
let i = 0;
|
||||
|
||||
for (; i < len; i++) {
|
||||
if (innerMulti && Array.isArray(values[i])) {
|
||||
result += stringify.multiValue(values[i], innerMulti, type, null, designSet, structuredValue);
|
||||
} else {
|
||||
result += stringify.value(values[i], type, designSet, structuredValue);
|
||||
}
|
||||
|
||||
if (i !== (len - 1)) {
|
||||
result += delim;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes a single ical value runs the associated "toICAL" method from the
|
||||
* design value type if available to convert the value.
|
||||
*
|
||||
* @function ICAL.stringify.value
|
||||
* @param {String|Number} value A formatted value
|
||||
* @param {String} type Lowercase iCalendar/vCard value type
|
||||
* (like boolean, date-time, etc..)
|
||||
* @return {String} iCalendar/vCard value for single value
|
||||
*/
|
||||
stringify.value = function(value, type, designSet, structuredValue) {
|
||||
if (type in designSet.value && 'toICAL' in designSet.value[type]) {
|
||||
return designSet.value[type].toICAL(value, structuredValue);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal helper for rfc6868. Exposing this on ICAL.stringify so that
|
||||
* hackers can disable the rfc6868 parsing if the really need to.
|
||||
*
|
||||
* @param {String} val The value to unescape
|
||||
* @return {String} The escaped value
|
||||
*/
|
||||
stringify._rfc6868Unescape = function(val) {
|
||||
return val.replace(/[\n^"]/g, function(x) {
|
||||
return RFC6868_REPLACE_MAP[x];
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user