Auto backup before evolver run - 2026年 03月 06日 星期五 19:00:05 CST
This commit is contained in:
+439
@@ -0,0 +1,439 @@
|
||||
/* 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 */
|
||||
|
||||
const NAME_INDEX = 0;
|
||||
const PROP_INDEX = 1;
|
||||
const TYPE_INDEX = 2;
|
||||
const VALUE_INDEX = 3;
|
||||
|
||||
import design from "./design.js";
|
||||
import ICALStringify from "./stringify.js";
|
||||
import ICALParse from "./parse.js";
|
||||
|
||||
// needed for typescript type resolution
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Component from "./component.js";
|
||||
// needed for typescript type resolution
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Duration from "./duration.js";
|
||||
// needed for typescript type resolution
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import UtcOffset from "./utc_offset.js";
|
||||
// needed for typescript type resolution
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Binary from "./binary.js";
|
||||
// needed for typescript type resolution
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Period from "./period.js";
|
||||
// needed for typescript type resolution
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Recur from "./recur.js";
|
||||
// needed for typescript type resolution
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Time from "./time.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
|
||||
* @typedef {import("./types.js").Geo} Geo
|
||||
* Imports the 'Geo' type from the "types.js" module
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a layer on top of the raw jCal object for manipulating a single property, with its
|
||||
* parameters and value.
|
||||
*
|
||||
* @memberof ICAL
|
||||
*/
|
||||
class Property {
|
||||
/**
|
||||
* Create an {@link ICAL.Property} by parsing the passed iCalendar string.
|
||||
*
|
||||
* @param {String} str The iCalendar string to parse
|
||||
* @param {designSet=} designSet The design data to use for this property
|
||||
* @return {Property} The created iCalendar property
|
||||
*/
|
||||
static fromString(str, designSet) {
|
||||
return new Property(ICALParse.property(str, designSet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ICAL.Property instance.
|
||||
*
|
||||
* It is important to note that mutations done in the wrapper directly mutate the jCal object used
|
||||
* to initialize.
|
||||
*
|
||||
* Can also be used to create new properties by passing the name of the property (as a String).
|
||||
*
|
||||
* @param {Array|String} jCal Raw jCal representation OR the new name of the property
|
||||
* @param {Component=} parent Parent component
|
||||
*/
|
||||
constructor(jCal, parent) {
|
||||
this._parent = parent || null;
|
||||
|
||||
if (typeof(jCal) === 'string') {
|
||||
// We are creating the property by name and need to detect the type
|
||||
this.jCal = [jCal, {}, design.defaultType];
|
||||
this.jCal[TYPE_INDEX] = this.getDefaultType();
|
||||
} else {
|
||||
this.jCal = jCal;
|
||||
}
|
||||
this._updateType();
|
||||
}
|
||||
|
||||
/**
|
||||
* The value type for this property
|
||||
* @type {String}
|
||||
*/
|
||||
get type() {
|
||||
return this.jCal[TYPE_INDEX];
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this property, in lowercase.
|
||||
* @type {String}
|
||||
*/
|
||||
get name() {
|
||||
return this.jCal[NAME_INDEX];
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent component for this property.
|
||||
* @type {Component}
|
||||
*/
|
||||
get parent() {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
set parent(p) {
|
||||
// Before setting the parent, check if the design set has changed. If it
|
||||
// has, we later need to update the type if it was unknown before.
|
||||
let designSetChanged = !this._parent || (p && p._designSet != this._parent._designSet);
|
||||
|
||||
this._parent = p;
|
||||
|
||||
if (this.type == design.defaultType && designSetChanged) {
|
||||
this.jCal[TYPE_INDEX] = this.getDefaultType();
|
||||
this._updateType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The design set for this property, e.g. icalendar vs vcard
|
||||
*
|
||||
* @type {designSet}
|
||||
* @private
|
||||
*/
|
||||
get _designSet() {
|
||||
return this.parent ? this.parent._designSet : design.defaultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the type metadata from the current jCal type and design set.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_updateType() {
|
||||
let designSet = this._designSet;
|
||||
|
||||
if (this.type in designSet.value) {
|
||||
if ('decorate' in designSet.value[this.type]) {
|
||||
this.isDecorated = true;
|
||||
} else {
|
||||
this.isDecorated = false;
|
||||
}
|
||||
|
||||
if (this.name in designSet.property) {
|
||||
this.isMultiValue = ('multiValue' in designSet.property[this.name]);
|
||||
this.isStructuredValue = ('structuredValue' in designSet.property[this.name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate a single value. The act of hydrating means turning the raw jCal
|
||||
* value into a potentially wrapped object, for example {@link ICAL.Time}.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} index The index of the value to hydrate
|
||||
* @return {?Object} The decorated value.
|
||||
*/
|
||||
_hydrateValue(index) {
|
||||
if (this._values && this._values[index]) {
|
||||
return this._values[index];
|
||||
}
|
||||
|
||||
// for the case where there is no value.
|
||||
if (this.jCal.length <= (VALUE_INDEX + index)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isDecorated) {
|
||||
if (!this._values) {
|
||||
this._values = [];
|
||||
}
|
||||
return (this._values[index] = this._decorate(
|
||||
this.jCal[VALUE_INDEX + index]
|
||||
));
|
||||
} else {
|
||||
return this.jCal[VALUE_INDEX + index];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate a single value, returning its wrapped object. This is used by
|
||||
* the hydrate function to actually wrap the value.
|
||||
*
|
||||
* @private
|
||||
* @param {?} value The value to decorate
|
||||
* @return {Object} The decorated value
|
||||
*/
|
||||
_decorate(value) {
|
||||
return this._designSet.value[this.type].decorate(value, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undecorate a single value, returning its raw jCal data.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} value The value to undecorate
|
||||
* @return {?} The undecorated value
|
||||
*/
|
||||
_undecorate(value) {
|
||||
return this._designSet.value[this.type].undecorate(value, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value at the given index while also hydrating it. The passed
|
||||
* value can either be a decorated or undecorated value.
|
||||
*
|
||||
* @private
|
||||
* @param {?} value The value to set
|
||||
* @param {Number} index The index to set it at
|
||||
*/
|
||||
_setDecoratedValue(value, index) {
|
||||
if (!this._values) {
|
||||
this._values = [];
|
||||
}
|
||||
|
||||
if (typeof(value) === 'object' && 'icaltype' in value) {
|
||||
// decorated value
|
||||
this.jCal[VALUE_INDEX + index] = this._undecorate(value);
|
||||
this._values[index] = value;
|
||||
} else {
|
||||
// undecorated value
|
||||
this.jCal[VALUE_INDEX + index] = value;
|
||||
this._values[index] = this._decorate(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a parameter on the property.
|
||||
*
|
||||
* @param {String} name Parameter name (lowercase)
|
||||
* @return {Array|String} Parameter value
|
||||
*/
|
||||
getParameter(name) {
|
||||
if (name in this.jCal[PROP_INDEX]) {
|
||||
return this.jCal[PROP_INDEX][name];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets first parameter on the property.
|
||||
*
|
||||
* @param {String} name Parameter name (lowercase)
|
||||
* @return {String} Parameter value
|
||||
*/
|
||||
getFirstParameter(name) {
|
||||
let parameters = this.getParameter(name);
|
||||
|
||||
if (Array.isArray(parameters)) {
|
||||
return parameters[0];
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a parameter on the property.
|
||||
*
|
||||
* @param {String} name The parameter name
|
||||
* @param {Array|String} value The parameter value
|
||||
*/
|
||||
setParameter(name, value) {
|
||||
let lcname = name.toLowerCase();
|
||||
if (typeof value === "string" &&
|
||||
lcname in this._designSet.param &&
|
||||
'multiValue' in this._designSet.param[lcname]) {
|
||||
value = [value];
|
||||
}
|
||||
this.jCal[PROP_INDEX][name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a parameter
|
||||
*
|
||||
* @param {String} name The parameter name
|
||||
*/
|
||||
removeParameter(name) {
|
||||
delete this.jCal[PROP_INDEX][name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default type based on this property's name.
|
||||
*
|
||||
* @return {String} The default type for this property
|
||||
*/
|
||||
getDefaultType() {
|
||||
let name = this.jCal[NAME_INDEX];
|
||||
let designSet = this._designSet;
|
||||
|
||||
if (name in designSet.property) {
|
||||
let details = designSet.property[name];
|
||||
if ('defaultType' in details) {
|
||||
return details.defaultType;
|
||||
}
|
||||
}
|
||||
return design.defaultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type of property and clears out any existing values of the current
|
||||
* type.
|
||||
*
|
||||
* @param {String} type New iCAL type (see design.*.values)
|
||||
*/
|
||||
resetType(type) {
|
||||
this.removeAllValues();
|
||||
this.jCal[TYPE_INDEX] = type;
|
||||
this._updateType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first property value.
|
||||
*
|
||||
* @return {Binary | Duration | Period |
|
||||
* Recur | Time | UtcOffset | Geo | string | null} First property value
|
||||
*/
|
||||
getFirstValue() {
|
||||
return this._hydrateValue(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all values on the property.
|
||||
*
|
||||
* NOTE: this creates an array during each call.
|
||||
*
|
||||
* @return {Array} List of values
|
||||
*/
|
||||
getValues() {
|
||||
let len = this.jCal.length - VALUE_INDEX;
|
||||
|
||||
if (len < 1) {
|
||||
// it is possible for a property to have no value.
|
||||
return [];
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let result = [];
|
||||
|
||||
for (; i < len; i++) {
|
||||
result[i] = this._hydrateValue(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all values from this property
|
||||
*/
|
||||
removeAllValues() {
|
||||
if (this._values) {
|
||||
this._values.length = 0;
|
||||
}
|
||||
this.jCal.length = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values of the property. Will overwrite the existing values.
|
||||
* This can only be used for multi-value properties.
|
||||
*
|
||||
* @param {Array} values An array of values
|
||||
*/
|
||||
setValues(values) {
|
||||
if (!this.isMultiValue) {
|
||||
throw new Error(
|
||||
this.name + ': does not not support mulitValue.\n' +
|
||||
'override isMultiValue'
|
||||
);
|
||||
}
|
||||
|
||||
let len = values.length;
|
||||
let i = 0;
|
||||
this.removeAllValues();
|
||||
|
||||
if (len > 0 &&
|
||||
typeof(values[0]) === 'object' &&
|
||||
'icaltype' in values[0]) {
|
||||
this.resetType(values[0].icaltype);
|
||||
}
|
||||
|
||||
if (this.isDecorated) {
|
||||
for (; i < len; i++) {
|
||||
this._setDecoratedValue(values[i], i);
|
||||
}
|
||||
} else {
|
||||
for (; i < len; i++) {
|
||||
this.jCal[VALUE_INDEX + i] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current value of the property. If this is a multi-value
|
||||
* property, all other values will be removed.
|
||||
*
|
||||
* @param {String|Object} value New property value.
|
||||
*/
|
||||
setValue(value) {
|
||||
this.removeAllValues();
|
||||
if (typeof(value) === 'object' && 'icaltype' in value) {
|
||||
this.resetType(value.icaltype);
|
||||
}
|
||||
|
||||
if (this.isDecorated) {
|
||||
this._setDecoratedValue(value, 0);
|
||||
} else {
|
||||
this.jCal[VALUE_INDEX] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Object representation of this component. The returned object
|
||||
* is a live jCal object and should be cloned if modified.
|
||||
* @return {Object}
|
||||
*/
|
||||
toJSON() {
|
||||
return this.jCal;
|
||||
}
|
||||
|
||||
/**
|
||||
* The string representation of this component.
|
||||
* @return {String}
|
||||
*/
|
||||
toICALString() {
|
||||
return ICALStringify.property(
|
||||
this.jCal, this._designSet, true
|
||||
);
|
||||
}
|
||||
}
|
||||
export default Property;
|
||||
Reference in New Issue
Block a user