@ymchun/ics

A ics file parser and generator based on RFC-5545

Usage no npm install needed!

<script type="module">
  import ymchunIcs from 'https://cdn.skypack.dev/@ymchun/ics';
</script>

README

@ymchun/ics

Build Status Coverage Status Known Vulnerabilities npm version

UNDER ACTIVE DEVELOPMENT, MAY HAVE BREAKING CHANGES

Install

$ npm i @ymchun/ics

Basic Usage

import { Parser, ParserOptions } from '@ymchun/ics';

const icsStr = '......'; // your ics file content
const opts: ParserOptions = {
    // whether the parser should emit warning message
    quiet: true,
};

// 'calendars' is array of v-calendar components
const calendars = new Parser(opts).parse(icsStr);
// convert Vcalendar back to ics file
const ics = convertToIcs(calendar[0].getICSTokens());

Extending components and properties

More components and properties can be supported by extending Component and Property classes.

Also, your custom components and properties should implements ComponentImpl and PropertyImpl<T>.

For PropertyImpl<T>, the generic type refer to the type of your property value.

Define your custom property with parameters

class MyCustomPropertyA extends Property implements PropertyImpl<TextValue> {
    // type should match your property key when feeding into the parser options
    public type = 'MyCustomPropertyA'; // <-- property key, mandatory
    public value!: TextValue; // <-- property value, mandatory
    public parameters = {
        // property parameters
        Encoding: null as TextValue | null,
        MyCustomParam: null as MyCustomParam | null,
    };

    // set property value
    public setValue(value: string): this {
        // your property should use the 'token' property from parent class to set the value
        // since properties may have different value type, so this method is where you parse your own value
        // 'token' will become null after this method is called
        this.value = new BinaryValue().setValue(this.token.value);
        return this;
    }

    // set property parameters
    public setParameter(type: string, value: string): this {
        switch (param.name) {
            case PARAMETER.Encoding:
                this.parameters.Encoding = new TextValue().setValue(param.value);
                break;
            case PARAMETER.MyCustomParam:
                this.parameters.MyCustomParam = new MyCustomParam().setValue(param.value);
                break;
        }
        return this;
    }

    // toString convert property into ics string
    public toString(): string {
        const paramStr = propertyParameterToString(this.parameters);
        return foldLine(`${this.type}${paramStr}:${this.value.toString()}`);
    }
}

Define your custom property without parameters

class MyCustomPropertyB extends Property implements PropertyImpl<DateValue> {
    // type should match your property key when feeding into the parser options
    public type = 'MyCustomPropertyB'; // <-- property key, mandatory
    public value!: DateValue; // <-- property value, mandatory

    // this method should be implemented
    public setValue(value: string): this {
        // or if your property value is a Date object
        this.value = new DateValue().setValue(this.token.value);
        return this;
    }

    // toString convert property into ics string
    public toString(): string {
        return foldLine(`${this.type}:${this.value.toString()}`);
    }
}

Define your custom component

class MyCustomComponent extends Component implements ComponentImpl {
    // type should match your component key when feeding into the parser options
    public type = 'MyCustomComponent'; // <-- component key, mandatory

    public myCustomPropertyA: MyCustomPropertyA; // <-- use 'MyCustomPropertyA' here
    public myCustomPropertyB: MyCustomPropertyB; // <-- use 'MyCustomPropertyB' here

    public childComponentTypeA: ComponentTypeA;
    public childComponentTypeB: ComponentTypeB;

    public propertyTypeA: PropertyTypeA;
    public propertyTypeB: PropertyTypeB;
    public propertyTypeC: PropertyTypeC;

    // this method should be implemented when your component contains other components
    public setComponent(component: Component): this {
        switch (component.type) {
            case 'TypeA':
                this.daylight = component as ComponentTypeA;
                break;
            case 'TypeB':
                this.daylight = component as ComponentTypeB;
                break;
        }
        return this;
    }

    // this method should be implemented when your component has properties
    public setProperty(property: Property): this {
        switch (property.type) {
            case 'TypeA':
                this.propertyTypeA = property as PropertyTypeA;
                break;
            case 'TypeB':
                this.propertyTypeB = property as PropertyTypeB;
                break;
            case 'TypeC':
                this.propertyTypeC = property as PropertyTypeC;
                break;
            // property key --v
            case 'MyCustomPropertyA':
                this.myCustomPropertyA = property as MyCustomPropertyA;
                break;
            case 'MyCustomPropertyB':
                this.myCustomPropertyB = property as MyCustomPropertyB;
                break;
        }
        return this;
    }

    // toString convert component into ics string
    public getICSTokens(): ConvertToICS {
        // result
        const payload: ConvertToICS = {
            children: [],
            type: this.type,
        };

        // push properties
        if (this.myCustomPropertyA) {
            payload.children.push(this.myCustomPropertyA.toString());
        }
        if (this.myCustomPropertyB) {
            payload.children.push(this.myCustomPropertyB.toString());
        }
        if (this.propertyTypeA) {
            payload.children.push(this.propertyTypeA.toString());
        }
        if (this.propertyTypeB) {
            payload.children.push(this.propertyTypeB.toString());
        }
        if (this.propertyTypeC) {
            payload.children.push(this.propertyTypeC.toString());
        }

        // push components
        if (this.childComponentTypeA) {
            payload.children.push(this.childComponentTypeA.getICSTokens());
        }
        if (this.childComponentTypeB) {
            payload.children.push(this.childComponentTypeB.getICSTokens());
        }

        return payload;
    }
}

Extending existing component

class MyExtendedVCalendar extends VCalendar implements ComponentImpl {
    public myCustomProperty: MyCustomProperty; // <-- use 'MyCustomProperty' here

    // this method should be implemented when your component has properties
    public setProperty(property: Property): this {
        switch (property.type) {
            case 'MyCustomProperty':
                this.myCustomProperty = property as MyCustomProperty;
                break;
            default:
                // you should call parent class 'setProperty' method to avoid missing parent properties
                super.setProperty(property);
        }
        return this;
    }

    // toString convert component into ics string
    public getICSTokens(): ConvertToICS {
        // you should call the parent get tokens first
        const payload = super.getICSTokens();

        // then append your custom properties
        if (this.myCustomProperty) {
            payload.children.push(this.myCustomProperty.toString());
        }

        return payload;
    }
}

Register your custom components and properties via options

const icsStr = '......'; // your ics file content
const opts: ParserOptions = {
    components: {
        // the key should match the ics 'BEGIN:xxx' and 'END:xxx'
        // i.e. when parser meet 'BEGIN:MyCustomComponent', it will use this component.
        // v-- component key
        MyCustomComponent: MyCustomComponent,
        // overriding the default vCalendar component
        [COMPONENT.Calendar]: MyExtendedVCalendar,
    },
    properties: {
        // the key should match the ics 'MyCustomPropertyA:xxx' component property
        // v-- property key
        MyCustomPropertyA: MyCustomPropertyA,
        MyCustomPropertyB: MyCustomPropertyB,
    },
};

// parse icsStr into VCalendar object
const calendars = new Parser(opts).parse(icsStr);