aboutsummaryrefslogblamecommitdiffstats
path: root/lib/caldav/sax.js
blob: db3a4866235452835cb65aca3079b640db0c8654 (plain) (tree)
1
2
3
4
5

                       
                                          
 
                                       














                                                  

                   

                

      














                                              

                  
                       


                     

                                                  

             






                                   









                                                        

       
                             

      





                                                 
       
      
 



















                                                               

      

                                

      














                                                          
 
                                       






                         
                                         
                                                       

                                                           


                               
                 



                          
 

                                                 
 
                        
                                       
 
                                           
                                  



                                                 
 



                                      
       
 

                                           

      




                                                     

                                
 
                              
 


                                              

       

                                            
 





                                     



                            
                                        

      









                                                       






                          


                                 
   
(function(module, ns) {

  var Responder = ns.require('responder');

  if (typeof(window) === 'undefined') {
    Parser.sax = require('sax');
  } else {
    Parser.sax = sax;
  }

  /**
   * Creates a parser object.
   *
   * @param {Object} baseHandler base sax handler.
   */
  function Parser(baseHandler) {
    var handler;

    var events = [
      'ontext',
      'onopentag',
      'onclosetag',
      'onerror',
      'onend'
    ];

    if (typeof(baseHandler) !== 'undefined') {
      handler = baseHandler;
    } else {
      handler = ns.require('sax/base');
    }

    this.stack = [];
    this.handles = {};
    this._handlerStack = [];
    this.tagStack = [];
    this.root = this.current = {};

    this.setHandler(handler);

    this._parse = Parser.sax.parser(true, {
      xmlns: true,
      trim: true,
      normalize: false,
      lowercase: true
    });

    events.forEach(function(event) {
      this._parse[event] = this[event].bind(this);
    }, this);

    Responder.call(this);
  }

  Parser.prototype = {

    __proto__: Responder.prototype,

    /**
     * Sets current handler, optionally adding
     * previous one to the handlerStack.
     *
     * @param {Object} handler new handler.
     * @param {Boolean} storeOriginal store old handler?
     */
    setHandler: function(handler, storeOriginal) {
      if (storeOriginal) {
        this._handlerStack.push(this.handler);
      }

      this.handler = handler;
    },

    /**
     * Sets handler to previous one in the stack.
     */
    restoreHandler: function() {
      if (this._handlerStack.length) {
        this.handler = this._handlerStack.pop();
      }
    },

    /**
     * Registers a top level handler
     *
     * @param {String} tag xmlns uri/local tag name for example
     *                     DAV:/a.
     *
     * @param {Object} handler new handler to use when tag is
     *                         triggered.
     */
    registerHandler: function(tag, handler) {
      this.handles[tag] = handler;
    },

    /**
     * Writes data into the parser.
     *
     * @param {String} chunk partial/complete chunk of xml.
     */
    write: function(chunk) {
      return this._parse.write(chunk);
    },

    get closed() {
      return this._parse.closed;
    },

    /**
     * Determines if given tagSpec has a specific handler.
     *
     * @param {String} tagSpec usual tag spec.
     */
    getHandler: function(tagSpec) {
      var handler;
      var handlers = this.handler.handles;

      if (!handlers) {
        handlers = this.handles;
      }

      if (tagSpec in handlers) {
        handler = handlers[tagSpec];

        if (handler !== this.handler) {
          return handler;
        }
      }

      return false;
    },

    _fireHandler: function(event, data) {
      if (typeof(this.handler[event]) === 'function') {
        this.handler[event].call(this, data, this.handler);
      }
    },

    onopentag: function(data) {
      var handle;
      var stackData = {
        local: data.local,
        name: data.name
      };

      //build tagSpec for others to use.
      data.tagSpec = data.uri + '/' + data.local;

      //add to stackData
      stackData.tagSpec = data.tagSpec;

      // shortcut to the current tag object
      this.currentTag = stackData;

      //determine if we need to switch to another
      //handler object.
      handle = this.getHandler(data.tagSpec);

      if (handle) {
        //switch to new handler object
        this.setHandler(handle, true);
        stackData.handler = handle;
      }

      this.tagStack.push(stackData);
      this._fireHandler('onopentag', data);
    },

    //XXX: optimize later
    get currentTag() {
      return this.tagStack[this.tagStack.length - 1];
    },

    onclosetag: function(data) {
      var stack, handler;

      stack = this.currentTag;

      if (stack.handler) {
        //fire oncomplete handler if available
        this._fireHandler('oncomplete');
      }

      //fire the onclosetag event
      this._fireHandler('onclosetag', data);

      if (stack.handler) {
        //restore previous handler
        this.restoreHandler();
      }

      //actually remove the stack tag
      this.tagStack.pop();
    },

    ontext: function(data) {
      this._fireHandler('ontext', data);
    },

    onerror: function(data) {
      //TODO: XXX implement handling of parsing errors.
      //unlikely but possible if server goes down
      //or there is some authentication issue that
      //we miss.
      this._fireHandler('onerror', data);
    },

    onend: function() {
      this._fireHandler('onend', this.root);
    }
  };

  module.exports = Parser;

}.apply(
  this,
  (this.Caldav) ?
    [Caldav('sax'), Caldav] :
    [module, require('./caldav')]
));