component_parser.js

/* 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 ICALParse from "./parse.js";
import Component from "./component.js";
import Event from "./event.js";
import Timezone from "./timezone.js";

/**
 * The ComponentParser is used to process a String or jCal Object,
 * firing callbacks for various found components, as well as completion.
 *
 * @example
 * var options = {
 *   // when false no events will be emitted for type
 *   parseEvent: true,
 *   parseTimezone: true
 * };
 *
 * var parser = new ICAL.ComponentParser(options);
 *
 * parser.onevent(eventComponent) {
 *   //...
 * }
 *
 * // ontimezone, etc...
 *
 * parser.oncomplete = function() {
 *
 * };
 *
 * parser.process(stringOrComponent);
 *
 * @memberof ICAL
 */
class ComponentParser {
  /**
   * Creates a new ICAL.ComponentParser instance.
   *
   * @param {Object=} options                   Component parser options
   * @param {Boolean} options.parseEvent        Whether events should be parsed
   * @param {Boolean} options.parseTimezeone    Whether timezones should be parsed
   */
  constructor(options) {
    if (typeof(options) === 'undefined') {
      options = {};
    }

    for (let [key, value] of Object.entries(options)) {
      this[key] = value;
    }
  }

  /**
   * When true, parse events
   *
   * @type {Boolean}
   */
  parseEvent = true;

  /**
   * When true, parse timezones
   *
   * @type {Boolean}
   */
  parseTimezone = true;


  /* SAX like events here for reference */

  /**
   * Fired when parsing is complete
   * @callback
   */
  oncomplete = /* c8 ignore next */ function() {};

  /**
   * Fired if an error occurs during parsing.
   *
   * @callback
   * @param {Error} err details of error
   */
  onerror = /* c8 ignore next */ function(err) {};

  /**
   * Fired when a top level component (VTIMEZONE) is found
   *
   * @callback
   * @param {Timezone} component     Timezone object
   */
  ontimezone = /* c8 ignore next */ function(component) {};

  /**
   * Fired when a top level component (VEVENT) is found.
   *
   * @callback
   * @param {Event} component    Top level component
   */
  onevent = /* c8 ignore next */ function(component) {};

  /**
   * Process a string or parse ical object.  This function itself will return
   * nothing but will start the parsing process.
   *
   * Events must be registered prior to calling this method.
   *
   * @param {Component|String|Object} ical      The component to process,
   *        either in its final form, as a jCal Object, or string representation
   */
  process(ical) {
    //TODO: this is sync now in the future we will have a incremental parser.
    if (typeof(ical) === 'string') {
      ical = ICALParse(ical);
    }

    if (!(ical instanceof Component)) {
      ical = new Component(ical);
    }

    let components = ical.getAllSubcomponents();
    let i = 0;
    let len = components.length;
    let component;

    for (; i < len; i++) {
      component = components[i];

      switch (component.name) {
        case 'vtimezone':
          if (this.parseTimezone) {
            let tzid = component.getFirstPropertyValue('tzid');
            if (tzid) {
              this.ontimezone(new Timezone({
                tzid: tzid,
                component: component
              }));
            }
          }
          break;
        case 'vevent':
          if (this.parseEvent) {
            this.onevent(new Event(component));
          }
          break;
        default:
          continue;
      }
    }

    //XXX: ideally we should do a "nextTick" here
    //     so in all cases this is actually async.
    this.oncomplete();
  }
}
export default ComponentParser;