view sxe-document.js @ 6:24aa8dccb170

Make XMPP actually work.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Tue, 31 Jan 2012 15:59:28 +0100
parents 03ef53b969bd
children 7b2ca4d5af6d
line wrap: on
line source

'use strict';

/** Copyright (c) 2012 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
  * deal in the Software without restriction, including without limitation the
  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  * sell copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  * IN THE SOFTWARE.
  */

var Document = function(initiator, name, host, domId, prolog) {
  this.initiator = initiator;
  this.name = name;
  this.host = host;

  this.prolog = prolog || 'data:application/xhtml+xml,%3C%3Fxml%20version%3D%271.0%27%3F%3E%0A%3C%21DOCTYPE%20html%3E%0A';
  this.state = 'not-started';
  this.records = {};
  this.dom = document.getElementById(domId);
};

Document.prototype = {
  add: function(jid, child) {
    var record = new Record(jid, child);

    if (record.rid in this.records)
      console.log('duplicate new');

    this.records[record.rid] = record;
    record.toDOM(this.records, this.dom);
  },
  update: function(jid, child) {
    var target = child.getAttributeNS(null, 'target');

    if (!(target in this.records))
      return; // Ignore it.

    var record = this.records[target];
    console.log(record, child);
    child.setAttributeNS(null, 'rid', target);

    var version = +child.getAttributeNS(null, 'version');
    if (this.state === 'getting-state')
      record.version = version;
    else
      record.version++;

    if (record.version === version) {
      var type = child.getAttributeNS(null, 'type');
      if (type === 'text' || type === 'attr' || type === 'comment') {
        var chdata = child.getAttributeNS(null, 'chdata');
        var replacefrom = +child.getAttributeNS(null, 'replacefrom');
        var replacen = +child.getAttributeNS(null, 'replacen');
        if (chdata && replacefrom && replacen) {
          var string = record.chdata.substr(0, replacefrom);
          string += chdata;
          string += record.chdata.substr(replacefrom + replacen);
          child.removeAttributeNS(null, 'replacefrom');
          child.removeAttributeNS(null, 'replacen');
          child.setAttributeNS(null, 'chdata', string);
        }
      }
      record.update(jid, child);
      record.toDOM(this.records, this.dom);
    } else
      ; // Not sure I understand correctly.
  },
  remove: function(jid, child) {
    var rid = child.getAttributeNS(null, 'target');
    this.records[rid].remove(this.records);
    delete this.records[rid];
  },
  processState: function(jid, elements) {
    var i = 0;
    var first = elements[0];
    if (first.localName === 'document-begin') {
      if (this.state !== 'not-started')
        return; //TODO: the session has already started.
      i = 1;
      this.prolog = first.getAttributeNS(null, 'prolog');
      this.state = 'getting-session';
    }

    //TODO: if the session isn’t started, should ignore changes?

    for (; i < elements.length; i++) {
      var child = elements[i];
      var change = child.localName;

      switch (change) {
        case 'new':
          this.add(jid, child);
          break;
        case 'set':
          this.update(jid, child);
          break;
        case 'remove':
          this.remove(jid, child);
          break;
        case 'document-end':
          this.state = 'started';
          break;
      }
    }
  },
  createState: function(state, root, parent) {
    if (!root)
      root = this.dom;
    var children = root.childNodes;

    for (var i = 0; i < children.length; i++) {
      var child = children[i];
      var element = document.createElementNS(Lightstring.NS.sxe, 'new');
      var rid = Lightstring.newId('GUID');
      element.setAttributeNS(null, 'rid', rid);

      if (parent)
        element.setAttributeNS(null, 'parent', parent);

      switch (child.nodeType) {
        case 1:
          element.setAttributeNS(null, 'type', 'element');
          element.setAttributeNS(null, 'ns', child.namespaceURI);
          element.setAttributeNS(null, 'name', child.localName);
          state.push(element);

          //TODO: move that elsewhere, or make it prettier.
          var convertAttr = function(attr) {
            var element = document.createElementNS(Lightstring.NS.sxe, 'new');
            element.setAttributeNS(null, 'type', 'attr');
            var arid = Lightstring.newId('GUID');
            element.setAttributeNS(null, 'rid', arid);
            element.setAttributeNS(null, 'parent', rid);
            if (attr.namespaceURI)
              element.setAttributeNS(null, 'ns', attr.namespaceURI);
            element.setAttributeNS(null, 'name', attr.localName);
            element.setAttributeNS(null, 'chdata', attr.textContent);
            state.push(element);
          };

          for (var j = 0; j < child.attributes.length; j++)
            convertAttr(child.attributes[j]);

          state = this.createState(state, child, rid);
          break;

        case 3:
          element.setAttributeNS(null, 'type', 'text');
          element.setAttributeNS(null, 'chdata', child.textContent);
          state.push(element);
          break;

        case 7:
          element.setAttributeNS(null, 'type', 'processinginstruction');
          element.setAttributeNS(null, 'pitarget', child.target);
          element.setAttributeNS(null, 'pidata', child.data);
          state.push(element);
          break;

        case 8:
          element.setAttributeNS(null, 'type', 'comment');
          element.setAttributeNS(null, 'chdata', child.textContent);
          state.push(element);
          break;
      }
    }
    return state;
  },
  empty: function() {
    var children = this.dom.childNodes;
    for (var i = children.length - 1; i >= 0; i--)
      this.dom.removeChild(children[i]);
  }
};