text: /*
  
   This is a generated file. DO NOT EDIT.
  
   Copyright (C) 2010-2015 KO GmbH <copyright@kogmbh.com>
  
   @licstart
   This file is the compiled version of the WebODF library.
  
   WebODF is free software: you can redistribute it and/or modify it
   under the terms of the GNU Affero General Public License (GNU AGPL)
   as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.
  
   WebODF is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.
  
   You should have received a copy of the GNU Affero General Public License
   along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
   @licend
  
   @source: http://www.webodf.org/
   @source: https://github.com/kogmbh/WebODF/
  */
  var webodf_version = "0.5.10-8-gf5949f3";
  function Runtime() {
  }
  Runtime.prototype.getVariable = function(name) {
  };
  Runtime.prototype.toJson = function(anything) {
  };
  Runtime.prototype.fromJson = function(jsonstr) {
  };
  Runtime.prototype.byteArrayFromString = function(string, encoding) {
  };
  Runtime.prototype.byteArrayToString = function(bytearray, encoding) {
  };
  Runtime.prototype.read = function(path, offset, length, callback) {
  };
  Runtime.prototype.readFile = function(path, encoding, callback) {
  };
  Runtime.prototype.readFileSync = function(path, encoding) {
  };
  Runtime.prototype.loadXML = function(path, callback) {
  };
  Runtime.prototype.writeFile = function(path, data, callback) {
  };
  Runtime.prototype.deleteFile = function(path, callback) {
  };
  Runtime.prototype.log = function(msgOrCategory, msg) {
  };
  Runtime.prototype.setTimeout = function(callback, milliseconds) {
  };
  Runtime.prototype.clearTimeout = function(timeoutID) {
  };
  Runtime.prototype.libraryPaths = function() {
  };
  Runtime.prototype.currentDirectory = function() {
  };
  Runtime.prototype.setCurrentDirectory = function(dir) {
  };
  Runtime.prototype.type = function() {
  };
  Runtime.prototype.getDOMImplementation = function() {
  };
  Runtime.prototype.parseXML = function(xml) {
  };
  Runtime.prototype.exit = function(exitCode) {
  };
  Runtime.prototype.getWindow = function() {
  };
  Runtime.prototype.requestAnimationFrame = function(callback) {
  };
  Runtime.prototype.cancelAnimationFrame = function(requestId) {
  };
  Runtime.prototype.assert = function(condition, message) {
  };
  var IS_COMPILED_CODE = true;
  Runtime.byteArrayToString = function(bytearray, encoding) {
    function byteArrayToString(bytearray) {
      var s = "", i, l = bytearray.length;
      for (i = 0;i < l;i += 1) {
        s += String.fromCharCode(bytearray[i] & 255);
      }
      return s;
    }
    function utf8ByteArrayToString(bytearray) {
      var s = "", startPos, i, l = bytearray.length, chars = [], c0, c1, c2, c3, codepoint;
      if (l >= 3 && bytearray[0] === 239 && bytearray[1] === 187 && bytearray[2] === 191) {
        startPos = 3;
      } else {
        startPos = 0;
      }
      for (i = startPos;i < l;i += 1) {
        c0 = bytearray[i];
        if (c0 < 128) {
          chars.push(c0);
        } else {
          i += 1;
          c1 = bytearray[i];
          if (c0 >= 194 && c0 < 224) {
            chars.push((c0 & 31) << 6 | c1 & 63);
          } else {
            i += 1;
            c2 = bytearray[i];
            if (c0 >= 224 && c0 < 240) {
              chars.push((c0 & 15) << 12 | (c1 & 63) << 6 | c2 & 63);
            } else {
              i += 1;
              c3 = bytearray[i];
              if (c0 >= 240 && c0 < 245) {
                codepoint = (c0 & 7) << 18 | (c1 & 63) << 12 | (c2 & 63) << 6 | c3 & 63;
                codepoint -= 65536;
                chars.push((codepoint >> 10) + 55296, (codepoint & 1023) + 56320);
              }
            }
          }
        }
        if (chars.length >= 1E3) {
          s += String.fromCharCode.apply(null, chars);
          chars.length = 0;
        }
      }
      return s + String.fromCharCode.apply(null, chars);
    }
    var result;
    if (encoding === "utf8") {
      result = utf8ByteArrayToString(bytearray);
    } else {
      if (encoding !== "binary") {
        this.log("Unsupported encoding: " + encoding);
      }
      result = byteArrayToString(bytearray);
    }
    return result;
  };
  Runtime.getVariable = function(name) {
    try {
      return eval(name);
    } catch (e) {
      return undefined;
    }
  };
  Runtime.toJson = function(anything) {
    return JSON.stringify(anything);
  };
  Runtime.fromJson = function(jsonstr) {
    return JSON.parse(jsonstr);
  };
  Runtime.getFunctionName = function getFunctionName(f) {
    var m;
    if (f.name === undefined) {
      m = (new RegExp("function\\s+(\\w+)")).exec(f);
      return m && m[1];
    }
    return f.name;
  };
  Runtime.assert = function(condition, message) {
    if (!condition) {
      this.log("alert", "ASSERTION FAILED:\n" + message);
      throw new Error(message);
    }
  };
  function BrowserRuntime() {
    var self = this;
    function getUtf8LengthForString(string) {
      var l = string.length, i, n, j = 0;
      for (i = 0;i < l;i += 1) {
        n = string.charCodeAt(i);
        j += 1 + (n > 128) + (n > 2048);
        if (n > 55040 && n < 57344) {
          j += 1;
          i += 1;
        }
      }
      return j;
    }
    function utf8ByteArrayFromString(string, length, addBOM) {
      var l = string.length, bytearray, i, n, j;
      bytearray = new Uint8Array(new ArrayBuffer(length));
      if (addBOM) {
        bytearray[0] = 239;
        bytearray[1] = 187;
        bytearray[2] = 191;
        j = 3;
      } else {
        j = 0;
      }
      for (i = 0;i < l;i += 1) {
        n = string.charCodeAt(i);
        if (n < 128) {
          bytearray[j] = n;
          j += 1;
        } else {
          if (n < 2048) {
            bytearray[j] = 192 | n >>> 6;
            bytearray[j + 1] = 128 | n & 63;
            j += 2;
          } else {
            if (n <= 55040 || n >= 57344) {
              bytearray[j] = 224 | n >>> 12 & 15;
              bytearray[j + 1] = 128 | n >>> 6 & 63;
              bytearray[j + 2] = 128 | n & 63;
              j += 3;
            } else {
              i += 1;
              n = (n - 55296 << 10 | string.charCodeAt(i) - 56320) + 65536;
              bytearray[j] = 240 | n >>> 18 & 7;
              bytearray[j + 1] = 128 | n >>> 12 & 63;
              bytearray[j + 2] = 128 | n >>> 6 & 63;
              bytearray[j + 3] = 128 | n & 63;
              j += 4;
            }
          }
        }
      }
      return bytearray;
    }
    function utf8ByteArrayFromXHRString(string, wishLength) {
      var addBOM = false, length = getUtf8LengthForString(string);
      if (typeof wishLength === "number") {
        if (wishLength !== length && wishLength !== length + 3) {
          return undefined;
        }
        addBOM = length + 3 === wishLength;
        length = wishLength;
      }
      return utf8ByteArrayFromString(string, length, addBOM);
    }
    function byteArrayFromString(string) {
      var l = string.length, a = new Uint8Array(new ArrayBuffer(l)), i;
      for (i = 0;i < l;i += 1) {
        a[i] = string.charCodeAt(i) & 255;
      }
      return a;
    }
    this.byteArrayFromString = function(string, encoding) {
      var result;
      if (encoding === "utf8") {
        result = utf8ByteArrayFromString(string, getUtf8LengthForString(string), false);
      } else {
        if (encoding !== "binary") {
          self.log("unknown encoding: " + encoding);
        }
        result = byteArrayFromString(string);
      }
      return result;
    };
    this.byteArrayToString = Runtime.byteArrayToString;
    this.getVariable = Runtime.getVariable;
    this.fromJson = Runtime.fromJson;
    this.toJson = Runtime.toJson;
    function log(msgOrCategory, msg) {
      var category;
      if (msg !== undefined) {
        category = msgOrCategory;
      } else {
        msg = msgOrCategory;
      }
      console.debug(msg);
      if (self.enableAlerts && category === "alert") {
        console.error(msg);
      }
    }
    function arrayToUint8Array(buffer) {
      var l = buffer.length, i, a = new Uint8Array(new ArrayBuffer(l));
      for (i = 0;i < l;i += 1) {
        a[i] = buffer[i];
      }
      return a;
    }
    function stringToBinaryWorkaround(xhr) {
      var cl, data;
      cl = xhr.getResponseHeader("Content-Length");
      if (cl) {
        cl = parseInt(cl, 10);
      }
      if (cl && cl !== xhr.responseText.length) {
        data = utf8ByteArrayFromXHRString(xhr.responseText, cl);
      }
      if (data === undefined) {
        data = byteArrayFromString(xhr.responseText);
      }
      return data;
    }
    function handleXHRResult(path, encoding, xhr) {
      var r, d, a, data;
      if (xhr.status === 0 && !xhr.responseText) {
        r = {err:"File " + path + " is empty.", data:null};
      } else {
        if (xhr.status === 200 || xhr.status === 0) {
          if (xhr.response && typeof xhr.response !== "string") {
            if (encoding === "binary") {
              d = xhr.response;
              data = new Uint8Array(d);
            } else {
              data = String(xhr.response);
            }
          } else {
            if (encoding === "binary") {
              if (xhr.responseBody !== null && String(typeof VBArray) !== "undefined") {
                a = (new VBArray(xhr.responseBody)).toArray();
                data = arrayToUint8Array(a);
              } else {
                data = stringToBinaryWorkaround(xhr);
              }
            } else {
              data = xhr.responseText;
            }
          }
          r = {err:null, data:data};
        } else {
          r = {err:xhr.responseText || xhr.statusText, data:null};
        }
      }
      return r;
    }
    function createXHR(path, encoding, async) {
      var xhr = new XMLHttpRequest;
      xhr.open("GET", path, async);
      if (xhr.overrideMimeType) {
        if (encoding !== "binary") {
          xhr.overrideMimeType("text/plain; charset=" + encoding);
        } else {
          xhr.overrideMimeType("text/plain; charset=x-user-defined");
        }
      }
      return xhr;
    }
    function readFile(path, encoding, callback) {
      var xhr = createXHR(path, encoding, true);
      function handleResult() {
        var r;
        if (xhr.readyState === 4) {
          r = handleXHRResult(path, encoding, xhr);
          callback(r.err, r.data);
        }
      }
      xhr.onreadystatechange = handleResult;
      try {
        xhr.send(null);
      } catch (e) {
        callback(e.message, null);
      }
    }
    function read(path, offset, length, callback) {
      readFile(path, "binary", function(err, result) {
        var r = null;
        if (result) {
          if (typeof result === "string") {
            throw "This should not happen.";
          }
          r = result.subarray(offset, offset + length);
        }
        callback(err, r);
      });
    }
    function readFileSync(path, encoding) {
      var xhr = createXHR(path, encoding, false), r;
      try {
        xhr.send(null);
        r = handleXHRResult(path, encoding, xhr);
        if (r.err) {
          throw r.err;
        }
        if (r.data === null) {
          throw "No data read from " + path + ".";
        }
      } catch (e) {
        throw e;
      }
      return r.data;
    }
    function writeFile(path, data, callback) {
      var xhr = new XMLHttpRequest, d;
      function handleResult() {
        if (xhr.readyState === 4) {
          if (xhr.status === 0 && !xhr.responseText) {
            callback("File " + path + " is empty.");
          } else {
            if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 0) {
              callback(null);
            } else {
              callback("Status " + String(xhr.status) + ": " + xhr.responseText || xhr.statusText);
            }
          }
        }
      }
      xhr.open("PUT", path, true);
      xhr.onreadystatechange = handleResult;
      if (data.buffer && !xhr.sendAsBinary) {
        d = data.buffer;
      } else {
        d = self.byteArrayToString(data, "binary");
      }
      try {
        if (xhr.sendAsBinary) {
          xhr.sendAsBinary(d);
        } else {
          xhr.send(d);
        }
      } catch (e) {
        self.log("HUH? " + e + " " + data);
        callback(e.message);
      }
    }
    function deleteFile(path, callback) {
      var xhr = new XMLHttpRequest;
      xhr.open("DELETE", path, true);
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
          if (xhr.status < 200 && xhr.status >= 300) {
            callback(xhr.responseText);
          } else {
            callback(null);
          }
        }
      };
      xhr.send(null);
    }
    function loadXML(path, callback) {
      var xhr = new XMLHttpRequest;
      function handleResult() {
        if (xhr.readyState === 4) {
          if (xhr.status === 0 && !xhr.responseText) {
            callback("File " + path + " is empty.", null);
          } else {
            if (xhr.status === 200 || xhr.status === 0) {
              callback(null, xhr.responseXML);
            } else {
              callback(xhr.responseText, null);
            }
          }
        }
      }
      xhr.open("GET", path, true);
      if (xhr.overrideMimeType) {
        xhr.overrideMimeType("text/xml");
      }
      xhr.onreadystatechange = handleResult;
      try {
        xhr.send(null);
      } catch (e) {
        callback(e.message, null);
      }
    }
    this.readFile = readFile;
    this.read = read;
    this.readFileSync = readFileSync;
    this.writeFile = writeFile;
    this.deleteFile = deleteFile;
    this.loadXML = loadXML;
    this.log = log;
    this.enableAlerts = true;
    this.assert = Runtime.assert;
    this.setTimeout = function(f, msec) {
      return setTimeout(function() {
        f();
      }, msec);
    };
    this.clearTimeout = function(timeoutID) {
      clearTimeout(timeoutID);
    };
    this.libraryPaths = function() {
      return ["lib"];
    };
    this.setCurrentDirectory = function() {
    };
    this.currentDirectory = function() {
      return "";
    };
    this.type = function() {
      return "BrowserRuntime";
    };
    this.getDOMImplementation = function() {
      return window.document.implementation;
    };
    this.parseXML = function(xml) {
      var parser = new DOMParser;
      return parser.parseFromString(xml, "text/xml");
    };
    this.exit = function(exitCode) {
      log("Calling exit with code " + String(exitCode) + ", but exit() is not implemented.");
    };
    this.getWindow = function() {
      return window;
    };
    this.requestAnimationFrame = function(callback) {
      var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame, requestId = 0;
      if (rAF) {
        rAF.bind(window);
        requestId = rAF(callback);
      } else {
        return setTimeout(callback, 15);
      }
      return requestId;
    };
    this.cancelAnimationFrame = function(requestId) {
      var cAF = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame;
      if (cAF) {
        cAF.bind(window);
        cAF(requestId);
      } else {
        clearTimeout(requestId);
      }
    };
  }
  function NodeJSRuntime() {
    var self = this, fs = require("fs"), pathmod = require("path"), currentDirectory = "", parser, domImplementation;
    function bufferToUint8Array(buffer) {
      var l = buffer.length, i, a = new Uint8Array(new ArrayBuffer(l));
      for (i = 0;i < l;i += 1) {
        a[i] = buffer[i];
      }
      return a;
    }
    this.byteArrayFromString = function(string, encoding) {
      var buf = new Buffer(string, encoding), i, l = buf.length, a = new Uint8Array(new ArrayBuffer(l));
      for (i = 0;i < l;i += 1) {
        a[i] = buf[i];
      }
      return a;
    };
    this.byteArrayToString = Runtime.byteArrayToString;
    this.getVariable = Runtime.getVariable;
    this.fromJson = Runtime.fromJson;
    this.toJson = Runtime.toJson;
    function readFile(path, encoding, callback) {
      function convert(err, data) {
        if (err) {
          return callback(err, null);
        }
        if (!data) {
          return callback("No data for " + path + ".", null);
        }
        var d;
        if (typeof data === "string") {
          d = data;
          return callback(err, d);
        }
        d = data;
        callback(err, bufferToUint8Array(d));
      }
      path = pathmod.resolve(currentDirectory, path);
      if (encoding !== "binary") {
        fs.readFile(path, encoding, convert);
      } else {
        fs.readFile(path, null, convert);
      }
    }
    this.readFile = readFile;
    function loadXML(path, callback) {
      readFile(path, "utf-8", function(err, data) {
        if (err) {
          return callback(err, null);
        }
        if (!data) {
          return callback("No data for " + path + ".", null);
        }
        var d = data;
        callback(null, self.parseXML(d));
      });
    }
    this.loadXML = loadXML;
    this.writeFile = function(path, data, callback) {
      var buf = new Buffer(data);
      path = pathmod.resolve(currentDirectory, path);
      fs.writeFile(path, buf, "binary", function(err) {
        callback(err || null);
      });
    };
    this.deleteFile = function(path, callback) {
      path = pathmod.resolve(currentDirectory, path);
      fs.unlink(path, callback);
    };
    this.read = function(path, offset, length, callback) {
      path = pathmod.resolve(currentDirectory, path);
      fs.open(path, "r+", 666, function(err, fd) {
        if (err) {
          callback(err, null);
          return;
        }
        var buffer = new Buffer(length);
        fs.read(fd, buffer, 0, length, offset, function(err) {
          fs.close(fd);
          callback(err, bufferToUint8Array(buffer));
        });
      });
    };
    this.readFileSync = function(path, encoding) {
      var s, enc = encoding === "binary" ? null : encoding, r = fs.readFileSync(path, enc);
      if (r === null) {
        throw "File " + path + " could not be read.";
      }
      if (encoding === "binary") {
        s = r;
        s = bufferToUint8Array(s);
      } else {
        s = r;
      }
      return s;
    };
    function log(msgOrCategory, msg) {
      var category;
      if (msg !== undefined) {
        category = msgOrCategory;
      } else {
        msg = msgOrCategory;
      }
      if (category === "alert") {
        process.stderr.write("\n!!!!! ALERT !!!!!" + "\n");
      }
      process.stderr.write(msg + "\n");
      if (category === "alert") {
        process.stderr.write("!!!!! ALERT !!!!!" + "\n");
      }
    }
    this.log = log;
    this.assert = Runtime.assert;
    this.setTimeout = function(f, msec) {
      return setTimeout(function() {
        f();
      }, msec);
    };
    this.clearTimeout = function(timeoutID) {
      clearTimeout(timeoutID);
    };
    this.libraryPaths = function() {
      return [__dirname];
    };
    this.setCurrentDirectory = function(dir) {
      currentDirectory = dir;
    };
    this.currentDirectory = function() {
      return currentDirectory;
    };
    this.type = function() {
      return "NodeJSRuntime";
    };
    this.getDOMImplementation = function() {
      return domImplementation;
    };
    this.parseXML = function(xml) {
      return parser.parseFromString(xml, "text/xml");
    };
    this.exit = process.exit;
    this.getWindow = function() {
      return null;
    };
    this.requestAnimationFrame = function(callback) {
      return setTimeout(callback, 15);
    };
    this.cancelAnimationFrame = function(requestId) {
      clearTimeout(requestId);
    };
    function init() {
      var DOMParser = require("xmldom").DOMParser;
      parser = new DOMParser;
      domImplementation = self.parseXML("<a/>").implementation;
    }
    init();
  }
  function RhinoRuntime() {
    var self = this, Packages = {}, dom = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance(), builder, entityresolver, currentDirectory = "";
    dom.setValidating(false);
    dom.setNamespaceAware(true);
    dom.setExpandEntityReferences(false);
    dom.setSchema(null);
    entityresolver = Packages.org.xml.sax.EntityResolver({resolveEntity:function(publicId, systemId) {
      var file;
      function open(path) {
        var reader = new Packages.java.io.FileReader(path), source = new Packages.org.xml.sax.InputSource(reader);
        return source;
      }
      file = systemId;
      return open(file);
    }});
    builder = dom.newDocumentBuilder();
    builder.setEntityResolver(entityresolver);
    this.byteArrayFromString = function(string, encoding) {
      var i, l = string.length, a = new Uint8Array(new ArrayBuffer(l));
      for (i = 0;i < l;i += 1) {
        a[i] = string.charCodeAt(i) & 255;
      }
      return a;
    };
    this.byteArrayToString = Runtime.byteArrayToString;
    this.getVariable = Runtime.getVariable;
    this.fromJson = Runtime.fromJson;
    this.toJson = Runtime.toJson;
    function loadXML(path, callback) {
      var file = new Packages.java.io.File(path), xmlDocument = null;
      try {
        xmlDocument = builder.parse(file);
      } catch (err) {
        print(err);
        return callback(err, null);
      }
      callback(null, xmlDocument);
    }
    function runtimeReadFile(path, encoding, callback) {
      if (currentDirectory) {
        path = currentDirectory + "/" + path;
      }
      var file = new Packages.java.io.File(path), data, rhinoencoding = encoding === "binary" ? "latin1" : encoding;
      if (!file.isFile()) {
        callback(path + " is not a file.", null);
      } else {
        data = readFile(path, rhinoencoding);
        if (data && encoding === "binary") {
          data = self.byteArrayFromString(data, "binary");
        }
        callback(null, data);
      }
    }
    function runtimeReadFileSync(path, encoding) {
      var file = new Packages.java.io.File(path);
      if (!file.isFile()) {
        return null;
      }
      if (encoding === "binary") {
        encoding = "latin1";
      }
      return readFile(path, encoding);
    }
    this.loadXML = loadXML;
    this.readFile = runtimeReadFile;
    this.writeFile = function(path, data, callback) {
      if (currentDirectory) {
        path = currentDirectory + "/" + path;
      }
      var out = new Packages.java.io.FileOutputStream(path), i, l = data.length;
      for (i = 0;i < l;i += 1) {
        out.write(data[i]);
      }
      out.close();
      callback(null);
    };
    this.deleteFile = function(path, callback) {
      if (currentDirectory) {
        path = currentDirectory + "/" + path;
      }
      var file = new Packages.java.io.File(path), otherPath = path + Math.random(), other = new Packages.java.io.File(otherPath);
      if (file.rename(other)) {
        other.deleteOnExit();
        callback(null);
      } else {
        callback("Could not delete " + path);
      }
    };
    this.read = function(path, offset, length, callback) {
      if (currentDirectory) {
        path = currentDirectory + "/" + path;
      }
      var data = runtimeReadFileSync(path, "binary");
      if (data) {
        callback(null, this.byteArrayFromString(data.substring(offset, offset + length), "binary"));
      } else {
        callback("Cannot read " + path, null);
      }
    };
    this.readFileSync = function(path, encoding) {
      if (!encoding) {
        return "";
      }
      var s = readFile(path, encoding);
      if (s === null) {
        throw "File could not be read.";
      }
      return s;
    };
    function log(msgOrCategory, msg) {
      var category;
      if (msg !== undefined) {
        category = msgOrCategory;
      } else {
        msg = msgOrCategory;
      }
      if (category === "alert") {
        print("\n!!!!! ALERT !!!!!");
      }
      print(msg);
      if (category === "alert") {
        print("!!!!! ALERT !!!!!");
      }
    }
    this.log = log;
    this.assert = Runtime.assert;
    this.setTimeout = function(f) {
      f();
      return 0;
    };
    this.clearTimeout = function() {
    };
    this.libraryPaths = function() {
      return ["lib"];
    };
    this.setCurrentDirectory = function(dir) {
      currentDirectory = dir;
    };
    this.currentDirectory = function() {
      return currentDirectory;
    };
    this.type = function() {
      return "RhinoRuntime";
    };
    this.getDOMImplementation = function() {
      return builder.getDOMImplementation();
    };
    this.parseXML = function(xml) {
      var reader = new Packages.java.io.StringReader(xml), source = new Packages.org.xml.sax.InputSource(reader);
      return builder.parse(source);
    };
    this.exit = quit;
    this.getWindow = function() {
      return null;
    };
    this.requestAnimationFrame = function(callback) {
      callback();
      return 0;
    };
    this.cancelAnimationFrame = function() {
    };
  }
  Runtime.create = function create() {
    var result;
    if (String(typeof window) !== "undefined") {
      result = new BrowserRuntime;
    } else {
      if (String(typeof require) !== "undefined") {
        result = new NodeJSRuntime;
      } else {
        result = new RhinoRuntime;
      }
    }
    return result;
  };
  var runtime = Runtime.create();
  var core = {};
  var gui = {};
  var xmldom = {};
  var odf = {};
  var ops = {};
  var webodf = {};
  (function() {
    function getWebODFVersion() {
      var version = String(typeof webodf_version) !== "undefined" ? webodf_version : "From Source";
      return version;
    }
    webodf.Version = getWebODFVersion();
  })();
  (function() {
    function loadDependenciesFromManifest(dir, dependencies, expectFail) {
      var path = dir + "/manifest.json", content, list, manifest, m;
      runtime.log("Loading manifest: " + path);
      try {
        content = runtime.readFileSync(path, "utf-8");
      } catch (e) {
        if (expectFail) {
          runtime.log("No loadable manifest found.");
        } else {
          console.debug(String(e));
          throw e;
        }
        return;
      }
      list = JSON.parse(content);
      manifest = list;
      for (m in manifest) {
        if (manifest.hasOwnProperty(m)) {
          dependencies[m] = {dir:dir, deps:manifest[m]};
        }
      }
    }
    function loadDependenciesFromManifests() {
      var dependencies = [], paths = runtime.libraryPaths(), i;
      if (runtime.currentDirectory() && paths.indexOf(runtime.currentDirectory()) === -1) {
        loadDependenciesFromManifest(runtime.currentDirectory(), dependencies, true);
      }
      for (i = 0;i < paths.length;i += 1) {
        loadDependenciesFromManifest(paths[i], dependencies);
      }
      return dependencies;
    }
    function getPath(dir, className) {
      return dir + "/" + className.replace(".", "/") + ".js";
    }
    function getLoadList(classNames, dependencies, isDefined) {
      var loadList = [], stack = {}, visited = {};
      function visit(n) {
        if (visited[n] || isDefined(n)) {
          return;
        }
        if (stack[n]) {
          throw "Circular dependency detected for " + n + ".";
        }
        stack[n] = true;
        if (!dependencies[n]) {
          throw "Missing dependency information for class " + n + ".";
        }
        var d = dependencies[n], deps = d.deps, i, l = deps.length;
        for (i = 0;i < l;i += 1) {
          visit(deps[i]);
        }
        stack[n] = false;
        visited[n] = true;
        loadList.push(getPath(d.dir, n));
      }
      classNames.forEach(visit);
      return loadList;
    }
    function addContent(path, content) {
      content += "\n//# sourceURL=" + path;
      return content;
    }
    function loadFiles(paths) {
      var i, content;
      for (i = 0;i < paths.length;i += 1) {
        content = runtime.readFileSync(paths[i], "utf-8");
        content = addContent(paths[i], content);
        eval(content);
      }
    }
    function loadFilesInBrowser(paths, callback) {
      var e = document.currentScript || document.documentElement.lastChild, df = document.createDocumentFragment(), script, i;
      for (i = 0;i < paths.length;i += 1) {
        script = document.createElement("script");
        script.type = "text/javascript";
        script.charset = "utf-8";
        script.async = false;
        script.setAttribute("src", paths[i]);
        df.appendChild(script);
      }
      if (callback) {
        script.onload = callback;
      }
      e.parentNode.insertBefore(df, e);
    }
    var dependencies, packages = {core:core, gui:gui, xmldom:xmldom, odf:odf, ops:ops};
    function isDefined(classname) {
      var parts = classname.split("."), i, p = packages, l = parts.length;
      for (i = 0;i < l;i += 1) {
        if (!p.hasOwnProperty(parts[i])) {
          return false;
        }
        p = p[parts[i]];
      }
      return true;
    }
    runtime.loadClasses = function(classnames, callback) {
      if (IS_COMPILED_CODE || classnames.length === 0) {
        return callback && callback();
      }
      dependencies = dependencies || loadDependenciesFromManifests();
      classnames = getLoadList(classnames, dependencies, isDefined);
      if (classnames.length === 0) {
        return callback && callback();
      }
      if (runtime.type() === "BrowserRuntime" && callback) {
        loadFilesInBrowser(classnames, callback);
      } else {
        loadFiles(classnames);
        if (callback) {
          callback();
        }
      }
    };
    runtime.loadClass = function(classname, callback) {
      runtime.loadClasses([classname], callback);
    };
  })();
  (function() {
    var translator = function(string) {
      return string;
    };
    function tr(original) {
      var result = translator(original);
      if (!result || String(typeof result) !== "string") {
        return original;
      }
      return result;
    }
    runtime.getTranslator = function() {
      return translator;
    };
    runtime.setTranslator = function(translatorFunction) {
      translator = translatorFunction;
    };
    runtime.tr = tr;
  })();
  (function(args) {
    if (args) {
      args = Array.prototype.slice.call(args);
    } else {
      args = [];
    }
    function run(argv) {
      if (!argv.length) {
        return;
      }
      var script = argv[0];
      runtime.readFile(script, "utf8", function(err, code) {
        var path = "", pathEndIndex = script.lastIndexOf("/"), codestring = code;
        if (pathEndIndex !== -1) {
          path = script.substring(0, pathEndIndex);
        } else {
          path = ".";
        }
        runtime.setCurrentDirectory(path);
        function inner_run() {
          var script, path, args, argv, result;
          result = eval(codestring);
          if (result) {
            runtime.exit(result);
          }
          return;
        }
        if (err) {
          runtime.log(err);
          runtime.exit(1);
        } else {
          if (codestring === null) {
            runtime.log("No code found for " + script);
            runtime.exit(1);
          } else {
            inner_run.apply(null, argv);
          }
        }
      });
    }
    if (runtime.type() === "NodeJSRuntime") {
      run(process.argv.slice(2));
    } else {
      if (runtime.type() === "RhinoRuntime") {
        run(args);
      } else {
        run(args.slice(1));
      }
    }
  })(String(typeof arguments) !== "undefined" && arguments);
  (function() {
    function createASyncSingleton() {
      function forEach(items, f, callback) {
        var i, l = items.length, itemsDone = 0;
        function end(err) {
          if (itemsDone !== l) {
            if (err) {
              itemsDone = l;
              callback(err);
            } else {
              itemsDone += 1;
              if (itemsDone === l) {
                callback(null);
              }
            }
          }
        }
        for (i = 0;i < l;i += 1) {
          f(items[i], end);
        }
      }
      function destroyAll(items, callback) {
        function destroy(itemIndex, err) {
          if (err) {
            callback(err);
          } else {
            if (itemIndex < items.length) {
              items[itemIndex](function(err) {
                destroy(itemIndex + 1, err);
              });
            } else {
              callback();
            }
          }
        }
        destroy(0, undefined);
      }
      return {forEach:forEach, destroyAll:destroyAll};
    }
    core.Async = createASyncSingleton();
  })();
  function makeBase64() {
    function makeB64tab(bin) {
      var t = {}, i, l;
      for (i = 0, l = bin.length;i < l;i += 1) {
        t[bin.charAt(i)] = i;
      }
      return t;
    }
    var b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", b64tab = makeB64tab(b64chars), convertUTF16StringToBase64, convertBase64ToUTF16String, window = runtime.getWindow(), btoa, atob;
    function stringToArray(s) {
      var i, l = s.length, a = new Uint8Array(new ArrayBuffer(l));
      for (i = 0;i < l;i += 1) {
        a[i] = s.charCodeAt(i) & 255;
      }
      return a;
    }
    function convertUTF8ArrayToBase64(bin) {
      var n, b64 = "", i, l = bin.length - 2;
      for (i = 0;i < l;i += 3) {
        n = bin[i] << 16 | bin[i + 1] << 8 | bin[i + 2];
        b64 += b64chars[n >>> 18];
        b64 += b64chars[n >>> 12 & 63];
        b64 += b64chars[n >>> 6 & 63];
        b64 += b64chars[n & 63];
      }
      if (i === l + 1) {
        n = bin[i] << 4;
        b64 += b64chars[n >>> 6];
        b64 += b64chars[n & 63];
        b64 += "==";
      } else {
        if (i === l) {
          n = bin[i] << 10 | bin[i + 1] << 2;
          b64 += b64chars[n >>> 12];
          b64 += b64chars[n >>> 6 & 63];
          b64 += b64chars[n & 63];
          b64 += "=";
        }
      }
      return b64;
    }
    function convertBase64ToUTF8Array(b64) {
      b64 = b64.replace(/[^A-Za-z0-9+\/]+/g, "");
      var l = b64.length, bin = new Uint8Array(new ArrayBuffer(3 * l)), padlen = b64.length % 4, o = 0, i, n, a = [0, 0, 2, 1];
      for (i = 0;i < l;i += 4) {
        n = (b64tab[b64.charAt(i)] || 0) << 18 | (b64tab[b64.charAt(i + 1)] || 0) << 12 | (b64tab[b64.charAt(i + 2)] || 0) << 6 | (b64tab[b64.charAt(i + 3)] || 0);
        bin[o] = n >> 16;
        bin[o + 1] = n >> 8 & 255;
        bin[o + 2] = n & 255;
        o += 3;
      }
      l = 3 * l - a[padlen];
      return bin.subarray(0, l);
    }
    function convertUTF16ArrayToUTF8Array(uni) {
      var i, n, l = uni.length, o = 0, bin = new Uint8Array(new ArrayBuffer(3 * l));
      for (i = 0;i < l;i += 1) {
        n = uni[i];
        if (n < 128) {
          bin[o++] = n;
        } else {
          if (n < 2048) {
            bin[o++] = 192 | n >>> 6;
            bin[o++] = 128 | n & 63;
          } else {
            bin[o++] = 224 | n >>> 12 & 15;
            bin[o++] = 128 | n >>> 6 & 63;
            bin[o++] = 128 | n & 63;
          }
        }
      }
      return bin.subarray(0, o);
    }
    function convertUTF8ArrayToUTF16Array(bin) {
      var i, c0, c1, c2, l = bin.length, uni = new Uint8Array(new ArrayBuffer(l)), o = 0;
      for (i = 0;i < l;i += 1) {
        c0 = bin[i];
        if (c0 < 128) {
          uni[o++] = c0;
        } else {
          i += 1;
          c1 = bin[i];
          if (c0 < 224) {
            uni[o++] = (c0 & 31) << 6 | c1 & 63;
          } else {
            i += 1;
            c2 = bin[i];
            uni[o++] = (c0 & 15) << 12 | (c1 & 63) << 6 | c2 & 63;
          }
        }
      }
      return uni.subarray(0, o);
    }
    function convertUTF8StringToBase64(bin) {
      return convertUTF8ArrayToBase64(stringToArray(bin));
    }
    function convertBase64ToUTF8String(b64) {
      return String.fromCharCode.apply(String, convertBase64ToUTF8Array(b64));
    }
    function convertUTF8StringToUTF16Array(bin) {
      return convertUTF8ArrayToUTF16Array(stringToArray(bin));
    }
    function convertUTF8ArrayToUTF16String(bin) {
      var b = convertUTF8ArrayToUTF16Array(bin), r = "", i = 0, chunksize = 45E3;
      while (i < b.length) {
        r += String.fromCharCode.apply(String, b.subarray(i, i + chunksize));
        i += chunksize;
      }
      return r;
    }
    function convertUTF8StringToUTF16String_internal(bin, i, end) {
      var c0, c1, c2, j, str = "";
      for (j = i;j < end;j += 1) {
        c0 = bin.charCodeAt(j) & 255;
        if (c0 < 128) {
          str += String.fromCharCode(c0);
        } else {
          j += 1;
          c1 = bin.charCodeAt(j) & 255;
          if (c0 < 224) {
            str += String.fromCharCode((c0 & 31) << 6 | c1 & 63);
          } else {
            j += 1;
            c2 = bin.charCodeAt(j) & 255;
            str += String.fromCharCode((c0 & 15) << 12 | (c1 & 63) << 6 | c2 & 63);
          }
        }
      }
      return str;
    }
    function convertUTF8StringToUTF16String(bin, callback) {
      var partsize = 1E5, str = "", pos = 0;
      if (bin.length < partsize) {
        callback(convertUTF8StringToUTF16String_internal(bin, 0, bin.length), true);
        return;
      }
      if (typeof bin !== "string") {
        bin = bin.slice();
      }
      function f() {
        var end = pos + partsize;
        if (end > bin.length) {
          end = bin.length;
        }
        str += convertUTF8StringToUTF16String_internal(bin, pos, end);
        pos = end;
        end = pos === bin.length;
        if (callback(str, end) && !end) {
          runtime.setTimeout(f, 0);
        }
      }
      f();
    }
    function convertUTF16StringToUTF8Array(uni) {
      return convertUTF16ArrayToUTF8Array(stringToArray(uni));
    }
    function convertUTF16ArrayToUTF8String(uni) {
      return String.fromCharCode.apply(String, convertUTF16ArrayToUTF8Array(uni));
    }
    function convertUTF16StringToUTF8String(uni) {
      return String.fromCharCode.apply(String, convertUTF16ArrayToUTF8Array(stringToArray(uni)));
    }
    if (window && window.btoa) {
      btoa = window.btoa;
      convertUTF16StringToBase64 = function(uni) {
        return btoa(convertUTF16StringToUTF8String(uni));
      };
    } else {
      btoa = convertUTF8StringToBase64;
      convertUTF16StringToBase64 = function(uni) {
        return convertUTF8ArrayToBase64(convertUTF16StringToUTF8Array(uni));
      };
    }
    if (window && window.atob) {
      atob = window.atob;
      convertBase64ToUTF16String = function(b64) {
        var b = atob(b64);
        return convertUTF8StringToUTF16String_internal(b, 0, b.length);
      };
    } else {
      atob = convertBase64ToUTF8String;
      convertBase64ToUTF16String = function(b64) {
        return convertUTF8ArrayToUTF16String(convertBase64ToUTF8Array(b64));
      };
    }
    core.Base64 = function Base64() {
      this.convertUTF8ArrayToBase64 = convertUTF8ArrayToBase64;
      this.convertByteArrayToBase64 = convertUTF8ArrayToBase64;
      this.convertBase64ToUTF8Array = convertBase64ToUTF8Array;
      this.convertBase64ToByteArray = convertBase64ToUTF8Array;
      this.convertUTF16ArrayToUTF8Array = convertUTF16ArrayToUTF8Array;
      this.convertUTF16ArrayToByteArray = convertUTF16ArrayToUTF8Array;
      this.convertUTF8ArrayToUTF16Array = convertUTF8ArrayToUTF16Array;
      this.convertByteArrayToUTF16Array = convertUTF8ArrayToUTF16Array;
      this.convertUTF8StringToBase64 = convertUTF8StringToBase64;
      this.convertBase64ToUTF8String = convertBase64ToUTF8String;
      this.convertUTF8StringToUTF16Array = convertUTF8StringToUTF16Array;
      this.convertUTF8ArrayToUTF16String = convertUTF8ArrayToUTF16String;
      this.convertByteArrayToUTF16String = convertUTF8ArrayToUTF16String;
      this.convertUTF8StringToUTF16String = convertUTF8StringToUTF16String;
      this.convertUTF16StringToUTF8Array = convertUTF16StringToUTF8Array;
      this.convertUTF16StringToByteArray = convertUTF16StringToUTF8Array;
      this.convertUTF16ArrayToUTF8String = convertUTF16ArrayToUTF8String;
      this.convertUTF16StringToUTF8String = convertUTF16StringToUTF8String;
      this.convertUTF16StringToBase64 = convertUTF16StringToBase64;
      this.convertBase64ToUTF16String = convertBase64ToUTF16String;
      this.fromBase64 = convertBase64ToUTF8String;
      this.toBase64 = convertUTF8StringToBase64;
      this.atob = atob;
      this.btoa = btoa;
      this.utob = convertUTF16StringToUTF8String;
      this.btou = convertUTF8StringToUTF16String;
      this.encode = convertUTF16StringToBase64;
      this.encodeURI = function(u) {
        return convertUTF16StringToBase64(u).replace(/[+\/]/g, function(m0) {
          return m0 === "+" ? "-" : "_";
        }).replace(/\\=+$/, "");
      };
      this.decode = function(a) {
        return convertBase64ToUTF16String(a.replace(/[\-_]/g, function(m0) {
          return m0 === "-" ? "+" : "/";
        }));
      };
      return this;
    };
    return core.Base64;
  }
  core.Base64 = makeBase64();
  core.CSSUnits = function CSSUnits() {
    var self = this, sizemap = {"in":1, "cm":2.54, "mm":25.4, "pt":72, "pc":12, "px":96};
    this.convert = function(value, oldUnit, newUnit) {
      return value * sizemap[newUnit] / sizemap[oldUnit];
    };
    this.convertMeasure = function(measure, newUnit) {
      var value, oldUnit, newMeasure;
      if (measure && newUnit) {
        value = parseFloat(measure);
        oldUnit = measure.replace(value.toString(), "");
        newMeasure = self.convert(value, oldUnit, newUnit);
      }
      return newMeasure;
    };
    this.getUnits = function(measure) {
      return measure.substr(measure.length - 2, measure.length);
    };
  };
  (function() {
    var browserQuirks;
    function getBrowserQuirks() {
      var range, directBoundingRect, rangeBoundingRect, testContainer, testElement, detectedQuirks, window, document, docElement, body, docOverflow, bodyOverflow, bodyHeight, bodyScroll;
      if (browserQuirks === undefined) {
        window = runtime.getWindow();
        document = window && window.document;
        docElement = document.documentElement;
        body = document.body;
        browserQuirks = {rangeBCRIgnoresElementBCR:false, unscaledRangeClientRects:false, elementBCRIgnoresBodyScroll:false};
        if (document) {
          testContainer = document.createElement("div");
          testContainer.style.position = "absolute";
          testContainer.style.left = "-99999px";
          testContainer.style.transform = "scale(2)";
          testContainer.style["-webkit-transform"] = "scale(2)";
          testElement = document.createElement("div");
          testContainer.appendChild(testElement);
          body.appendChild(testContainer);
          range = document.createRange();
          range.selectNode(testElement);
          browserQuirks.rangeBCRIgnoresElementBCR = range.getClientRects().length === 0;
          testElement.appendChild(document.createTextNode("Rect transform test"));
          directBoundingRect = testElement.getBoundingClientRect();
          rangeBoundingRect = range.getBoundingClientRect();
          browserQuirks.unscaledRangeClientRects = Math.abs(directBoundingRect.height - rangeBoundingRect.height) > 2;
          testContainer.style.transform = "";
          testContainer.style["-webkit-transform"] = "";
          docOverflow = docElement.style.overflow;
          bodyOverflow = body.style.overflow;
          bodyHeight = body.style.height;
          bodyScroll = body.scrollTop;
          docElement.style.overflow = "visible";
          body.style.overflow = "visible";
          body.style.height = "200%";
          body.scrollTop = body.scrollHeight;
          browserQuirks.elementBCRIgnoresBodyScroll = range.getBoundingClientRect().top !== testElement.getBoundingClientRect().top;
          body.scrollTop = bodyScroll;
          body.style.height = bodyHeight;
          body.style.overflow = bodyOverflow;
          docElement.style.overflow = docOverflow;
          range.detach();
          body.removeChild(testContainer);
          detectedQuirks = Object.keys(browserQuirks).map(function(quirk) {
            return quirk + ":" + String(browserQuirks[quirk]);
          }).join(", ");
          runtime.log("Detected browser quirks - " + detectedQuirks);
        }
      }
      return browserQuirks;
    }
    function getDirectChild(parent, ns, name) {
      var node = parent ? parent.firstElementChild : null;
      while (node) {
        if (node.localName === name && node.namespaceURI === ns) {
          return node;
        }
        node = node.nextElementSibling;
      }
      return null;
    }
    core.DomUtilsImpl = function DomUtilsImpl() {
      var sharedRange = null;
      function getSharedRange(doc) {
        var range;
        if (sharedRange) {
          range = sharedRange;
        } else {
          sharedRange = range = doc.createRange();
        }
        return range;
      }
      function findStablePoint(container, offset) {
        var c = container;
        if (offset < c.childNodes.length) {
          c = c.childNodes.item(offset);
          offset = 0;
          while (c.firstChild) {
            c = c.firstChild;
          }
        } else {
          while (c.lastChild) {
            c = c.lastChild;
            offset = c.nodeType === Node.TEXT_NODE ? c.textContent.length : c.childNodes.length;
          }
        }
        return {container:c, offset:offset};
      }
      function getPositionInContainingNode(node, container) {
        var offset = 0, n;
        while (node.parentNode !== container) {
          runtime.assert(node.parentNode !== null, "parent is null");
          node = node.parentNode;
        }
        n = container.firstChild;
        while (n !== node) {
          offset += 1;
          n = n.nextSibling;
        }
        return offset;
      }
      function splitBoundaries(range) {
        var modifiedNodes = [], originalEndContainer, resetToContainerLength, end, splitStart, node, text, offset;
        if (range.startContainer.nodeType === Node.TEXT_NODE || range.endContainer.nodeType === Node.TEXT_NODE) {
          originalEndContainer = range.endContainer;
          resetToContainerLength = range.endContainer.nodeType !== Node.TEXT_NODE ? range.endOffset === range.endContainer.childNodes.length : false;
          end = findStablePoint(range.endContainer, range.endOffset);
          if (end.container === originalEndContainer) {
            originalEndContainer = null;
          }
          range.setEnd(end.container, end.offset);
          node = range.endContainer;
          if (range.endOffset !== 0 && node.nodeType === Node.TEXT_NODE) {
            text = node;
            if (range.endOffset !== text.length) {
              modifiedNodes.push(text.splitText(range.endOffset));
              modifiedNodes.push(text);
            }
          }
          node = range.startContainer;
          if (range.startOffset !== 0 && node.nodeType === Node.TEXT_NODE) {
            text = node;
            if (range.startOffset !== text.length) {
              splitStart = text.splitText(range.startOffset);
              modifiedNodes.push(text);
              modifiedNodes.push(splitStart);
              range.setStart(splitStart, 0);
            }
          }
          if (originalEndContainer !== null) {
            node = range.endContainer;
            while (node.parentNode && node.parentNode !== originalEndContainer) {
              node = node.parentNode;
            }
            if (resetToContainerLength) {
              offset = originalEndContainer.childNodes.length;
            } else {
              offset = getPositionInContainingNode(node, originalEndContainer);
            }
            range.setEnd(originalEndContainer, offset);
          }
        }
        return modifiedNodes;
      }
      this.splitBoundaries = splitBoundaries;
      function containsRange(container, insideRange) {
        return container.compareBoundaryPoints(Range.START_TO_START, insideRange) <= 0 && container.compareBoundaryPoints(Range.END_TO_END, insideRange) >= 0;
      }
      this.containsRange = containsRange;
      function rangesIntersect(range1, range2) {
        return range1.compareBoundaryPoints(Range.END_TO_START, range2) <= 0 && range1.compareBoundaryPoints(Range.START_TO_END, range2) >= 0;
      }
      this.rangesIntersect = rangesIntersect;
      function rangeIntersection(range1, range2) {
        var newRange;
        if (rangesIntersect(range1, range2)) {
          newRange = range1.cloneRange();
          if (range1.compareBoundaryPoints(Range.START_TO_START, range2) === -1) {
            newRange.setStart(range2.startContainer, range2.startOffset);
          }
          if (range1.compareBoundaryPoints(Range.END_TO_END, range2) === 1) {
            newRange.setEnd(range2.endContainer, range2.endOffset);
          }
        }
        return newRange;
      }
      this.rangeIntersection = rangeIntersection;
      function maximumOffset(node) {
        return node.nodeType === Node.TEXT_NODE ? node.length : node.childNodes.length;
      }
      function moveToNonRejectedNode(walker, root, nodeFilter) {
        var node = walker.currentNode;
        if (node !== root) {
          node = node.parentNode;
          while (node && node !== root) {
            if (nodeFilter(node) === NodeFilter.FILTER_REJECT) {
              walker.currentNode = node;
            }
            node = node.parentNode;
          }
        }
        return walker.currentNode;
      }
      function getNodesInRange(range, nodeFilter, whatToShow) {
        var document = range.startContainer.ownerDocument, elements = [], rangeRoot = range.commonAncestorContainer, root = rangeRoot.nodeType === Node.TEXT_NODE ? rangeRoot.parentNode : rangeRoot, treeWalker = document.createTreeWalker(root, whatToShow, nodeFilter, false), currentNode, lastNodeInRange, endNodeCompareFlags, comparePositionResult;
        if (range.endContainer.childNodes[range.endOffset - 1]) {
          lastNodeInRange = range.endContainer.childNodes[range.endOffset - 1];
          endNodeCompareFlags = Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINED_BY;
        } else {
          lastNodeInRange = range.endContainer;
          endNodeCompareFlags = Node.DOCUMENT_POSITION_PRECEDING;
        }
        if (range.startContainer.childNodes[range.startOffset]) {
          currentNode = range.startContainer.childNodes[range.startOffset];
          treeWalker.currentNode = currentNode;
        } else {
          if (range.startOffset === maximumOffset(range.startContainer)) {
            currentNode = range.startContainer;
            treeWalker.currentNode = currentNode;
            treeWalker.lastChild();
            currentNode = treeWalker.nextNode();
          } else {
            currentNode = range.startContainer;
            treeWalker.currentNode = currentNode;
          }
        }
        if (currentNode) {
          currentNode = moveToNonRejectedNode(treeWalker, root, nodeFilter);
          switch(nodeFilter(currentNode)) {
            case NodeFilter.FILTER_REJECT:
              currentNode = treeWalker.nextSibling();
              while (!currentNode && treeWalker.parentNode()) {
                currentNode = treeWalker.nextSibling();
              }
              break;
            case NodeFilter.FILTER_SKIP:
              currentNode = treeWalker.nextNode();
              break;
            default:
              break;
          }
          while (currentNode) {
            comparePositionResult = lastNodeInRange.compareDocumentPosition(currentNode);
            if (comparePositionResult !== 0 && (comparePositionResult & endNodeCompareFlags) === 0) {
              break;
            }
            elements.push(currentNode);
            currentNode = treeWalker.nextNode();
          }
        }
        return elements;
      }
      this.getNodesInRange = getNodesInRange;
      function mergeTextNodes(node, nextNode) {
        var mergedNode = null, text, nextText;
        if (node.nodeType === Node.TEXT_NODE) {
          text = node;
          if (text.length === 0) {
            text.parentNode.removeChild(text);
            if (nextNode.nodeType === Node.TEXT_NODE) {
              mergedNode = nextNode;
            }
          } else {
            if (nextNode.nodeType === Node.TEXT_NODE) {
              nextText = nextNode;
              text.appendData(nextText.data);
              nextNode.parentNode.removeChild(nextNode);
            }
            mergedNode = node;
          }
        }
        return mergedNode;
      }
      function normalizeTextNodes(node) {
        if (node && node.nextSibling) {
          node = mergeTextNodes(node, node.nextSibling);
        }
        if (node && node.previousSibling) {
          mergeTextNodes(node.previousSibling, node);
        }
      }
      this.normalizeTextNodes = normalizeTextNodes;
      function rangeContainsNode(limits, node) {
        var range = node.ownerDocument.createRange(), nodeRange = node.ownerDocument.createRange(), result;
        range.setStart(limits.startContainer, limits.startOffset);
        range.setEnd(limits.endContainer, limits.endOffset);
        nodeRange.selectNodeContents(node);
        result = containsRange(range, nodeRange);
        range.detach();
        nodeRange.detach();
        return result;
      }
      this.rangeContainsNode = rangeContainsNode;
      function mergeIntoParent(targetNode) {
        var parent = targetNode.parentNode;
        while (targetNode.firstChild) {
          parent.insertBefore(targetNode.firstChild, targetNode);
        }
        parent.removeChild(targetNode);
        return parent;
      }
      this.mergeIntoParent = mergeIntoParent;
      function removeUnwantedNodes(targetNode, nodeFilter) {
        var parent = targetNode.parentNode, node = targetNode.firstChild, filterResult = nodeFilter(targetNode), next;
        if (filterResult === NodeFilter.FILTER_SKIP) {
          return parent;
        }
        while (node) {
          next = node.nextSibling;
          removeUnwantedNodes(node, nodeFilter);
          node = next;
        }
        if (parent && filterResult === NodeFilter.FILTER_REJECT) {
          mergeIntoParent(targetNode);
        }
        return parent;
      }
      this.removeUnwantedNodes = removeUnwantedNodes;
      this.removeAllChildNodes = function(node) {
        while (node.firstChild) {
          node.removeChild(node.firstChild);
        }
      };
      function getElementsByTagNameNS(node, namespace, tagName) {
        var e = [], list, i, l;
        list = node.getElementsByTagNameNS(namespace, tagName);
        e.length = l = list.length;
        for (i = 0;i < l;i += 1) {
          e[i] = list.item(i);
        }
        return e;
      }
      this.getElementsByTagNameNS = getElementsByTagNameNS;
      function getElementsByTagName(node, tagName) {
        var e = [], list, i, l;
        list = node.getElementsByTagName(tagName);
        e.length = l = list.length;
        for (i = 0;i < l;i += 1) {
          e[i] = list.item(i);
        }
        return e;
      }
      this.getElementsByTagName = getElementsByTagName;
      function containsNode(parent, descendant) {
        return parent === descendant || parent.contains(descendant);
      }
      this.containsNode = containsNode;
      function containsNodeForBrokenWebKit(parent, descendant) {
        return parent === descendant || Boolean(parent.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY);
      }
      function comparePoints(c1, o1, c2, o2) {
        if (c1 === c2) {
          return o2 - o1;
        }
        var comparison = c1.compareDocumentPosition(c2);
        if (comparison === 2) {
          comparison = -1;
        } else {
          if (comparison === 4) {
            comparison = 1;
          } else {
            if (comparison === 10) {
              o1 = getPositionInContainingNode(c1, c2);
              comparison = o1 < o2 ? 1 : -1;
            } else {
              o2 = getPositionInContainingNode(c2, c1);
              comparison = o2 < o1 ? -1 : 1;
            }
          }
        }
        return comparison;
      }
      this.comparePoints = comparePoints;
      function adaptRangeDifferenceToZoomLevel(inputNumber, zoomLevel) {
        if (getBrowserQuirks().unscaledRangeClientRects) {
          return inputNumber;
        }
        return inputNumber / zoomLevel;
      }
      this.adaptRangeDifferenceToZoomLevel = adaptRangeDifferenceToZoomLevel;
      this.translateRect = function(child, parent, zoomLevel) {
        return {top:adaptRangeDifferenceToZoomLevel(child.top - parent.top, zoomLevel), left:adaptRangeDifferenceToZoomLevel(child.left - parent.left, zoomLevel), bottom:adaptRangeDifferenceToZoomLevel(child.bottom - parent.top, zoomLevel), right:adaptRangeDifferenceToZoomLevel(child.right - parent.left, zoomLevel), width:adaptRangeDifferenceToZoomLevel(child.width, zoomLevel), height:adaptRangeDifferenceToZoomLevel(child.height, zoomLevel)};
      };
      function getBoundingClientRect(node) {
        var doc = node.ownerDocument, quirks = getBrowserQuirks(), range, element, rect, body = doc.body;
        if (quirks.unscaledRangeClientRects === false || quirks.rangeBCRIgnoresElementBCR) {
          if (node.nodeType === Node.ELEMENT_NODE) {
            element = node;
            rect = element.getBoundingClientRect();
            if (quirks.elementBCRIgnoresBodyScroll) {
              return {left:rect.left + body.scrollLeft, right:rect.right + body.scrollLeft, top:rect.top + body.scrollTop, bottom:rect.bottom + body.scrollTop, width:rect.width, height:rect.height};
            }
            return rect;
          }
        }
        range = getSharedRange(doc);
        range.selectNode(node);
        return range.getBoundingClientRect();
      }
      this.getBoundingClientRect = getBoundingClientRect;
      function mapKeyValObjOntoNode(node, properties, nsResolver) {
        Object.keys(properties).forEach(function(key) {
          var parts = key.split(":"), prefix = parts[0], localName = parts[1], ns = nsResolver(prefix), value = properties[key], element;
          if (ns) {
            element = node.getElementsByTagNameNS(ns, localName)[0];
            if (!element) {
              element = node.ownerDocument.createElementNS(ns, key);
              node.appendChild(element);
            }
            element.textContent = value;
          } else {
            runtime.log("Key ignored: " + key);
          }
        });
      }
      this.mapKeyValObjOntoNode = mapKeyValObjOntoNode;
      function removeKeyElementsFromNode(node, propertyNames, nsResolver) {
        propertyNames.forEach(function(propertyName) {
          var parts = propertyName.split(":"), prefix = parts[0], localName = parts[1], ns = nsResolver(prefix), element;
          if (ns) {
            element = node.getElementsByTagNameNS(ns, localName)[0];
            if (element) {
              element.parentNode.removeChild(element);
            } else {
              runtime.log("Element for " + propertyName + " not found.");
            }
          } else {
            runtime.log("Property Name ignored: " + propertyName);
          }
        });
      }
      this.removeKeyElementsFromNode = removeKeyElementsFromNode;
      function getKeyValRepresentationOfNode(node, prefixResolver) {
        var properties = {}, currentSibling = node.firstElementChild, prefix;
        while (currentSibling) {
          prefix = prefixResolver(currentSibling.namespaceURI);
          if (prefix) {
            properties[prefix + ":" + currentSibling.localName] = currentSibling.textContent;
          }
          currentSibling = currentSibling.nextElementSibling;
        }
        return properties;
      }
      this.getKeyValRepresentationOfNode = getKeyValRepresentationOfNode;
      function mapObjOntoNode(node, properties, nsResolver) {
        Object.keys(properties).forEach(function(key) {
          var parts = key.split(":"), prefix = parts[0], localName = parts[1], ns = nsResolver(prefix), value = properties[key], valueType = typeof value, element;
          if (valueType === "object") {
            if (Object.keys(value).length) {
              if (ns) {
                element = node.getElementsByTagNameNS(ns, localName)[0] || node.ownerDocument.createElementNS(ns, key);
              } else {
                element = node.getElementsByTagName(localName)[0] || node.ownerDocument.createElement(key);
              }
              node.appendChild(element);
              mapObjOntoNode(element, value, nsResolver);
            }
          } else {
            if (ns) {
              runtime.assert(valueType === "number" || valueType === "string", "attempting to map unsupported type '" + valueType + "' (key: " + key + ")");
              node.setAttributeNS(ns, key, String(value));
            }
          }
        });
      }
      this.mapObjOntoNode = mapObjOntoNode;
      function cloneEvent(event) {
        var e = Object.create(null);
        Object.keys(event.constructor.prototype).forEach(function(x) {
          e[x] = event[x];
        });
        e.prototype = event.constructor.prototype;
        return e;
      }
      this.cloneEvent = cloneEvent;
      this.getDirectChild = getDirectChild;
      function init(self) {
        var appVersion, webKitOrSafari, ie, window = runtime.getWindow();
        if (window === null) {
          return;
        }
        appVersion = window.navigator.appVersion.toLowerCase();
        webKitOrSafari = appVersion.indexOf("chrome") === -1 && (appVersion.indexOf("applewebkit") !== -1 || appVersion.indexOf("safari") !== -1);
        ie = appVersion.indexOf("msie") !== -1 || appVersion.indexOf("trident") !== -1;
        if (webKitOrSafari || ie) {
          self.containsNode = containsNodeForBrokenWebKit;
        }
      }
      init(this);
    };
    core.DomUtils = new core.DomUtilsImpl;
  })();
  core.Cursor = function Cursor(document, memberId) {
    var cursorns = "urn:webodf:names:cursor", cursorNode = document.createElementNS(cursorns, "cursor"), anchorNode = document.createElementNS(cursorns, "anchor"), forwardSelection, recentlyModifiedNodes = [], selectedRange = document.createRange(), isCollapsed, domUtils = core.DomUtils;
    function putIntoTextNode(node, container, offset) {
      runtime.assert(Boolean(container), "putCursorIntoTextNode: invalid container");
      var parent = container.parentNode;
      runtime.assert(Boolean(parent), "putCursorIntoTextNode: container without parent");
      runtime.assert(offset >= 0 && offset <= container.length, "putCursorIntoTextNode: offset is out of bounds");
      if (offset === 0) {
        parent.insertBefore(node, container);
      } else {
        if (offset === container.length) {
          parent.insertBefore(node, container.nextSibling);
        } else {
          container.splitText(offset);
          parent.insertBefore(node, container.nextSibling);
        }
      }
    }
    function removeNode(node) {
      if (node.parentNode) {
        recentlyModifiedNodes.push(node.previousSibling);
        recentlyModifiedNodes.push(node.nextSibling);
        node.parentNode.removeChild(node);
      }
    }
    function putNode(node, container, offset) {
      if (container.nodeType === Node.TEXT_NODE) {
        putIntoTextNode(node, container, offset);
      } else {
        if (container.nodeType === Node.ELEMENT_NODE) {
          container.insertBefore(node, container.childNodes.item(offset));
        }
      }
      recentlyModifiedNodes.push(node.previousSibling);
      recentlyModifiedNodes.push(node.nextSibling);
    }
    function getStartNode() {
      return forwardSelection ? anchorNode : cursorNode;
    }
    function getEndNode() {
      return forwardSelection ? cursorNode : anchorNode;
    }
    this.getNode = function() {
      return cursorNode;
    };
    this.getAnchorNode = function() {
      return anchorNode.parentNode ? anchorNode : cursorNode;
    };
    this.getSelectedRange = function() {
      if (isCollapsed) {
        selectedRange.setStartBefore(cursorNode);
        selectedRange.collapse(true);
      } else {
        selectedRange.setStartAfter(getStartNode());
        selectedRange.setEndBefore(getEndNode());
      }
      return selectedRange;
    };
    this.setSelectedRange = function(range, isForwardSelection) {
      if (selectedRange && selectedRange !== range) {
        selectedRange.detach();
      }
      selectedRange = range;
      forwardSelection = isForwardSelection !== false;
      isCollapsed = range.collapsed;
      if (range.collapsed) {
        removeNode(anchorNode);
        removeNode(cursorNode);
        putNode(cursorNode, range.startContainer, range.startOffset);
      } else {
        removeNode(anchorNode);
        removeNode(cursorNode);
        putNode(getEndNode(), range.endContainer, range.endOffset);
        putNode(getStartNode(), range.startContainer, range.startOffset);
      }
      recentlyModifiedNodes.forEach(domUtils.normalizeTextNodes);
      recentlyModifiedNodes.length = 0;
    };
    this.hasForwardSelection = function() {
      return forwardSelection;
    };
    this.remove = function() {
      removeNode(cursorNode);
      recentlyModifiedNodes.forEach(domUtils.normalizeTextNodes);
      recentlyModifiedNodes.length = 0;
    };
    function init() {
      cursorNode.setAttributeNS(cursorns, "memberId", memberId);
      anchorNode.setAttributeNS(cursorns, "memberId", memberId);
    }
    init();
  };
  core.Destroyable = function Destroyable() {
  };
  core.Destroyable.prototype.destroy = function(callback) {
  };
  core.EventSource = function() {
  };
  core.EventSource.prototype.subscribe = function(eventId, cb) {
  };
  core.EventSource.prototype.unsubscribe = function(eventId, cb) {
  };
  core.EventNotifier = function EventNotifier(eventIds) {
    var eventListener = {};
    this.emit = function(eventId, args) {
      var i, subscribers;
      runtime.assert(eventListener.hasOwnProperty(eventId), 'unknown event fired "' + eventId + '"');
      subscribers = eventListener[eventId];
      for (i = 0;i < subscribers.length;i += 1) {
        subscribers[i](args);
      }
    };
    this.subscribe = function(eventId, cb) {
      runtime.assert(eventListener.hasOwnProperty(eventId), 'tried to subscribe to unknown event "' + eventId + '"');
      eventListener[eventId].push(cb);
    };
    this.unsubscribe = function(eventId, cb) {
      var cbIndex;
      runtime.assert(eventListener.hasOwnProperty(eventId), 'tried to unsubscribe from unknown event "' + eventId + '"');
      cbIndex = eventListener[eventId].indexOf(cb);
      runtime.assert(cbIndex !== -1, 'tried to unsubscribe unknown callback from event "' + eventId + '"');
      if (cbIndex !== -1) {
        eventListener[eventId].splice(cbIndex, 1);
      }
    };
    function register(eventId) {
      runtime.assert(!eventListener.hasOwnProperty(eventId), 'Duplicated event ids: "' + eventId + '" registered more than once.');
      eventListener[eventId] = [];
    }
    this.register = register;
    function init() {
      if (eventIds) {
        eventIds.forEach(register);
      }
    }
    init();
  };
  core.ScheduledTask = function ScheduledTask(fn, scheduleTask, cancelTask) {
    var timeoutId, scheduled = false, args = [], destroyed = false;
    function cancel() {
      if (scheduled) {
        cancelTask(timeoutId);
        scheduled = false;
      }
    }
    function execute() {
      cancel();
      fn.apply(undefined, args);
      args = null;
    }
    this.trigger = function() {
      runtime.assert(destroyed === false, "Can't trigger destroyed ScheduledTask instance");
      args = Array.prototype.slice.call(arguments);
      if (!scheduled) {
        scheduled = true;
        timeoutId = scheduleTask(execute);
      }
    };
    this.triggerImmediate = function() {
      runtime.assert(destroyed === false, "Can't trigger destroyed ScheduledTask instance");
      args = Array.prototype.slice.call(arguments);
      execute();
    };
    this.processRequests = function() {
      if (scheduled) {
        execute();
      }
    };
    this.cancel = cancel;
    this.restart = function() {
      runtime.assert(destroyed === false, "Can't trigger destroyed ScheduledTask instance");
      cancel();
      scheduled = true;
      timeoutId = scheduleTask(execute);
    };
    this.destroy = function(callback) {
      cancel();
      destroyed = true;
      callback();
    };
  };
  (function() {
    var redrawTasks;
    function RedrawTasks() {
      var callbacks = {};
      this.requestRedrawTask = function(callback) {
        var id = runtime.requestAnimationFrame(function() {
          callback();
          delete callbacks[id];
        });
        callbacks[id] = callback;
        return id;
      };
      this.performRedraw = function() {
        Object.keys(callbacks).forEach(function(id) {
          callbacks[id]();
          runtime.cancelAnimationFrame(parseInt(id, 10));
        });
        callbacks = {};
      };
      this.cancelRedrawTask = function(id) {
        runtime.cancelAnimationFrame(id);
        delete callbacks[id];
      };
    }
    core.Task = {};
    core.Task.SUPPRESS_MANUAL_PROCESSING = false;
    core.Task.processTasks = function() {
      if (!core.Task.SUPPRESS_MANUAL_PROCESSING) {
        redrawTasks.performRedraw();
      }
    };
    core.Task.createRedrawTask = function(callback) {
      return new core.ScheduledTask(callback, redrawTasks.requestRedrawTask, redrawTasks.cancelRedrawTask);
    };
    core.Task.createTimeoutTask = function(callback, delay) {
      return new core.ScheduledTask(callback, function(callback) {
        return runtime.setTimeout(callback, delay);
      }, runtime.clearTimeout);
    };
    function init() {
      redrawTasks = new RedrawTasks;
    }
    init();
  })();
  core.EventSubscriptions = function() {
    var subscriptions = [], frameEventNotifier = new core.EventNotifier, frameSubscriptions = {}, nextFrameEventId = 0;
    function addSubscription(eventSource, eventid, callback) {
      eventSource.subscribe(eventid, callback);
      subscriptions.push({eventSource:eventSource, eventid:eventid, callback:callback});
    }
    this.addSubscription = addSubscription;
    this.addFrameSubscription = function(eventSource, eventid, callback) {
      var frameSubscription, frameEventId, eventFrameSubscriptions, i;
      if (!frameSubscriptions.hasOwnProperty(eventid)) {
        frameSubscriptions[eventid] = [];
      }
      eventFrameSubscriptions = frameSubscriptions[eventid];
      for (i = 0;i < eventFrameSubscriptions.length;i += 1) {
        if (eventFrameSubscriptions[i].eventSource === eventSource) {
          frameSubscription = eventFrameSubscriptions[i];
          break;
        }
      }
      if (!frameSubscription) {
        frameEventId = "s" + nextFrameEventId;
        nextFrameEventId += 1;
        frameEventNotifier.register(frameEventId);
        frameSubscription = {frameEventId:frameEventId, eventSource:eventSource, task:core.Task.createRedrawTask(function() {
          frameEventNotifier.emit(frameEventId, undefined);
        })};
        eventFrameSubscriptions.push(frameSubscription);
        addSubscription(eventSource, eventid, frameSubscription.task.trigger);
      }
      frameEventNotifier.subscribe(frameSubscription.frameEventId, callback);
    };
    function unsubscribeAll() {
      var cleanup = [];
      subscriptions.forEach(function(subscription) {
        subscription.eventSource.unsubscribe(subscription.eventid, subscription.callback);
      });
      subscriptions.length = 0;
      Object.keys(frameSubscriptions).forEach(function(eventId) {
        frameSubscriptions[eventId].forEach(function(subscriber) {
          cleanup.push(subscriber.task.destroy);
        });
        delete frameSubscriptions[eventId];
      });
      core.Async.destroyAll(cleanup, function() {
      });
      frameEventNotifier = new core.EventNotifier;
    }
    this.unsubscribeAll = unsubscribeAll;
    this.destroy = function(callback) {
      unsubscribeAll();
      callback();
    };
  };
  core.LazyProperty = function(valueLoader) {
    var cachedValue, valueLoaded = false;
    this.value = function() {
      if (!valueLoaded) {
        cachedValue = valueLoader();
        valueLoaded = true;
      }
      return cachedValue;
    };
    this.reset = function() {
      valueLoaded = false;
    };
  };
  core.LoopWatchDog = function LoopWatchDog(timeout, maxChecks) {
    var startTime = Date.now(), checks = 0;
    function check() {
      var t;
      if (timeout) {
        t = Date.now();
        if (t - startTime > timeout) {
          runtime.log("alert", "watchdog timeout");
          throw "timeout!";
        }
      }
      if (maxChecks > 0) {
        checks += 1;
        if (checks > maxChecks) {
          runtime.log("alert", "watchdog loop overflow");
          throw "loop overflow";
        }
      }
    }
    this.check = check;
  };
  core.NodeFilterChain = function(filters) {
    var FILTER_REJECT = NodeFilter.FILTER_REJECT, FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT;
    this.acceptNode = function(node) {
      var i;
      for (i = 0;i < filters.length;i += 1) {
        if (filters[i].acceptNode(node) === FILTER_REJECT) {
          return FILTER_REJECT;
        }
      }
      return FILTER_ACCEPT;
    };
  };
  core.PositionIterator = function PositionIterator(root, whatToShow, filter, expandEntityReferences) {
    var self = this, walker, currentPos, nodeFilter, TEXT_NODE = Node.TEXT_NODE, ELEMENT_NODE = Node.ELEMENT_NODE, FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT, FILTER_REJECT = NodeFilter.FILTER_REJECT;
    function EmptyTextNodeFilter() {
      this.acceptNode = function(node) {
        var text = node;
        if (!node || node.nodeType === TEXT_NODE && text.length === 0) {
          return FILTER_REJECT;
        }
        return FILTER_ACCEPT;
      };
    }
    function FilteredEmptyTextNodeFilter(filter) {
      this.acceptNode = function(node) {
        var text = node;
        if (!node || node.nodeType === TEXT_NODE && text.length === 0) {
          return FILTER_REJECT;
        }
        return filter.acceptNode(node);
      };
    }
    this.nextPosition = function() {
      var currentNode = walker.currentNode, nodeType = currentNode.nodeType, text = currentNode;
      if (currentNode === root) {
        return false;
      }
      if (currentPos === 0 && nodeType === ELEMENT_NODE) {
        if (walker.firstChild() === null) {
          currentPos = 1;
        }
      } else {
        if (nodeType === TEXT_NODE && currentPos + 1 < text.length) {
          currentPos += 1;
        } else {
          if (walker.nextSibling() !== null) {
            currentPos = 0;
          } else {
            if (walker.parentNode()) {
              currentPos = 1;
            } else {
              return false;
            }
          }
        }
      }
      return true;
    };
    function setAtEnd() {
      var text = walker.currentNode, type = text.nodeType;
      if (type === TEXT_NODE) {
        currentPos = text.length - 1;
      } else {
        currentPos = type === ELEMENT_NODE ? 1 : 0;
      }
    }
    function previousNode() {
      if (walker.previousSibling() === null) {
        if (!walker.parentNode() || walker.currentNode === root) {
          walker.firstChild();
          return false;
        }
        currentPos = 0;
      } else {
        setAtEnd();
      }
      return true;
    }
    this.previousPosition = function() {
      var moved = true, currentNode = walker.currentNode;
      if (currentPos === 0) {
        moved = previousNode();
      } else {
        if (currentNode.nodeType === TEXT_NODE) {
          currentPos -= 1;
        } else {
          if (walker.lastChild() !== null) {
            setAtEnd();
          } else {
            if (currentNode === root) {
              moved = false;
            } else {
              currentPos = 0;
            }
          }
        }
      }
      return moved;
    };
    this.previousNode = previousNode;
    this.container = function() {
      var n = walker.currentNode, t = n.nodeType;
      if (currentPos === 0 && t !== TEXT_NODE) {
        n = n.parentNode;
      }
      return n;
    };
    this.rightNode = function() {
      var n = walker.currentNode, text = n, nodeType = n.nodeType;
      if (nodeType === TEXT_NODE && currentPos === text.length) {
        n = n.nextSibling;
        while (n && nodeFilter(n) !== FILTER_ACCEPT) {
          n = n.nextSibling;
        }
      } else {
        if (nodeType === ELEMENT_NODE && currentPos === 1) {
          n = null;
        }
      }
      return n;
    };
    this.leftNode = function() {
      var n = walker.currentNode;
      if (currentPos === 0) {
        n = n.previousSibling;
        while (n && nodeFilter(n) !== FILTER_ACCEPT) {
          n = n.previousSibling;
        }
      } else {
        if (n.nodeType === ELEMENT_NODE) {
          n = n.lastChild;
          while (n && nodeFilter(n) !== FILTER_ACCEPT) {
            n = n.previousSibling;
          }
        }
      }
      return n;
    };
    this.getCurrentNode = function() {
      var n = walker.currentNode;
      return n;
    };
    this.unfilteredDomOffset = function() {
      if (walker.currentNode.nodeType === TEXT_NODE) {
        return currentPos;
      }
      var c = 0, n = walker.currentNode;
      if (currentPos === 1) {
        n = n.lastChild;
      } else {
        n = n.previousSibling;
      }
      while (n) {
        c += 1;
        n = n.previousSibling;
      }
      return c;
    };
    this.getPreviousSibling = function() {
      var currentNode = walker.currentNode, sibling = walker.previousSibling();
      walker.currentNode = currentNode;
      return sibling;
    };
    this.getNextSibling = function() {
      var currentNode = walker.currentNode, sibling = walker.nextSibling();
      walker.currentNode = currentNode;
      return sibling;
    };
    function moveToAcceptedNode() {
      var node = walker.currentNode, filterResult, moveResult;
      filterResult = nodeFilter(node);
      if (node !== root) {
        node = node.parentNode;
        while (node && node !== root) {
          if (nodeFilter(node) === FILTER_REJECT) {
            walker.currentNode = node;
            filterResult = FILTER_REJECT;
          }
          node = node.parentNode;
        }
      }
      if (filterResult === FILTER_REJECT) {
        currentPos = walker.currentNode.nodeType === TEXT_NODE ? node.length : 1;
        moveResult = self.nextPosition();
      } else {
        if (filterResult === FILTER_ACCEPT) {
          moveResult = true;
        } else {
          moveResult = self.nextPosition();
        }
      }
      if (moveResult) {
        runtime.assert(nodeFilter(walker.currentNode) === FILTER_ACCEPT, "moveToAcceptedNode did not result in walker being on an accepted node");
      }
      return moveResult;
    }
    this.setPositionBeforeElement = function(element) {
      runtime.assert(Boolean(element), "setPositionBeforeElement called without element");
      walker.currentNode = element;
      currentPos = 0;
      return moveToAcceptedNode();
    };
    this.setUnfilteredPosition = function(container, offset) {
      var text;
      runtime.assert(Boolean(container), "PositionIterator.setUnfilteredPosition called without container");
      walker.currentNode = container;
      if (container.nodeType === TEXT_NODE) {
        currentPos = offset;
        text = container;
        runtime.assert(offset <= text.length, "Error in setPosition: " + offset + " > " + text.length);
        runtime.assert(offset >= 0, "Error in setPosition: " + offset + " < 0");
        if (offset === text.length) {
          if (walker.nextSibling()) {
            currentPos = 0;
          } else {
            if (walker.parentNode()) {
              currentPos = 1;
            } else {
              runtime.assert(false, "Error in setUnfilteredPosition: position not valid.");
            }
          }
        }
      } else {
        if (offset < container.childNodes.length) {
          walker.currentNode = container.childNodes.item(offset);
          currentPos = 0;
        } else {
          currentPos = 1;
        }
      }
      return moveToAcceptedNode();
    };
    this.moveToEnd = function() {
      walker.currentNode = root;
      currentPos = 1;
    };
    this.moveToEndOfNode = function(node) {
      var text;
      if (node.nodeType === TEXT_NODE) {
        text = node;
        self.setUnfilteredPosition(text, text.length);
      } else {
        walker.currentNode = node;
        currentPos = 1;
      }
    };
    this.isBeforeNode = function() {
      return currentPos === 0;
    };
    this.getNodeFilter = function() {
      return nodeFilter;
    };
    function init() {
      var f;
      if (filter) {
        f = new FilteredEmptyTextNodeFilter(filter);
      } else {
        f = new EmptyTextNodeFilter;
      }
      nodeFilter = f.acceptNode;
      nodeFilter.acceptNode = nodeFilter;
      whatToShow = whatToShow || NodeFilter.SHOW_ALL;
      runtime.assert(root.nodeType !== Node.TEXT_NODE, "Internet Explorer doesn't allow tree walker roots to be text nodes");
      walker = root.ownerDocument.createTreeWalker(root, whatToShow, nodeFilter, expandEntityReferences);
      currentPos = 0;
      if (walker.firstChild() === null) {
        currentPos = 1;
      }
    }
    init();
  };
  core.PositionFilter = function PositionFilter() {
  };
  core.PositionFilter.FilterResult = {FILTER_ACCEPT:1, FILTER_REJECT:2, FILTER_SKIP:3};
  core.PositionFilter.prototype.acceptPosition = function(point) {
  };
  core.PositionFilterChain = function PositionFilterChain() {
    var filterChain = [], FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT;
    this.acceptPosition = function(iterator) {
      var i;
      for (i = 0;i < filterChain.length;i += 1) {
        if (filterChain[i].acceptPosition(iterator) === FILTER_REJECT) {
          return FILTER_REJECT;
        }
      }
      return FILTER_ACCEPT;
    };
    this.addFilter = function(filterInstance) {
      filterChain.push(filterInstance);
    };
  };
  core.StepDirection = {PREVIOUS:1, NEXT:2};
  core.StepIterator = function StepIterator(filter, iterator) {
    var FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, NEXT = core.StepDirection.NEXT, cachedContainer, cachedOffset, cachedFilterResult;
    function resetCache() {
      cachedContainer = null;
      cachedOffset = undefined;
      cachedFilterResult = undefined;
    }
    function isStep() {
      if (cachedFilterResult === undefined) {
        cachedFilterResult = filter.acceptPosition(iterator) === FILTER_ACCEPT;
      }
      return cachedFilterResult;
    }
    this.isStep = isStep;
    function setPosition(newContainer, newOffset) {
      resetCache();
      return iterator.setUnfilteredPosition(newContainer, newOffset);
    }
    this.setPosition = setPosition;
    function container() {
      if (!cachedContainer) {
        cachedContainer = iterator.container();
      }
      return cachedContainer;
    }
    this.container = container;
    function offset() {
      if (cachedOffset === undefined) {
        cachedOffset = iterator.unfilteredDomOffset();
      }
      return cachedOffset;
    }
    this.offset = offset;
    function nextStep() {
      resetCache();
      while (iterator.nextPosition()) {
        resetCache();
        if (isStep()) {
          return true;
        }
      }
      return false;
    }
    this.nextStep = nextStep;
    function previousStep() {
      resetCache();
      while (iterator.previousPosition()) {
        resetCache();
        if (isStep()) {
          return true;
        }
      }
      return false;
    }
    this.previousStep = previousStep;
    this.advanceStep = function(direction) {
      return direction === NEXT ? nextStep() : previousStep();
    };
    this.roundToClosestStep = function() {
      var currentContainer, currentOffset, isAtStep = isStep();
      if (!isAtStep) {
        currentContainer = container();
        currentOffset = offset();
        isAtStep = previousStep();
        if (!isAtStep) {
          setPosition(currentContainer, currentOffset);
          isAtStep = nextStep();
        }
      }
      return isAtStep;
    };
    this.roundToPreviousStep = function() {
      var isAtStep = isStep();
      if (!isAtStep) {
        isAtStep = previousStep();
      }
      return isAtStep;
    };
    this.roundToNextStep = function() {
      var isAtStep = isStep();
      if (!isAtStep) {
        isAtStep = nextStep();
      }
      return isAtStep;
    };
    this.leftNode = function() {
      return iterator.leftNode();
    };
    this.snapshot = function() {
      return new core.StepIterator.StepSnapshot(container(), offset());
    };
    this.restore = function(snapshot) {
      setPosition(snapshot.container, snapshot.offset);
    };
  };
  core.StepIterator.StepSnapshot = function(container, offset) {
    this.container = container;
    this.offset = offset;
  };
  core.Utils = function Utils() {
    function hashString(value) {
      var hash = 0, i, l;
      for (i = 0, l = value.length;i < l;i += 1) {
        hash = (hash << 5) - hash + value.charCodeAt(i);
        hash |= 0;
      }
      return hash;
    }
    this.hashString = hashString;
    var mergeObjects;
    function mergeItems(destination, source) {
      if (source && Array.isArray(source)) {
        destination = destination || [];
        if (!Array.isArray(destination)) {
          throw "Destination is not an array.";
        }
        destination = destination.concat(source.map(function(obj) {
          return mergeItems(null, obj);
        }));
      } else {
        if (source && typeof source === "object") {
          destination = destination || {};
          if (typeof destination !== "object") {
            throw "Destination is not an object.";
          }
          Object.keys(source).forEach(function(p) {
            destination[p] = mergeItems(destination[p], source[p]);
          });
        } else {
          destination = source;
        }
      }
      return destination;
    }
    mergeObjects = function(destination, source) {
      Object.keys(source).forEach(function(p) {
        destination[p] = mergeItems(destination[p], source[p]);
      });
      return destination;
    };
    this.mergeObjects = mergeObjects;
  };
  core.Zip = function Zip(url, entriesReadCallback) {
    var self = this, zip, base64 = new core.Base64;
    function load(filename, callback) {
      var entry = zip.file(filename);
      if (entry) {
        callback(null, entry.asUint8Array());
      } else {
        callback(filename + " not found.", null);
      }
    }
    function loadAsString(filename, callback) {
      load(filename, function(err, data) {
        if (err || data === null) {
          return callback(err, null);
        }
        var d = runtime.byteArrayToString(data, "utf8");
        callback(null, d);
      });
    }
    function loadContentXmlAsFragments(filename, handler) {
      loadAsString(filename, function(err, data) {
        if (err) {
          return handler.rootElementReady(err);
        }
        handler.rootElementReady(null, data, true);
      });
    }
    function loadAsDataURL(filename, mimetype, callback) {
      load(filename, function(err, data) {
        if (err || !data) {
          return callback(err, null);
        }
        var p = data, chunksize = 45E3, i = 0, dataurl;
        if (!mimetype) {
          if (p[1] === 80 && p[2] === 78 && p[3] === 71) {
            mimetype = "image/png";
          } else {
            if (p[0] === 255 && p[1] === 216 && p[2] === 255) {
              mimetype = "image/jpeg";
            } else {
              if (p[0] === 71 && p[1] === 73 && p[2] === 70) {
                mimetype = "image/gif";
              } else {
                mimetype = "";
              }
            }
          }
        }
        dataurl = "data:" + mimetype + ";base64,";
        while (i < data.length) {
          dataurl += base64.convertUTF8ArrayToBase64(p.subarray(i, Math.min(i + chunksize, p.length)));
          i += chunksize;
        }
        callback(null, dataurl);
      });
    }
    function loadAsDOM(filename, callback) {
      loadAsString(filename, function(err, xmldata) {
        if (err || xmldata === null) {
          callback(err, null);
          return;
        }
        var parser = new DOMParser, dom = parser.parseFromString(xmldata, "text/xml");
        callback(null, dom);
      });
    }
    function save(filename, data, compressed, date) {
      zip.file(filename, data, {date:date, compression:compressed ? "DEFLATE" : "STORE"});
    }
    function remove(filename) {
      var exists = zip.file(filename) !== null;
      zip.remove(filename);
      return exists;
    }
    function createByteArray(successCallback, errorCallback) {
      try {
        successCallback(zip.generate({type:"uint8array", compression:"STORE"}));
      } catch (e) {
        errorCallback(e.message);
      }
    }
    function writeAs(newurl, callback) {
      createByteArray(function(data) {
        runtime.writeFile(newurl, data, callback);
      }, callback);
    }
    function write(callback) {
      writeAs(url, callback);
    }
    this.load = load;
    this.save = save;
    this.remove = remove;
    this.write = write;
    this.writeAs = writeAs;
    this.createByteArray = createByteArray;
    this.loadContentXmlAsFragments = loadContentXmlAsFragments;
    this.loadAsString = loadAsString;
    this.loadAsDOM = loadAsDOM;
    this.loadAsDataURL = loadAsDataURL;
    this.getEntries = function() {
      return Object.keys(zip.files).map(function(filename) {
        var e = zip.files[filename];
        return {filename:filename, date:e.date};
      });
    };
    zip = new externs.JSZip;
    if (entriesReadCallback === null) {
      return;
    }
    runtime.readFile(url, "binary", function(err, result) {
      if (typeof result === "string") {
        err = "file was read as a string. Should be Uint8Array.";
      }
      if (err || !result || result.length === 0) {
        entriesReadCallback("File '" + url + "' cannot be read. Err: " + (err || "[none]"), self);
      } else {
        try {
          zip.load(result, {checkCRC32:false});
          entriesReadCallback(null, self);
        } catch (e) {
          entriesReadCallback(e.message, self);
        }
      }
    });
  };
  core.SimpleClientRect = null;
  gui.CommonConstraints = {EDIT:{ANNOTATIONS:{ONLY_DELETE_OWN:"onlyDeleteOwn"}, REVIEW_MODE:"reviewMode"}};
  gui.SessionConstraints = function SessionConstraints() {
    var constraints = {}, constraintNotifier = new core.EventNotifier;
    function registerConstraint(constraint) {
      if (!constraints.hasOwnProperty(constraint)) {
        constraints[constraint] = false;
        constraintNotifier.register(constraint);
      }
    }
    this.registerConstraint = registerConstraint;
    this.subscribe = function(constraint, callback) {
      registerConstraint(constraint);
      constraintNotifier.subscribe(constraint, callback);
    };
    this.unsubscribe = function(constraint, callback) {
      constraintNotifier.unsubscribe(constraint, callback);
    };
    this.setState = function(constraint, enabled) {
      runtime.assert(constraints.hasOwnProperty(constraint) === true, "No such constraint");
      if (constraints[constraint] !== enabled) {
        constraints[constraint] = enabled;
        constraintNotifier.emit(constraint, enabled);
      }
    };
    this.getState = function(constraint) {
      runtime.assert(constraints.hasOwnProperty(constraint) === true, "No such constraint");
      return constraints[constraint];
    };
  };
  gui.BlacklistNamespaceNodeFilter = function(excludedNamespaces) {
    var excludedNamespacesObj = {}, FILTER_REJECT = NodeFilter.FILTER_REJECT, FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT;
    this.acceptNode = function(node) {
      if (!node || excludedNamespacesObj.hasOwnProperty(node.namespaceURI)) {
        return FILTER_REJECT;
      }
      return FILTER_ACCEPT;
    };
    function init() {
      excludedNamespaces.forEach(function(ns) {
        excludedNamespacesObj[ns] = true;
      });
    }
    init();
  };
  odf.Namespaces = {namespaceMap:{config:"urn:oasis:names:tc:opendocument:xmlns:config:1.0", db:"urn:oasis:names:tc:opendocument:xmlns:database:1.0", dc:"http://purl.org/dc/elements/1.1/", dr3d:"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0", draw:"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", chart:"urn:oasis:names:tc:opendocument:xmlns:chart:1.0", fo:"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0", form:"urn:oasis:names:tc:opendocument:xmlns:form:1.0", math:"http://www.w3.org/1998/Math/MathML",
  meta:"urn:oasis:names:tc:opendocument:xmlns:meta:1.0", number:"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0", office:"urn:oasis:names:tc:opendocument:xmlns:office:1.0", presentation:"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0", style:"urn:oasis:names:tc:opendocument:xmlns:style:1.0", svg:"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0", table:"urn:oasis:names:tc:opendocument:xmlns:table:1.0", text:"urn:oasis:names:tc:opendocument:xmlns:text:1.0", xforms:"http://www.w3.org/2002/xforms",
  xlink:"http://www.w3.org/1999/xlink", xml:"http://www.w3.org/XML/1998/namespace"}, prefixMap:{}, configns:"urn:oasis:names:tc:opendocument:xmlns:config:1.0", dbns:"urn:oasis:names:tc:opendocument:xmlns:database:1.0", dcns:"http://purl.org/dc/elements/1.1/", dr3dns:"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0", drawns:"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", chartns:"urn:oasis:names:tc:opendocument:xmlns:chart:1.0", fons:"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
  formns:"urn:oasis:names:tc:opendocument:xmlns:form:1.0", mathns:"http://www.w3.org/1998/Math/MathML", metans:"urn:oasis:names:tc:opendocument:xmlns:meta:1.0", numberns:"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0", officens:"urn:oasis:names:tc:opendocument:xmlns:office:1.0", presentationns:"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0", stylens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0", svgns:"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0", tablens:"urn:oasis:names:tc:opendocument:xmlns:table:1.0",
  textns:"urn:oasis:names:tc:opendocument:xmlns:text:1.0", xformsns:"http://www.w3.org/2002/xforms", xlinkns:"http://www.w3.org/1999/xlink", xmlns:"http://www.w3.org/XML/1998/namespace"};
  (function() {
    var map = odf.Namespaces.namespaceMap, pmap = odf.Namespaces.prefixMap, prefix;
    for (prefix in map) {
      if (map.hasOwnProperty(prefix)) {
        pmap[map[prefix]] = prefix;
      }
    }
  })();
  odf.Namespaces.forEachPrefix = function forEachPrefix(cb) {
    var ns = odf.Namespaces.namespaceMap, prefix;
    for (prefix in ns) {
      if (ns.hasOwnProperty(prefix)) {
        cb(prefix, ns[prefix]);
      }
    }
  };
  odf.Namespaces.lookupNamespaceURI = function lookupNamespaceURI(prefix) {
    var r = null;
    if (odf.Namespaces.namespaceMap.hasOwnProperty(prefix)) {
      r = odf.Namespaces.namespaceMap[prefix];
    }
    return r;
  };
  odf.Namespaces.lookupPrefix = function lookupPrefix(namespaceURI) {
    var map = odf.Namespaces.prefixMap;
    return map.hasOwnProperty(namespaceURI) ? map[namespaceURI] : null;
  };
  odf.Namespaces.lookupNamespaceURI.lookupNamespaceURI = odf.Namespaces.lookupNamespaceURI;
  (function() {
    odf.OdfSchemaImpl = function() {
      var TEXT = "text", FIELD = "field", OBJECT = "object", STYLE = "style", DEPRECATED = "deprecated", UNKNOWN = "uncategorized", containers = [["config:config-item", UNKNOWN], ["form:item", OBJECT], ["form:option", UNKNOWN], ["math:math", FIELD], ["meta:user-defined", UNKNOWN], ["number:currency-symbol", UNKNOWN], ["number:embedded-text", UNKNOWN], ["number:text", UNKNOWN], ["presentation:date-time-decl", UNKNOWN], ["presentation:footer-decl", UNKNOWN], ["presentation:header-decl", UNKNOWN], ["svg:desc",
      TEXT], ["svg:title", TEXT], ["table:desc", UNKNOWN], ["table:title", UNKNOWN], ["text:a", TEXT], ["text:author-initials", FIELD], ["text:author-name", FIELD], ["text:bibliography-mark", FIELD], ["text:bookmark-ref", FIELD], ["text:chapter", FIELD], ["text:character-count", FIELD], ["text:conditional-text", FIELD], ["text:creation-date", FIELD], ["text:creation-time", FIELD], ["text:creator", FIELD], ["text:database-display", FIELD], ["text:database-name", FIELD], ["text:database-row-number",
      FIELD], ["text:date", FIELD], ["text:dde-connection", FIELD], ["text:description", FIELD], ["text:editing-cycles", FIELD], ["text:editing-duration", FIELD], ["text:execute-macro", UNKNOWN], ["text:expression", UNKNOWN], ["text:file-name", FIELD], ["text:h", TEXT], ["text:hidden-paragraph", TEXT], ["text:hidden-text", TEXT], ["text:image-count", FIELD], ["text:index-entry-span", UNKNOWN], ["text:index-title-template", UNKNOWN], ["text:initial-creator", FIELD], ["text:keywords", FIELD], ["text:linenumbering-separator",
      STYLE], ["text:measure", UNKNOWN], ["text:meta", UNKNOWN], ["text:meta-field", UNKNOWN], ["text:modification-date", FIELD], ["text:modification-time", FIELD], ["text:note-citation", FIELD], ["text:note-continuation-notice-backward", STYLE], ["text:note-continuation-notice-forward", STYLE], ["text:note-ref", FIELD], ["text:object-count", FIELD], ["text:p", TEXT], ["text:page-continuation", UNKNOWN], ["text:page-count", FIELD], ["text:page-number", FIELD], ["text:page-variable-get", FIELD], ["text:page-variable-set",
      FIELD], ["text:paragraph-count", FIELD], ["text:placeholder", FIELD], ["text:print-date", FIELD], ["text:print-time", FIELD], ["text:printed-by", FIELD], ["text:reference-ref", FIELD], ["text:ruby-base", TEXT], ["text:ruby-text", TEXT], ["text:script", TEXT], ["text:sender-city", FIELD], ["text:sender-company", FIELD], ["text:sender-country", FIELD], ["text:sender-email", FIELD], ["text:sender-fax", FIELD], ["text:sender-firstname", FIELD], ["text:sender-initials", FIELD], ["text:sender-lastname",
      FIELD], ["text:sender-phone-private", FIELD], ["text:sender-phone-work", FIELD], ["text:sender-position", FIELD], ["text:sender-postal-code", FIELD], ["text:sender-state-or-province", FIELD], ["text:sender-street", FIELD], ["text:sender-title", FIELD], ["text:sequence", UNKNOWN], ["text:sequence-ref", UNKNOWN], ["text:sheet-name", UNKNOWN], ["text:span", TEXT], ["text:subject", FIELD], ["text:table-count", FIELD], ["text:table-formula", DEPRECATED], ["text:template-name", UNKNOWN], ["text:text-input",
      FIELD], ["text:time", FIELD], ["text:title", FIELD], ["text:user-defined", FIELD], ["text:user-field-get", FIELD], ["text:user-field-input", FIELD], ["text:variable-get", FIELD], ["text:variable-input", FIELD], ["text:variable-set", FIELD], ["text:word-count", FIELD], ["xforms:model", UNKNOWN]], cache = {};
      this.isTextContainer = function(namespaceURI, localName) {
        return cache[namespaceURI + ":" + localName] === TEXT;
      };
      this.isField = function(namespaceURI, localName) {
        return cache[namespaceURI + ":" + localName] === FIELD;
      };
      this.getFields = function() {
        return containers.filter(function(containerInfo) {
          return containerInfo[1] === FIELD;
        }).map(function(containerInfo) {
          return containerInfo[0];
        });
      };
      function init() {
        containers.forEach(function(containerInfo) {
          var name = containerInfo[0], type = containerInfo[1], nameParts = name.split(":"), prefix = nameParts[0], localName = nameParts[1], namespaceURI = odf.Namespaces.lookupNamespaceURI(prefix);
          if (namespaceURI) {
            cache[namespaceURI + ":" + localName] = type;
          } else {
            runtime.log("DEBUG: OdfSchema - unknown prefix '" + prefix + "'");
          }
        });
      }
      init();
    };
    odf.OdfSchema = new odf.OdfSchemaImpl;
  })();
  odf.OdfUtilsImpl = function OdfUtilsImpl() {
    var textns = odf.Namespaces.textns, drawns = odf.Namespaces.drawns, xlinkns = odf.Namespaces.xlinkns, domUtils = core.DomUtils, odfNodeNamespaceMap = [odf.Namespaces.dbns, odf.Namespaces.dcns, odf.Namespaces.dr3dns, odf.Namespaces.drawns, odf.Namespaces.chartns, odf.Namespaces.formns, odf.Namespaces.numberns, odf.Namespaces.officens, odf.Namespaces.presentationns, odf.Namespaces.stylens, odf.Namespaces.svgns, odf.Namespaces.tablens, odf.Namespaces.textns], odfSchema = odf.OdfSchema;
    function isImage(e) {
      var name = e && e.localName;
      return name === "image" && e.namespaceURI === drawns;
    }
    this.isImage = isImage;
    function isCharacterFrame(e) {
      return e !== null && e.nodeType === Node.ELEMENT_NODE && e.localName === "frame" && e.namespaceURI === drawns && e.getAttributeNS(textns, "anchor-type") === "as-char";
    }
    this.isCharacterFrame = isCharacterFrame;
    function isAnnotation(e) {
      var name = e && e.localName;
      return name === "annotation" && e.namespaceURI === odf.Namespaces.officens;
    }
    function isAnnotationWrapper(e) {
      var name = e && e.localName;
      return name === "div" && e.className === "annotationWrapper";
    }
    function isInlineRoot(e) {
      return isAnnotation(e) || isAnnotationWrapper(e);
    }
    this.isInlineRoot = isInlineRoot;
    this.isTextSpan = function(e) {
      var name = e && e.localName;
      return name === "span" && e.namespaceURI === textns;
    };
    function isHyperlink(node) {
      var name = node && node.localName;
      return name === "a" && node.namespaceURI === textns;
    }
    this.isHyperlink = isHyperlink;
    this.getHyperlinkTarget = function(element) {
      return element.getAttributeNS(xlinkns, "href") || "";
    };
    function isParagraph(e) {
      var name = e && e.localName;
      return (name === "p" || name === "h") && e.namespaceURI === textns;
    }
    this.isParagraph = isParagraph;
    function getParagraphElement(node, offset) {
      if (node && offset !== undefined && !isParagraph(node) && node.childNodes.item(offset)) {
        node = node.childNodes.item(offset);
      }
      while (node && !isParagraph(node)) {
        node = node.parentNode;
      }
      return node;
    }
    this.getParagraphElement = getParagraphElement;
    function getParentAnnotation(node, container) {
      while (node && node !== container) {
        if (node.namespaceURI === odf.Namespaces.officens && node.localName === "annotation") {
          return node;
        }
        node = node.parentNode;
      }
      return null;
    }
    this.getParentAnnotation = getParentAnnotation;
    this.isWithinAnnotation = function(node, container) {
      return Boolean(getParentAnnotation(node, container));
    };
    this.getAnnotationCreator = function(annotationElement) {
      var creatorElement = annotationElement.getElementsByTagNameNS(odf.Namespaces.dcns, "creator")[0];
      return creatorElement.textContent;
    };
    this.isListItem = function(e) {
      var name = e && e.localName;
      return name === "list-item" && e.namespaceURI === textns;
    };
    this.isLineBreak = function(e) {
      var name = e && e.localName;
      return name === "line-break" && e.namespaceURI === textns;
    };
    function isODFWhitespace(text) {
      return /^[ \t\r\n]+$/.test(text);
    }
    this.isODFWhitespace = isODFWhitespace;
    function isGroupingElement(n) {
      if (n === null || n.nodeType !== Node.ELEMENT_NODE) {
        return false;
      }
      var e = n, localName = e.localName;
      return odfSchema.isTextContainer(e.namespaceURI, localName) || localName === "span" && e.className === "webodf-annotationHighlight";
    }
    this.isGroupingElement = isGroupingElement;
    function isFieldElement(n) {
      if (n === null || n.nodeType !== Node.ELEMENT_NODE) {
        return false;
      }
      var e = n, localName = e.localName;
      return odfSchema.isField(e.namespaceURI, localName);
    }
    this.isFieldElement = isFieldElement;
    function isCharacterElement(e) {
      var n = e && e.localName, ns, r = false;
      if (n) {
        ns = e.namespaceURI;
        if (ns === textns) {
          r = n === "s" || n === "tab" || n === "line-break";
        }
      }
      return r;
    }
    this.isCharacterElement = isCharacterElement;
    function isAnchoredAsCharacterElement(e) {
      return isCharacterElement(e) || isFieldElement(e) || isCharacterFrame(e) || isInlineRoot(e);
    }
    this.isAnchoredAsCharacterElement = isAnchoredAsCharacterElement;
    function isSpaceElement(e) {
      var n = e && e.localName, ns, r = false;
      if (n) {
        ns = e.namespaceURI;
        if (ns === textns) {
          r = n === "s";
        }
      }
      return r;
    }
    this.isSpaceElement = isSpaceElement;
    function isODFNode(node) {
      return odfNodeNamespaceMap.indexOf(node.namespaceURI) !== -1;
    }
    this.isODFNode = isODFNode;
    function hasNoODFContent(node) {
      var childNode;
      if (isCharacterElement(node) || isFieldElement(node)) {
        return false;
      }
      if (isGroupingElement(node.parentNode) && node.nodeType === Node.TEXT_NODE) {
        return node.textContent.length === 0;
      }
      childNode = node.firstChild;
      while (childNode) {
        if (isODFNode(childNode) || !hasNoODFContent(childNode)) {
          return false;
        }
        childNode = childNode.nextSibling;
      }
      return true;
    }
    this.hasNoODFContent = hasNoODFContent;
    function firstChild(node) {
      while (node.firstChild !== null && isGroupingElement(node)) {
        node = node.firstChild;
      }
      return node;
    }
    this.firstChild = firstChild;
    function lastChild(node) {
      while (node.lastChild !== null && isGroupingElement(node)) {
        node = node.lastChild;
      }
      return node;
    }
    this.lastChild = lastChild;
    function previousNode(node) {
      while (!isParagraph(node) && node.previousSibling === null) {
        node = node.parentNode;
      }
      return isParagraph(node) ? null : lastChild(node.previousSibling);
    }
    this.previousNode = previousNode;
    function nextNode(node) {
      while (!isParagraph(node) && node.nextSibling === null) {
        node = node.parentNode;
      }
      return isParagraph(node) ? null : firstChild(node.nextSibling);
    }
    this.nextNode = nextNode;
    function scanLeftForNonSpace(node) {
      var r = false, text;
      while (node) {
        if (node.nodeType === Node.TEXT_NODE) {
          text = node;
          if (text.length === 0) {
            node = previousNode(text);
          } else {
            return !isODFWhitespace(text.data.substr(text.length - 1, 1));
          }
        } else {
          if (isAnchoredAsCharacterElement(node)) {
            r = isSpaceElement(node) === false;
            node = null;
          } else {
            node = previousNode(node);
          }
        }
      }
      return r;
    }
    this.scanLeftForNonSpace = scanLeftForNonSpace;
    function lookLeftForCharacter(node) {
      var text, r = 0, tl = 0;
      if (node.nodeType === Node.TEXT_NODE) {
        tl = node.length;
      }
      if (tl > 0) {
        text = node.data;
        if (!isODFWhitespace(text.substr(tl - 1, 1))) {
          r = 1;
        } else {
          if (tl === 1) {
            r = scanLeftForNonSpace(previousNode(node)) ? 2 : 0;
          } else {
            r = isODFWhitespace(text.substr(tl - 2, 1)) ? 0 : 2;
          }
        }
      } else {
        if (isAnchoredAsCharacterElement(node)) {
          r = 1;
        }
      }
      return r;
    }
    this.lookLeftForCharacter = lookLeftForCharacter;
    function lookRightForCharacter(node) {
      var r = false, l = 0;
      if (node && node.nodeType === Node.TEXT_NODE) {
        l = node.length;
      }
      if (l > 0) {
        r = !isODFWhitespace(node.data.substr(0, 1));
      } else {
        if (isAnchoredAsCharacterElement(node)) {
          r = true;
        }
      }
      return r;
    }
    this.lookRightForCharacter = lookRightForCharacter;
    function scanLeftForAnyCharacter(node) {
      var r = false, l;
      node = node && lastChild(node);
      while (node) {
        if (node.nodeType === Node.TEXT_NODE) {
          l = node.length;
        } else {
          l = 0;
        }
        if (l > 0 && !isODFWhitespace(node.data)) {
          r = true;
          break;
        }
        if (isAnchoredAsCharacterElement(node)) {
          r = true;
          break;
        }
        node = previousNode(node);
      }
      return r;
    }
    this.scanLeftForAnyCharacter = scanLeftForAnyCharacter;
    function scanRightForAnyCharacter(node) {
      var r = false, l;
      node = node && firstChild(node);
      while (node) {
        if (node.nodeType === Node.TEXT_NODE) {
          l = node.length;
        } else {
          l = 0;
        }
        if (l > 0 && !isODFWhitespace(node.data)) {
          r = true;
          break;
        }
        if (isAnchoredAsCharacterElement(node)) {
          r = true;
          break;
        }
        node = nextNode(node);
      }
      return r;
    }
    this.scanRightForAnyCharacter = scanRightForAnyCharacter;
    function isTrailingWhitespace(textnode, offset) {
      if (!isODFWhitespace(textnode.data.substr(offset))) {
        return false;
      }
      return !scanRightForAnyCharacter(nextNode(textnode));
    }
    this.isTrailingWhitespace = isTrailingWhitespace;
    function isSignificantWhitespace(textNode, offset) {
      var text = textNode.data, result;
      if (!isODFWhitespace(text[offset])) {
        return false;
      }
      if (isAnchoredAsCharacterElement(textNode.parentNode)) {
        return false;
      }
      if (offset > 0) {
        if (!isODFWhitespace(text[offset - 1])) {
          result = true;
        }
      } else {
        if (scanLeftForNonSpace(previousNode(textNode))) {
          result = true;
        }
      }
      if (result === true) {
        return isTrailingWhitespace(textNode, offset) ? false : true;
      }
      return false;
    }
    this.isSignificantWhitespace = isSignificantWhitespace;
    this.isDowngradableSpaceElement = function(node) {
      if (isSpaceElement(node)) {
        return scanLeftForNonSpace(previousNode(node)) && scanRightForAnyCharacter(nextNode(node));
      }
      return false;
    };
    function parseLength(length) {
      var re = /(-?[0-9]*[0-9][0-9]*(\.[0-9]*)?|0+\.[0-9]*[1-9][0-9]*|\.[0-9]*[1-9][0-9]*)((cm)|(mm)|(in)|(pt)|(pc)|(px)|(%))/, m = re.exec(length);
      if (!m) {
        return null;
      }
      return {value:parseFloat(m[1]), unit:m[3]};
    }
    this.parseLength = parseLength;
    function parsePositiveLength(length) {
      var result = parseLength(length);
      if (result && (result.value <= 0 || result.unit === "%")) {
        return null;
      }
      return result;
    }
    function parseNonNegativeLength(length) {
      var result = parseLength(length);
      if (result && (result.value < 0 || result.unit === "%")) {
        return null;
      }
      return result;
    }
    this.parseNonNegativeLength = parseNonNegativeLength;
    function parsePercentage(length) {
      var result = parseLength(length);
      if (result && result.unit !== "%") {
        return null;
      }
      return result;
    }
    function parseFoFontSize(fontSize) {
      return parsePositiveLength(fontSize) || parsePercentage(fontSize);
    }
    this.parseFoFontSize = parseFoFontSize;
    function parseFoLineHeight(lineHeight) {
      return parseNonNegativeLength(lineHeight) || parsePercentage(lineHeight);
    }
    this.parseFoLineHeight = parseFoLineHeight;
    function isTextContentContainingNode(node) {
      switch(node.namespaceURI) {
        case odf.Namespaces.drawns:
        ;
        case odf.Namespaces.svgns:
        ;
        case odf.Namespaces.dr3dns:
          return false;
        case odf.Namespaces.textns:
          switch(node.localName) {
            case "note-body":
            ;
            case "ruby-text":
              return false;
          }
          break;
        case odf.Namespaces.officens:
          switch(node.localName) {
            case "annotation":
            ;
            case "binary-data":
            ;
            case "event-listeners":
              return false;
          }
          break;
        default:
          switch(node.localName) {
            case "cursor":
            ;
            case "editinfo":
              return false;
          }
          break;
      }
      return true;
    }
    this.isTextContentContainingNode = isTextContentContainingNode;
    function isSignificantTextContent(textNode) {
      return Boolean(getParagraphElement(textNode) && (!isODFWhitespace(textNode.textContent) || isSignificantWhitespace(textNode, 0)));
    }
    function removePartiallyContainedNodes(range, nodes) {
      while (nodes.length > 0 && !domUtils.rangeContainsNode(range, nodes[0])) {
        nodes.shift();
      }
      while (nodes.length > 0 && !domUtils.rangeContainsNode(range, nodes[nodes.length - 1])) {
        nodes.pop();
      }
    }
    function getTextNodes(range, includePartial) {
      var textNodes;
      function nodeFilter(node) {
        var result = NodeFilter.FILTER_REJECT;
        if (node.nodeType === Node.TEXT_NODE) {
          if (isSignificantTextContent(node)) {
            result = NodeFilter.FILTER_ACCEPT;
          }
        } else {
          if (isTextContentContainingNode(node)) {
            result = NodeFilter.FILTER_SKIP;
          }
        }
        return result;
      }
      textNodes = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
      if (!includePartial) {
        removePartiallyContainedNodes(range, textNodes);
      }
      return textNodes;
    }
    this.getTextNodes = getTextNodes;
    function getTextElements(range, includePartial, includeInsignificantWhitespace) {
      var elements;
      function nodeFilter(node) {
        var result = NodeFilter.FILTER_REJECT;
        if (isCharacterElement(node.parentNode) || isFieldElement(node.parentNode) || isInlineRoot(node)) {
          result = NodeFilter.FILTER_REJECT;
        } else {
          if (node.nodeType === Node.TEXT_NODE) {
            if (includeInsignificantWhitespace || isSignificantTextContent(node)) {
              result = NodeFilter.FILTER_ACCEPT;
            }
          } else {
            if (isAnchoredAsCharacterElement(node)) {
              result = NodeFilter.FILTER_ACCEPT;
            } else {
              if (isTextContentContainingNode(node) || isGroupingElement(node)) {
                result = NodeFilter.FILTER_SKIP;
              }
            }
          }
        }
        return result;
      }
      elements = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
      if (!includePartial) {
        removePartiallyContainedNodes(range, elements);
      }
      return elements;
    }
    this.getTextElements = getTextElements;
    function prependParentContainers(startContainer, elements, filter) {
      var container = startContainer;
      while (container) {
        if (filter(container)) {
          if (elements[0] !== container) {
            elements.unshift(container);
          }
          break;
        }
        if (isInlineRoot(container)) {
          break;
        }
        container = container.parentNode;
      }
    }
    this.getParagraphElements = function(range) {
      var elements;
      function nodeFilter(node) {
        var result = NodeFilter.FILTER_REJECT;
        if (isParagraph(node)) {
          result = NodeFilter.FILTER_ACCEPT;
        } else {
          if (isTextContentContainingNode(node) || isGroupingElement(node)) {
            result = NodeFilter.FILTER_SKIP;
          }
        }
        return result;
      }
      elements = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT);
      prependParentContainers(range.startContainer, elements, isParagraph);
      return elements;
    };
    this.getImageElements = function(range) {
      var elements;
      function nodeFilter(node) {
        var result = NodeFilter.FILTER_SKIP;
        if (isImage(node)) {
          result = NodeFilter.FILTER_ACCEPT;
        }
        return result;
      }
      elements = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT);
      prependParentContainers(range.startContainer, elements, isImage);
      return elements;
    };
    function getRightNode(container, offset) {
      var node = container;
      if (offset < node.childNodes.length - 1) {
        node = node.childNodes[offset + 1];
      } else {
        while (!node.nextSibling) {
          node = node.parentNode;
        }
        node = node.nextSibling;
      }
      while (node.firstChild) {
        node = node.firstChild;
      }
      return node;
    }
    this.getHyperlinkElements = function(range) {
      var links = [], newRange = range.cloneRange(), node, textNodes;
      if (range.collapsed && range.endContainer.nodeType === Node.ELEMENT_NODE) {
        node = getRightNode(range.endContainer, range.endOffset);
        if (node.nodeType === Node.TEXT_NODE) {
          newRange.setEnd(node, 1);
        }
      }
      textNodes = getTextElements(newRange, true, false);
      textNodes.forEach(function(node) {
        var parent = node.parentNode;
        while (!isParagraph(parent)) {
          if (isHyperlink(parent) && links.indexOf(parent) === -1) {
            links.push(parent);
            break;
          }
          parent = parent.parentNode;
        }
      });
      newRange.detach();
      return links;
    };
    this.getNormalizedFontFamilyName = function(fontFamilyName) {
      if (!/^(["'])(?:.|[\n\r])*?\1$/.test(fontFamilyName)) {
        fontFamilyName = fontFamilyName.replace(/^[ \t\r\n\f]*((?:.|[\n\r])*?)[ \t\r\n\f]*$/, "$1");
        if (/[ \t\r\n\f]/.test(fontFamilyName)) {
          fontFamilyName = "'" + fontFamilyName.replace(/[ \t\r\n\f]+/g, " ") + "'";
        }
      }
      return fontFamilyName;
    };
  };
  odf.OdfUtils = new odf.OdfUtilsImpl;
  gui.OdfTextBodyNodeFilter = function() {
    var odfUtils = odf.OdfUtils, TEXT_NODE = Node.TEXT_NODE, FILTER_REJECT = NodeFilter.FILTER_REJECT, FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT, textns = odf.Namespaces.textns;
    this.acceptNode = function(node) {
      if (node.nodeType === TEXT_NODE) {
        if (!odfUtils.isGroupingElement(node.parentNode)) {
          return FILTER_REJECT;
        }
      } else {
        if (node.namespaceURI === textns && node.localName === "tracked-changes") {
          return FILTER_REJECT;
        }
      }
      return FILTER_ACCEPT;
    };
  };
  xmldom.LSSerializerFilter = function LSSerializerFilter() {
  };
  xmldom.LSSerializerFilter.prototype.acceptNode = function(node) {
  };
  odf.OdfNodeFilter = function OdfNodeFilter() {
    this.acceptNode = function(node) {
      var result;
      if (node.namespaceURI === "http://www.w3.org/1999/xhtml") {
        result = NodeFilter.FILTER_SKIP;
      } else {
        if (node.namespaceURI && node.namespaceURI.match(/^urn:webodf:/)) {
          result = NodeFilter.FILTER_REJECT;
        } else {
          result = NodeFilter.FILTER_ACCEPT;
        }
      }
      return result;
    };
  };
  xmldom.XPathIterator = function XPathIterator() {
  };
  xmldom.XPathIterator.prototype.next = function() {
  };
  xmldom.XPathIterator.prototype.reset = function() {
  };
  xmldom.XPathAtom;
  function createXPathSingleton() {
    var createXPathPathIterator, parsePredicates;
    function isSmallestPositive(a, b, c) {
      return a !== -1 && (a < b || b === -1) && (a < c || c === -1);
    }
    function parseXPathStep(xpath, pos, end, steps) {
      var location = "", predicates = [], brapos = xpath.indexOf("[", pos), slapos = xpath.indexOf("/", pos), eqpos = xpath.indexOf("=", pos);
      if (isSmallestPositive(slapos, brapos, eqpos)) {
        location = xpath.substring(pos, slapos);
        pos = slapos + 1;
      } else {
        if (isSmallestPositive(brapos, slapos, eqpos)) {
          location = xpath.substring(pos, brapos);
          pos = parsePredicates(xpath, brapos, predicates);
        } else {
          if (isSmallestPositive(eqpos, slapos, brapos)) {
            location = xpath.substring(pos, eqpos);
            pos = eqpos;
          } else {
            location = xpath.substring(pos, end);
            pos = end;
          }
        }
      }
      steps.push({location:location, predicates:predicates});
      return pos;
    }
    function parseXPath(xpath) {
      var steps = [], p = 0, end = xpath.length, value;
      while (p < end) {
        p = parseXPathStep(xpath, p, end, steps);
        if (p < end && xpath[p] === "=") {
          value = xpath.substring(p + 1, end);
          if (value.length > 2 && (value[0] === "'" || value[0] === '"')) {
            value = value.slice(1, value.length - 1);
          } else {
            try {
              value = parseInt(value, 10);
            } catch (ignore) {
            }
          }
          p = end;
        }
      }
      return {steps:steps, value:value};
    }
    parsePredicates = function parsePredicates(xpath, start, predicates) {
      var pos = start, l = xpath.length, depth = 0;
      while (pos < l) {
        if (xpath[pos] === "]") {
          depth -= 1;
          if (depth <= 0) {
            predicates.push(parseXPath(xpath.substring(start, pos)));
          }
        } else {
          if (xpath[pos] === "[") {
            if (depth <= 0) {
              start = pos + 1;
            }
            depth += 1;
          }
        }
        pos += 1;
      }
      return pos;
    };
    function XPathNodeIterator() {
      var node = null, done = false;
      this.setNode = function setNode(n) {
        node = n;
      };
      this.reset = function() {
        done = false;
      };
      this.next = function next() {
        var val = done ? null : node;
        done = true;
        return val;
      };
    }
    function AttributeIterator(it, namespace, localName) {
      this.reset = function reset() {
        it.reset();
      };
      this.next = function next() {
        var node = it.next();
        while (node) {
          if (node.nodeType === Node.ELEMENT_NODE) {
            node = node.getAttributeNodeNS(namespace, localName);
          }
          if (node) {
            return node;
          }
          node = it.next();
        }
        return node;
      };
    }
    function AllChildElementIterator(it, recurse) {
      var root = it.next(), node = null;
      this.reset = function reset() {
        it.reset();
        root = it.next();
        node = null;
      };
      this.next = function next() {
        while (root) {
          if (node) {
            if (recurse && node.firstChild) {
              node = node.firstChild;
            } else {
              while (!node.nextSibling && node !== root) {
                node = node.parentNode;
              }
              if (node === root) {
                root = it.next();
              } else {
                node = node.nextSibling;
              }
            }
          } else {
            do {
              node = root.firstChild;
              if (!node) {
                root = it.next();
              }
            } while (root && !node);
          }
          if (node && node.nodeType === Node.ELEMENT_NODE) {
            return node;
          }
        }
        return null;
      };
    }
    function ConditionIterator(it, condition) {
      this.reset = function reset() {
        it.reset();
      };
      this.next = function next() {
        var n = it.next();
        while (n && !condition(n)) {
          n = it.next();
        }
        return n;
      };
    }
    function createNodenameFilter(it, name, namespaceResolver) {
      var s = name.split(":", 2), namespace = namespaceResolver(s[0]), localName = s[1];
      return new ConditionIterator(it, function(node) {
        return node.localName === localName && node.namespaceURI === namespace;
      });
    }
    function createPredicateFilteredIterator(it, p, namespaceResolver) {
      var nit = new XPathNodeIterator, pit = createXPathPathIterator(nit, p, namespaceResolver), value = p.value;
      if (value === undefined) {
        return new ConditionIterator(it, function(node) {
          nit.setNode(node);
          pit.reset();
          return pit.next() !== null;
        });
      }
      return new ConditionIterator(it, function(node) {
        nit.setNode(node);
        pit.reset();
        var n = pit.next();
        return n ? n.nodeValue === value : false;
      });
    }
    function item(p, i) {
      return p[i];
    }
    createXPathPathIterator = function createXPathPathIterator(it, xpath, namespaceResolver) {
      var i, j, step, location, s, p, ns;
      for (i = 0;i < xpath.steps.length;i += 1) {
        step = xpath.steps[i];
        location = step.location;
        if (location === "") {
          it = new AllChildElementIterator(it, false);
        } else {
          if (location[0] === "@") {
            s = location.substr(1).split(":", 2);
            ns = namespaceResolver(s[0]);
            if (!ns) {
              throw "No namespace associated with the prefix " + s[0];
            }
            it = new AttributeIterator(it, ns, s[1]);
          } else {
            if (location !== ".") {
              it = new AllChildElementIterator(it, false);
              if (location.indexOf(":") !== -1) {
                it = createNodenameFilter(it, location, namespaceResolver);
              }
            }
          }
        }
        for (j = 0;j < step.predicates.length;j += 1) {
          p = item(step.predicates, j);
          it = createPredicateFilteredIterator(it, p, namespaceResolver);
        }
      }
      return it;
    };
    function fallback(node, xpath, namespaceResolver) {
      var it = new XPathNodeIterator, i, nodelist, parsedXPath;
      it.setNode(node);
      parsedXPath = parseXPath(xpath);
      it = createXPathPathIterator(it, parsedXPath, namespaceResolver);
      nodelist = [];
      i = it.next();
      while (i) {
        nodelist.push(i);
        i = it.next();
      }
      return nodelist;
    }
    function getODFElementsWithXPath(node, xpath, namespaceResolver) {
      var doc = node.ownerDocument, nodes, elements = [], n = null;
      if (!doc || typeof doc.evaluate !== "function") {
        elements = fallback(node, xpath, namespaceResolver);
      } else {
        nodes = doc.evaluate(xpath, node, namespaceResolver, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
        n = nodes.iterateNext();
        while (n !== null) {
          if (n.nodeType === Node.ELEMENT_NODE) {
            elements.push(n);
          }
          n = nodes.iterateNext();
        }
      }
      return elements;
    }
    return {getODFElementsWithXPath:getODFElementsWithXPath};
  }
  xmldom.XPath = createXPathSingleton();
  odf.StyleInfo = function StyleInfo() {
    var chartns = odf.Namespaces.chartns, dbns = odf.Namespaces.dbns, dr3dns = odf.Namespaces.dr3dns, drawns = odf.Namespaces.drawns, formns = odf.Namespaces.formns, numberns = odf.Namespaces.numberns, officens = odf.Namespaces.officens, presentationns = odf.Namespaces.presentationns, stylens = odf.Namespaces.stylens, tablens = odf.Namespaces.tablens, textns = odf.Namespaces.textns, nsprefixes = {"urn:oasis:names:tc:opendocument:xmlns:chart:1.0":"chart:", "urn:oasis:names:tc:opendocument:xmlns:database:1.0":"db:",
    "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0":"dr3d:", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0":"draw:", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0":"fo:", "urn:oasis:names:tc:opendocument:xmlns:form:1.0":"form:", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0":"number:", "urn:oasis:names:tc:opendocument:xmlns:office:1.0":"office:", "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0":"presentation:", "urn:oasis:names:tc:opendocument:xmlns:style:1.0":"style:",
    "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0":"svg:", "urn:oasis:names:tc:opendocument:xmlns:table:1.0":"table:", "urn:oasis:names:tc:opendocument:xmlns:text:1.0":"chart:", "http://www.w3.org/XML/1998/namespace":"xml:"}, elementstyles = {"text":[{ens:stylens, en:"tab-stop", ans:stylens, a:"leader-text-style"}, {ens:stylens, en:"drop-cap", ans:stylens, a:"style-name"}, {ens:textns, en:"notes-configuration", ans:textns, a:"citation-body-style-name"}, {ens:textns, en:"notes-configuration",
    ans:textns, a:"citation-style-name"}, {ens:textns, en:"a", ans:textns, a:"style-name"}, {ens:textns, en:"alphabetical-index", ans:textns, a:"style-name"}, {ens:textns, en:"linenumbering-configuration", ans:textns, a:"style-name"}, {ens:textns, en:"list-level-style-number", ans:textns, a:"style-name"}, {ens:textns, en:"ruby-text", ans:textns, a:"style-name"}, {ens:textns, en:"span", ans:textns, a:"style-name"}, {ens:textns, en:"a", ans:textns, a:"visited-style-name"}, {ens:stylens, en:"text-properties",
    ans:stylens, a:"text-line-through-text-style"}, {ens:textns, en:"alphabetical-index-source", ans:textns, a:"main-entry-style-name"}, {ens:textns, en:"index-entry-bibliography", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-chapter", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-link-end", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-link-start", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-page-number", ans:textns, a:"style-name"}, {ens:textns,
    en:"index-entry-span", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-tab-stop", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-text", ans:textns, a:"style-name"}, {ens:textns, en:"index-title-template", ans:textns, a:"style-name"}, {ens:textns, en:"list-level-style-bullet", ans:textns, a:"style-name"}, {ens:textns, en:"outline-level-style", ans:textns, a:"style-name"}], "paragraph":[{ens:drawns, en:"caption", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"circle", ans:drawns,
    a:"text-style-name"}, {ens:drawns, en:"connector", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"control", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"custom-shape", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"ellipse", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"frame", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"line", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"measure", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"path", ans:drawns, a:"text-style-name"},
    {ens:drawns, en:"polygon", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"polyline", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"rect", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"regular-polygon", ans:drawns, a:"text-style-name"}, {ens:officens, en:"annotation", ans:drawns, a:"text-style-name"}, {ens:formns, en:"column", ans:formns, a:"text-style-name"}, {ens:stylens, en:"style", ans:stylens, a:"next-style-name"}, {ens:tablens, en:"body", ans:tablens, a:"paragraph-style-name"},
    {ens:tablens, en:"even-columns", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"even-rows", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"first-column", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"first-row", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"last-column", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"last-row", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"odd-columns", ans:tablens, a:"paragraph-style-name"},
    {ens:tablens, en:"odd-rows", ans:tablens, a:"paragraph-style-name"}, {ens:textns, en:"notes-configuration", ans:textns, a:"default-style-name"}, {ens:textns, en:"alphabetical-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"bibliography-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"h", ans:textns, a:"style-name"}, {ens:textns, en:"illustration-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"index-source-style", ans:textns, a:"style-name"},
    {ens:textns, en:"object-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"p", ans:textns, a:"style-name"}, {ens:textns, en:"table-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"table-of-content-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"table-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"user-index-entry-template", ans:textns, a:"style-name"}, {ens:stylens, en:"page-layout-properties", ans:stylens, a:"register-truth-ref-style-name"}],
    "chart":[{ens:chartns, en:"axis", ans:chartns, a:"style-name"}, {ens:chartns, en:"chart", ans:chartns, a:"style-name"}, {ens:chartns, en:"data-label", ans:chartns, a:"style-name"}, {ens:chartns, en:"data-point", ans:chartns, a:"style-name"}, {ens:chartns, en:"equation", ans:chartns, a:"style-name"}, {ens:chartns, en:"error-indicator", ans:chartns, a:"style-name"}, {ens:chartns, en:"floor", ans:chartns, a:"style-name"}, {ens:chartns, en:"footer", ans:chartns, a:"style-name"}, {ens:chartns, en:"grid",
    ans:chartns, a:"style-name"}, {ens:chartns, en:"legend", ans:chartns, a:"style-name"}, {ens:chartns, en:"mean-value", ans:chartns, a:"style-name"}, {ens:chartns, en:"plot-area", ans:chartns, a:"style-name"}, {ens:chartns, en:"regression-curve", ans:chartns, a:"style-name"}, {ens:chartns, en:"series", ans:chartns, a:"style-name"}, {ens:chartns, en:"stock-gain-marker", ans:chartns, a:"style-name"}, {ens:chartns, en:"stock-loss-marker", ans:chartns, a:"style-name"}, {ens:chartns, en:"stock-range-line",
    ans:chartns, a:"style-name"}, {ens:chartns, en:"subtitle", ans:chartns, a:"style-name"}, {ens:chartns, en:"title", ans:chartns, a:"style-name"}, {ens:chartns, en:"wall", ans:chartns, a:"style-name"}], "section":[{ens:textns, en:"alphabetical-index", ans:textns, a:"style-name"}, {ens:textns, en:"bibliography", ans:textns, a:"style-name"}, {ens:textns, en:"illustration-index", ans:textns, a:"style-name"}, {ens:textns, en:"index-title", ans:textns, a:"style-name"}, {ens:textns, en:"object-index",
    ans:textns, a:"style-name"}, {ens:textns, en:"section", ans:textns, a:"style-name"}, {ens:textns, en:"table-of-content", ans:textns, a:"style-name"}, {ens:textns, en:"table-index", ans:textns, a:"style-name"}, {ens:textns, en:"user-index", ans:textns, a:"style-name"}], "ruby":[{ens:textns, en:"ruby", ans:textns, a:"style-name"}], "table":[{ens:dbns, en:"query", ans:dbns, a:"style-name"}, {ens:dbns, en:"table-representation", ans:dbns, a:"style-name"}, {ens:tablens, en:"background", ans:tablens,
    a:"style-name"}, {ens:tablens, en:"table", ans:tablens, a:"style-name"}], "table-column":[{ens:dbns, en:"column", ans:dbns, a:"style-name"}, {ens:tablens, en:"table-column", ans:tablens, a:"style-name"}], "table-row":[{ens:dbns, en:"query", ans:dbns, a:"default-row-style-name"}, {ens:dbns, en:"table-representation", ans:dbns, a:"default-row-style-name"}, {ens:tablens, en:"table-row", ans:tablens, a:"style-name"}], "table-cell":[{ens:dbns, en:"column", ans:dbns, a:"default-cell-style-name"}, {ens:tablens,
    en:"table-column", ans:tablens, a:"default-cell-style-name"}, {ens:tablens, en:"table-row", ans:tablens, a:"default-cell-style-name"}, {ens:tablens, en:"body", ans:tablens, a:"style-name"}, {ens:tablens, en:"covered-table-cell", ans:tablens, a:"style-name"}, {ens:tablens, en:"even-columns", ans:tablens, a:"style-name"}, {ens:tablens, en:"covered-table-cell", ans:tablens, a:"style-name"}, {ens:tablens, en:"even-columns", ans:tablens, a:"style-name"}, {ens:tablens, en:"even-rows", ans:tablens, a:"style-name"},
    {ens:tablens, en:"first-column", ans:tablens, a:"style-name"}, {ens:tablens, en:"first-row", ans:tablens, a:"style-name"}, {ens:tablens, en:"last-column", ans:tablens, a:"style-name"}, {ens:tablens, en:"last-row", ans:tablens, a:"style-name"}, {ens:tablens, en:"odd-columns", ans:tablens, a:"style-name"}, {ens:tablens, en:"odd-rows", ans:tablens, a:"style-name"}, {ens:tablens, en:"table-cell", ans:tablens, a:"style-name"}], "graphic":[{ens:dr3dns, en:"cube", ans:drawns, a:"style-name"}, {ens:dr3dns,
    en:"extrude", ans:drawns, a:"style-name"}, {ens:dr3dns, en:"rotate", ans:drawns, a:"style-name"}, {ens:dr3dns, en:"scene", ans:drawns, a:"style-name"}, {ens:dr3dns, en:"sphere", ans:drawns, a:"style-name"}, {ens:drawns, en:"caption", ans:drawns, a:"style-name"}, {ens:drawns, en:"circle", ans:drawns, a:"style-name"}, {ens:drawns, en:"connector", ans:drawns, a:"style-name"}, {ens:drawns, en:"control", ans:drawns, a:"style-name"}, {ens:drawns, en:"custom-shape", ans:drawns, a:"style-name"}, {ens:drawns,
    en:"ellipse", ans:drawns, a:"style-name"}, {ens:drawns, en:"frame", ans:drawns, a:"style-name"}, {ens:drawns, en:"g", ans:drawns, a:"style-name"}, {ens:drawns, en:"line", ans:drawns, a:"style-name"}, {ens:drawns, en:"measure", ans:drawns, a:"style-name"}, {ens:drawns, en:"page-thumbnail", ans:drawns, a:"style-name"}, {ens:drawns, en:"path", ans:drawns, a:"style-name"}, {ens:drawns, en:"polygon", ans:drawns, a:"style-name"}, {ens:drawns, en:"polyline", ans:drawns, a:"style-name"}, {ens:drawns, en:"rect",
    ans:drawns, a:"style-name"}, {ens:drawns, en:"regular-polygon", ans:drawns, a:"style-name"}, {ens:officens, en:"annotation", ans:drawns, a:"style-name"}], "presentation":[{ens:dr3dns, en:"cube", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"extrude", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"rotate", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"scene", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"sphere", ans:presentationns, a:"style-name"}, {ens:drawns, en:"caption",
    ans:presentationns, a:"style-name"}, {ens:drawns, en:"circle", ans:presentationns, a:"style-name"}, {ens:drawns, en:"connector", ans:presentationns, a:"style-name"}, {ens:drawns, en:"control", ans:presentationns, a:"style-name"}, {ens:drawns, en:"custom-shape", ans:presentationns, a:"style-name"}, {ens:drawns, en:"ellipse", ans:presentationns, a:"style-name"}, {ens:drawns, en:"frame", ans:presentationns, a:"style-name"}, {ens:drawns, en:"g", ans:presentationns, a:"style-name"}, {ens:drawns, en:"line",
    ans:presentationns, a:"style-name"}, {ens:drawns, en:"measure", ans:presentationns, a:"style-name"}, {ens:drawns, en:"page-thumbnail", ans:presentationns, a:"style-name"}, {ens:drawns, en:"path", ans:presentationns, a:"style-name"}, {ens:drawns, en:"polygon", ans:presentationns, a:"style-name"}, {ens:drawns, en:"polyline", ans:presentationns, a:"style-name"}, {ens:drawns, en:"rect", ans:presentationns, a:"style-name"}, {ens:drawns, en:"regular-polygon", ans:presentationns, a:"style-name"}, {ens:officens,
    en:"annotation", ans:presentationns, a:"style-name"}], "drawing-page":[{ens:drawns, en:"page", ans:drawns, a:"style-name"}, {ens:presentationns, en:"notes", ans:drawns, a:"style-name"}, {ens:stylens, en:"handout-master", ans:drawns, a:"style-name"}, {ens:stylens, en:"master-page", ans:drawns, a:"style-name"}], "list-style":[{ens:textns, en:"list", ans:textns, a:"style-name"}, {ens:textns, en:"numbered-paragraph", ans:textns, a:"style-name"}, {ens:textns, en:"list-item", ans:textns, a:"style-override"},
    {ens:stylens, en:"style", ans:stylens, a:"list-style-name"}], "data":[{ens:stylens, en:"style", ans:stylens, a:"data-style-name"}, {ens:stylens, en:"style", ans:stylens, a:"percentage-data-style-name"}, {ens:presentationns, en:"date-time-decl", ans:stylens, a:"data-style-name"}, {ens:textns, en:"creation-date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"creation-time", ans:stylens, a:"data-style-name"}, {ens:textns, en:"database-display", ans:stylens, a:"data-style-name"}, {ens:textns,
    en:"date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"editing-duration", ans:stylens, a:"data-style-name"}, {ens:textns, en:"expression", ans:stylens, a:"data-style-name"}, {ens:textns, en:"meta-field", ans:stylens, a:"data-style-name"}, {ens:textns, en:"modification-date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"modification-time", ans:stylens, a:"data-style-name"}, {ens:textns, en:"print-date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"print-time", ans:stylens,
    a:"data-style-name"}, {ens:textns, en:"table-formula", ans:stylens, a:"data-style-name"}, {ens:textns, en:"time", ans:stylens, a:"data-style-name"}, {ens:textns, en:"user-defined", ans:stylens, a:"data-style-name"}, {ens:textns, en:"user-field-get", ans:stylens, a:"data-style-name"}, {ens:textns, en:"user-field-input", ans:stylens, a:"data-style-name"}, {ens:textns, en:"variable-get", ans:stylens, a:"data-style-name"}, {ens:textns, en:"variable-input", ans:stylens, a:"data-style-name"}, {ens:textns,
    en:"variable-set", ans:stylens, a:"data-style-name"}], "page-layout":[{ens:presentationns, en:"notes", ans:stylens, a:"page-layout-name"}, {ens:stylens, en:"handout-master", ans:stylens, a:"page-layout-name"}, {ens:stylens, en:"master-page", ans:stylens, a:"page-layout-name"}]}, elements, xpath = xmldom.XPath;
    function hasDerivedStyles(odfbody, nsResolver, styleElement) {
      var nodes, xp, styleName = styleElement.getAttributeNS(stylens, "name"), styleFamily = styleElement.getAttributeNS(stylens, "family");
      xp = '//style:*[@style:parent-style-name="' + styleName + '"][@style:family="' + styleFamily + '"]';
      nodes = xpath.getODFElementsWithXPath(odfbody, xp, nsResolver);
      if (nodes.length) {
        return true;
      }
      return false;
    }
    function prefixUsedStyleNames(element, prefix) {
      var i, stylename, a, e, ns, elname, elns, localName, length = 0;
      elname = elements[element.localName];
      if (elname) {
        elns = elname[element.namespaceURI];
        if (elns) {
          length = elns.length;
        }
      }
      for (i = 0;i < length;i += 1) {
        a = elns[i];
        ns = a.ns;
        localName = a.localname;
        stylename = element.getAttributeNS(ns, localName);
        if (stylename) {
          element.setAttributeNS(ns, nsprefixes[ns] + localName, prefix + stylename);
        }
      }
      e = element.firstElementChild;
      while (e) {
        prefixUsedStyleNames(e, prefix);
        e = e.nextElementSibling;
      }
    }
    function prefixStyleName(styleElement, prefix) {
      var stylename = styleElement.getAttributeNS(drawns, "name"), ns;
      if (stylename) {
        ns = drawns;
      } else {
        stylename = styleElement.getAttributeNS(stylens, "name");
        if (stylename) {
          ns = stylens;
        }
      }
      if (ns) {
        styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", prefix + stylename);
      }
    }
    function prefixStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) {
      var s;
      if (styleElementsRoot) {
        s = styleElementsRoot.firstChild;
        while (s) {
          if (s.nodeType === Node.ELEMENT_NODE) {
            prefixStyleName(s, prefix);
          }
          s = s.nextSibling;
        }
        prefixUsedStyleNames(styleElementsRoot, prefix);
        if (styleUsingElementsRoot) {
          prefixUsedStyleNames(styleUsingElementsRoot, prefix);
        }
      }
    }
    function removeRegExpFromUsedStyleNames(element, regExp) {
      var i, stylename, e, elname, elns, a, ns, localName, length = 0;
      elname = elements[element.localName];
      if (elname) {
        elns = elname[element.namespaceURI];
        if (elns) {
          length = elns.length;
        }
      }
      for (i = 0;i < length;i += 1) {
        a = elns[i];
        ns = a.ns;
        localName = a.localname;
        stylename = element.getAttributeNS(ns, localName);
        if (stylename) {
          stylename = stylename.replace(regExp, "");
          element.setAttributeNS(ns, nsprefixes[ns] + localName, stylename);
        }
      }
      e = element.firstElementChild;
      while (e) {
        removeRegExpFromUsedStyleNames(e, regExp);
        e = e.nextElementSibling;
      }
    }
    function removeRegExpFromStyleName(styleElement, regExp) {
      var stylename = styleElement.getAttributeNS(drawns, "name"), ns;
      if (stylename) {
        ns = drawns;
      } else {
        stylename = styleElement.getAttributeNS(stylens, "name");
        if (stylename) {
          ns = stylens;
        }
      }
      if (ns) {
        stylename = stylename.replace(regExp, "");
        styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", stylename);
      }
    }
    function removePrefixFromStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) {
      var s, regExp = new RegExp("^" + prefix);
      if (styleElementsRoot) {
        s = styleElementsRoot.firstChild;
        while (s) {
          if (s.nodeType === Node.ELEMENT_NODE) {
            removeRegExpFromStyleName(s, regExp);
          }
          s = s.nextSibling;
        }
        removeRegExpFromUsedStyleNames(styleElementsRoot, regExp);
        if (styleUsingElementsRoot) {
          removeRegExpFromUsedStyleNames(styleUsingElementsRoot, regExp);
        }
      }
    }
    function determineStylesForNode(element, usedStyles) {
      var i, stylename, elname, elns, a, ns, localName, keyname, length = 0, map;
      elname = elements[element.localName];
      if (elname) {
        elns = elname[element.namespaceURI];
        if (elns) {
          length = elns.length;
        }
      }
      for (i = 0;i < length;i += 1) {
        a = elns[i];
        ns = a.ns;
        localName = a.localname;
        stylename = element.getAttributeNS(ns, localName);
        if (stylename) {
          usedStyles = usedStyles || {};
          keyname = a.keyname;
          if (usedStyles.hasOwnProperty(keyname)) {
            usedStyles[keyname][stylename] = 1;
          } else {
            map = {};
            map[stylename] = 1;
            usedStyles[keyname] = map;
          }
        }
      }
      return usedStyles;
    }
    function determineUsedStyles(styleUsingElementsRoot, usedStyles) {
      var i, e;
      determineStylesForNode(styleUsingElementsRoot, usedStyles);
      i = styleUsingElementsRoot.firstChild;
      while (i) {
        if (i.nodeType === Node.ELEMENT_NODE) {
          e = i;
          determineUsedStyles(e, usedStyles);
        }
        i = i.nextSibling;
      }
    }
    function StyleDefinition(key, name, family) {
      this.key = key;
      this.name = name;
      this.family = family;
      this.requires = {};
    }
    function getStyleDefinition(stylename, stylefamily, knownStyles) {
      var styleKey = stylename + '"' + stylefamily, styleDefinition = knownStyles[styleKey];
      if (!styleDefinition) {
        styleDefinition = knownStyles[styleKey] = new StyleDefinition(styleKey, stylename, stylefamily);
      }
      return styleDefinition;
    }
    function determineDependentStyles(element, styleScope, knownStyles) {
      var i, stylename, elname, elns, a, ns, localName, e, referencedStyleFamily, referencedStyleDef, length = 0, newScopeName = element.getAttributeNS(stylens, "name"), newScopeFamily = element.getAttributeNS(stylens, "family");
      if (newScopeName && newScopeFamily) {
        styleScope = getStyleDefinition(newScopeName, newScopeFamily, knownStyles);
      }
      if (styleScope) {
        elname = elements[element.localName];
        if (elname) {
          elns = elname[element.namespaceURI];
          if (elns) {
            length = elns.length;
          }
        }
        for (i = 0;i < length;i += 1) {
          a = elns[i];
          ns = a.ns;
          localName = a.localname;
          stylename = element.getAttributeNS(ns, localName);
          if (stylename) {
            referencedStyleFamily = a.keyname;
            referencedStyleDef = getStyleDefinition(stylename, referencedStyleFamily, knownStyles);
            styleScope.requires[referencedStyleDef.key] = referencedStyleDef;
          }
        }
      }
      e = element.firstElementChild;
      while (e) {
        determineDependentStyles(e, styleScope, knownStyles);
        e = e.nextElementSibling;
      }
      return knownStyles;
    }
    function inverse() {
      var i, l, keyname, list, item, e = {}, map, array, en, ens;
      for (keyname in elementstyles) {
        if (elementstyles.hasOwnProperty(keyname)) {
          list = elementstyles[keyname];
          l = list.length;
          for (i = 0;i < l;i += 1) {
            item = list[i];
            en = item.en;
            ens = item.ens;
            if (e.hasOwnProperty(en)) {
              map = e[en];
            } else {
              e[en] = map = {};
            }
            if (map.hasOwnProperty(ens)) {
              array = map[ens];
            } else {
              map[ens] = array = [];
            }
            array.push({ns:item.ans, localname:item.a, keyname:keyname});
          }
        }
      }
      return e;
    }
    function mergeRequiredStyles(styleDependency, usedStyles) {
      var family = usedStyles[styleDependency.family];
      if (!family) {
        family = usedStyles[styleDependency.family] = {};
      }
      family[styleDependency.name] = 1;
      Object.keys(styleDependency.requires).forEach(function(requiredStyleKey) {
        mergeRequiredStyles(styleDependency.requires[requiredStyleKey], usedStyles);
      });
    }
    function mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles) {
      var automaticStyles = determineDependentStyles(automaticStylesRoot, null, {});
      Object.keys(automaticStyles).forEach(function(styleKey) {
        var automaticStyleDefinition = automaticStyles[styleKey], usedFamily = usedStyles[automaticStyleDefinition.family];
        if (usedFamily && usedFamily.hasOwnProperty(automaticStyleDefinition.name)) {
          mergeRequiredStyles(automaticStyleDefinition, usedStyles);
        }
      });
    }
    function collectUsedFontFaces(usedFontFaceDeclMap, styleElement) {
      var localNames = ["font-name", "font-name-asian", "font-name-complex"], e, currentElement;
      function collectByAttribute(localName) {
        var fontFaceName = currentElement.getAttributeNS(stylens, localName);
        if (fontFaceName) {
          usedFontFaceDeclMap[fontFaceName] = true;
        }
      }
      e = styleElement && styleElement.firstElementChild;
      while (e) {
        currentElement = e;
        localNames.forEach(collectByAttribute);
        collectUsedFontFaces(usedFontFaceDeclMap, currentElement);
        e = e.nextElementSibling;
      }
    }
    this.collectUsedFontFaces = collectUsedFontFaces;
    function changeFontFaceNames(styleElement, fontFaceNameChangeMap) {
      var localNames = ["font-name", "font-name-asian", "font-name-complex"], e, currentElement;
      function changeFontFaceNameByAttribute(localName) {
        var fontFaceName = currentElement.getAttributeNS(stylens, localName);
        if (fontFaceName && fontFaceNameChangeMap.hasOwnProperty(fontFaceName)) {
          currentElement.setAttributeNS(stylens, "style:" + localName, fontFaceNameChangeMap[fontFaceName]);
        }
      }
      e = styleElement && styleElement.firstElementChild;
      while (e) {
        currentElement = e;
        localNames.forEach(changeFontFaceNameByAttribute);
        changeFontFaceNames(currentElement, fontFaceNameChangeMap);
        e = e.nextElementSibling;
      }
    }
    this.changeFontFaceNames = changeFontFaceNames;
    this.UsedStyleList = function(styleUsingElementsRoot, automaticStylesRoot) {
      var usedStyles = {};
      this.uses = function(element) {
        var localName = element.localName, name = element.getAttributeNS(drawns, "name") || element.getAttributeNS(stylens, "name"), keyName, map;
        if (localName === "style") {
          keyName = element.getAttributeNS(stylens, "family");
        } else {
          if (element.namespaceURI === numberns) {
            keyName = "data";
          } else {
            keyName = localName;
          }
        }
        map = usedStyles[keyName];
        return map ? map[name] > 0 : false;
      };
      determineUsedStyles(styleUsingElementsRoot, usedStyles);
      if (automaticStylesRoot) {
        mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles);
      }
    };
    function getStyleName(family, element) {
      var stylename, i, map = elements[element.localName];
      if (map) {
        map = map[element.namespaceURI];
        if (map) {
          for (i = 0;i < map.length;i += 1) {
            if (map[i].keyname === family) {
              map = map[i];
              if (element.hasAttributeNS(map.ns, map.localname)) {
                stylename = element.getAttributeNS(map.ns, map.localname);
                break;
              }
            }
          }
        }
      }
      return stylename;
    }
    this.getStyleName = getStyleName;
    this.hasDerivedStyles = hasDerivedStyles;
    this.prefixStyleNames = prefixStyleNames;
    this.removePrefixFromStyleNames = removePrefixFromStyleNames;
    this.determineStylesForNode = determineStylesForNode;
    elements = inverse();
  };
  if (typeof Object.create !== "function") {
    Object["create"] = function(o) {
      var F = function() {
      };
      F.prototype = o;
      return new F;
    };
  }
  xmldom.LSSerializer = function LSSerializer() {
    var self = this;
    function Namespaces(nsmap) {
      function invertMap(map) {
        var m = {}, i;
        for (i in map) {
          if (map.hasOwnProperty(i)) {
            m[map[i]] = i;
          }
        }
        return m;
      }
      var current = nsmap || {}, currentrev = invertMap(nsmap), levels = [current], levelsrev = [currentrev], level = 0;
      this.push = function() {
        level += 1;
        current = levels[level] = Object.create(current);
        currentrev = levelsrev[level] = Object.create(currentrev);
      };
      this.pop = function() {
        levels.pop();
        levelsrev.pop();
        level -= 1;
        current = levels[level];
        currentrev = levelsrev[level];
      };
      this.getLocalNamespaceDefinitions = function() {
        return currentrev;
      };
      this.getQName = function(node) {
        var ns = node.namespaceURI, i = 0, p;
        if (!ns) {
          return node.localName;
        }
        p = currentrev[ns];
        if (p) {
          return p + ":" + node.localName;
        }
        do {
          if (p || !node.prefix) {
            p = "ns" + i;
            i += 1;
          } else {
            p = node.prefix;
          }
          if (current[p] === ns) {
            break;
          }
          if (!current[p]) {
            current[p] = ns;
            currentrev[ns] = p;
            break;
          }
          p = null;
        } while (p === null);
        return p + ":" + node.localName;
      };
    }
    function escapeContent(value) {
      return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&apos;").replace(/"/g, "&quot;");
    }
    function serializeAttribute(qname, attr) {
      var escapedValue = typeof attr.value === "string" ? escapeContent(attr.value) : attr.value, s = qname + '="' + escapedValue + '"';
      return s;
    }
    function startElement(ns, qname, element) {
      var s = "", atts = element.attributes, length, i, attr, attstr = "", accept, prefix, nsmap;
      s += "<" + qname;
      length = atts.length;
      for (i = 0;i < length;i += 1) {
        attr = atts.item(i);
        if (attr.namespaceURI !== "http://www.w3.org/2000/xmlns/") {
          accept = self.filter ? self.filter.acceptNode(attr) : NodeFilter.FILTER_ACCEPT;
          if (accept === NodeFilter.FILTER_ACCEPT) {
            attstr += " " + serializeAttribute(ns.getQName(attr), attr);
          }
        }
      }
      nsmap = ns.getLocalNamespaceDefinitions();
      for (i in nsmap) {
        if (nsmap.hasOwnProperty(i)) {
          prefix = nsmap[i];
          if (!prefix) {
            s += ' xmlns="' + i + '"';
          } else {
            if (prefix !== "xmlns") {
              s += " xmlns:" + nsmap[i] + '="' + i + '"';
            }
          }
        }
      }
      s += attstr + ">";
      return s;
    }
    function serializeNode(ns, node) {
      var s = "", accept = self.filter ? self.filter.acceptNode(node) : NodeFilter.FILTER_ACCEPT, child, qname;
      if (accept === NodeFilter.FILTER_ACCEPT && node.nodeType === Node.ELEMENT_NODE) {
        ns.push();
        qname = ns.getQName(node);
        s += startElement(ns, qname, node);
      }
      if (accept === NodeFilter.FILTER_ACCEPT || accept === NodeFilter.FILTER_SKIP) {
        child = node.firstChild;
        while (child) {
          s += serializeNode(ns, child);
          child = child.nextSibling;
        }
        if (node.nodeValue) {
          s += escapeContent(node.nodeValue);
        }
      }
      if (qname) {
        s += "</" + qname + ">";
        ns.pop();
      }
      return s;
    }
    this.filter = null;
    this.writeToString = function(node, nsmap) {
      if (!node) {
        return "";
      }
      var ns = new Namespaces(nsmap);
      return serializeNode(ns, node);
    };
  };
  (function() {
    var styleInfo = new odf.StyleInfo, domUtils = core.DomUtils, officens = "urn:oasis:names:tc:opendocument:xmlns:office:1.0", manifestns = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0", webodfns = "urn:webodf:names:scope", stylens = odf.Namespaces.stylens, nodeorder = ["meta", "settings", "scripts", "font-face-decls", "styles", "automatic-styles", "master-styles", "body"], automaticStylePrefix = Date.now() + "_webodf_", base64 = new core.Base64, documentStylesScope = "document-styles", documentContentScope =
    "document-content";
    function getNodePosition(child) {
      var i, l = nodeorder.length;
      for (i = 0;i < l;i += 1) {
        if (child.namespaceURI === officens && child.localName === nodeorder[i]) {
          return i;
        }
      }
      return -1;
    }
    function OdfStylesFilter(styleUsingElementsRoot, automaticStyles) {
      var usedStyleList = new styleInfo.UsedStyleList(styleUsingElementsRoot, automaticStyles), odfNodeFilter = new odf.OdfNodeFilter;
      this.acceptNode = function(node) {
        var result = odfNodeFilter.acceptNode(node);
        if (result === NodeFilter.FILTER_ACCEPT && node.parentNode === automaticStyles && node.nodeType === Node.ELEMENT_NODE) {
          if (usedStyleList.uses(node)) {
            result = NodeFilter.FILTER_ACCEPT;
          } else {
            result = NodeFilter.FILTER_REJECT;
          }
        }
        return result;
      };
    }
    function OdfContentFilter(styleUsingElementsRoot, automaticStyles) {
      var odfStylesFilter = new OdfStylesFilter(styleUsingElementsRoot, automaticStyles);
      this.acceptNode = function(node) {
        var result = odfStylesFilter.acceptNode(node);
        if (result === NodeFilter.FILTER_ACCEPT && node.parentNode && node.parentNode.namespaceURI === odf.Namespaces.textns && (node.parentNode.localName === "s" || node.parentNode.localName === "tab")) {
          result = NodeFilter.FILTER_REJECT;
        }
        return result;
      };
    }
    function setChild(node, child) {
      if (!child) {
        return;
      }
      var childpos = getNodePosition(child), pos, c = node.firstChild;
      if (childpos === -1) {
        return;
      }
      while (c) {
        pos = getNodePosition(c);
        if (pos !== -1 && pos > childpos) {
          break;
        }
        c = c.nextSibling;
      }
      node.insertBefore(child, c);
    }
    odf.ODFElement = function ODFElement() {
    };
    odf.ODFDocumentElement = function ODFDocumentElement() {
    };
    odf.ODFDocumentElement.prototype = new odf.ODFElement;
    odf.ODFDocumentElement.prototype.constructor = odf.ODFDocumentElement;
    odf.ODFDocumentElement.prototype.automaticStyles;
    odf.ODFDocumentElement.prototype.body;
    odf.ODFDocumentElement.prototype.fontFaceDecls = null;
    odf.ODFDocumentElement.prototype.manifest = null;
    odf.ODFDocumentElement.prototype.masterStyles;
    odf.ODFDocumentElement.prototype.meta;
    odf.ODFDocumentElement.prototype.settings = null;
    odf.ODFDocumentElement.prototype.styles;
    odf.ODFDocumentElement.namespaceURI = officens;
    odf.ODFDocumentElement.localName = "document";
    odf.AnnotationElement = function AnnotationElement() {
    };
    odf.AnnotationElement.prototype.annotationEndElement;
    odf.OdfPart = function OdfPart(name, mimetype, container, zip) {
      var self = this;
      this.size = 0;
      this.type = null;
      this.name = name;
      this.container = container;
      this.url = null;
      this.mimetype = mimetype;
      this.document = null;
      this.onstatereadychange = null;
      this.onchange;
      this.EMPTY = 0;
      this.LOADING = 1;
      this.DONE = 2;
      this.state = this.EMPTY;
      this.data = "";
      this.load = function() {
        if (zip === null) {
          return;
        }
        this.mimetype = mimetype;
        zip.loadAsDataURL(name, mimetype, function(err, url) {
          if (err) {
            runtime.log(err);
          }
          self.url = url;
          if (self.onchange) {
            self.onchange(self);
          }
          if (self.onstatereadychange) {
            self.onstatereadychange(self);
          }
        });
      };
    };
    odf.OdfPart.prototype.load = function() {
    };
    odf.OdfPart.prototype.getUrl = function() {
      if (this.data) {
        return "data:;base64," + base64.toBase64(this.data);
      }
      return null;
    };
    odf.OdfContainer = function OdfContainer(urlOrType, onstatereadychange) {
      var self = this, zip, partMimetypes = {}, contentElement, url = "";
      this.onstatereadychange = onstatereadychange;
      this.onchange = null;
      this.state = null;
      this.rootElement;
      function removeProcessingInstructions(element) {
        var n = element.firstChild, next, e;
        while (n) {
          next = n.nextSibling;
          if (n.nodeType === Node.ELEMENT_NODE) {
            e = n;
            removeProcessingInstructions(e);
          } else {
            if (n.nodeType === Node.PROCESSING_INSTRUCTION_NODE) {
              element.removeChild(n);
            }
          }
          n = next;
        }
      }
      function linkAnnotationStartAndEndElements(rootElement) {
        var document = rootElement.ownerDocument, annotationStarts = {}, n, name, annotationStart, nodeIterator = document.createNodeIterator(rootElement, NodeFilter.SHOW_ELEMENT, null, false);
        n = nodeIterator.nextNode();
        while (n) {
          if (n.namespaceURI === officens) {
            if (n.localName === "annotation") {
              name = n.getAttributeNS(officens, "name");
              if (name) {
                if (annotationStarts.hasOwnProperty(name)) {
                  runtime.log("Warning: annotation name used more than once with <office:annotation/>: '" + name + "'");
                } else {
                  annotationStarts[name] = n;
                }
              }
            } else {
              if (n.localName === "annotation-end") {
                name = n.getAttributeNS(officens, "name");
                if (name) {
                  if (annotationStarts.hasOwnProperty(name)) {
                    annotationStart = annotationStarts[name];
                    if (!annotationStart.annotationEndElement) {
                      annotationStart.annotationEndElement = n;
                    } else {
                      runtime.log("Warning: annotation name used more than once with <office:annotation-end/>: '" + name + "'");
                    }
                  } else {
                    runtime.log("Warning: annotation end without an annotation start, name: '" + name + "'");
                  }
                } else {
                  runtime.log("Warning: annotation end without a name found");
                }
              }
            }
          }
          n = nodeIterator.nextNode();
        }
      }
      function setAutomaticStylesScope(stylesRootElement, scope) {
        var n = stylesRootElement && stylesRootElement.firstChild;
        while (n) {
          if (n.nodeType === Node.ELEMENT_NODE) {
            n.setAttributeNS(webodfns, "scope", scope);
          }
          n = n.nextSibling;
        }
      }
      function getEnsuredMetaElement() {
        var root = self.rootElement, meta = root.meta;
        if (!meta) {
          root.meta = meta = document.createElementNS(officens, "meta");
          setChild(root, meta);
        }
        return meta;
      }
      function getMetadata(metadataNs, metadataLocalName) {
        var node = self.rootElement.meta, textNode;
        node = node && node.firstChild;
        while (node && (node.namespaceURI !== metadataNs || node.localName !== metadataLocalName)) {
          node = node.nextSibling;
        }
        node = node && node.firstChild;
        while (node && node.nodeType !== Node.TEXT_NODE) {
          node = node.nextSibling;
        }
        if (node) {
          textNode = node;
          return textNode.data;
        }
        return null;
      }
      this.getMetadata = getMetadata;
      function unusedKey(key, map1, map2) {
        var i = 0, postFixedKey;
        key = key.replace(/\d+$/, "");
        postFixedKey = key;
        while (map1.hasOwnProperty(postFixedKey) || map2.hasOwnProperty(postFixedKey)) {
          i += 1;
          postFixedKey = key + i;
        }
        return postFixedKey;
      }
      function mapByFontFaceName(fontFaceDecls) {
        var fn, result = {}, fontname;
        fn = fontFaceDecls.firstChild;
        while (fn) {
          if (fn.nodeType === Node.ELEMENT_NODE && fn.namespaceURI === stylens && fn.localName === "font-face") {
            fontname = fn.getAttributeNS(stylens, "name");
            result[fontname] = fn;
          }
          fn = fn.nextSibling;
        }
        return result;
      }
      function mergeFontFaceDecls(targetFontFaceDeclsRootElement, sourceFontFaceDeclsRootElement) {
        var e, s, fontFaceName, newFontFaceName, targetFontFaceDeclsMap, sourceFontFaceDeclsMap, fontFaceNameChangeMap = {};
        targetFontFaceDeclsMap = mapByFontFaceName(targetFontFaceDeclsRootElement);
        sourceFontFaceDeclsMap = mapByFontFaceName(sourceFontFaceDeclsRootElement);
        e = sourceFontFaceDeclsRootElement.firstElementChild;
        while (e) {
          s = e.nextElementSibling;
          if (e.namespaceURI === stylens && e.localName === "font-face") {
            fontFaceName = e.getAttributeNS(stylens, "name");
            if (targetFontFaceDeclsMap.hasOwnProperty(fontFaceName)) {
              if (!e.isEqualNode(targetFontFaceDeclsMap[fontFaceName])) {
                newFontFaceName = unusedKey(fontFaceName, targetFontFaceDeclsMap, sourceFontFaceDeclsMap);
                e.setAttributeNS(stylens, "style:name", newFontFaceName);
                targetFontFaceDeclsRootElement.appendChild(e);
                targetFontFaceDeclsMap[newFontFaceName] = e;
                delete sourceFontFaceDeclsMap[fontFaceName];
                fontFaceNameChangeMap[fontFaceName] = newFontFaceName;
              }
            } else {
              targetFontFaceDeclsRootElement.appendChild(e);
              targetFontFaceDeclsMap[fontFaceName] = e;
              delete sourceFontFaceDeclsMap[fontFaceName];
            }
          }
          e = s;
        }
        return fontFaceNameChangeMap;
      }
      function cloneStylesInScope(stylesRootElement, scope) {
        var copy = null, e, s, scopeAttrValue;
        if (stylesRootElement) {
          copy = stylesRootElement.cloneNode(true);
          e = copy.firstElementChild;
          while (e) {
            s = e.nextElementSibling;
            scopeAttrValue = e.getAttributeNS(webodfns, "scope");
            if (scopeAttrValue && scopeAttrValue !== scope) {
              copy.removeChild(e);
            }
            e = s;
          }
        }
        return copy;
      }
      function cloneFontFaceDeclsUsedInStyles(fontFaceDeclsRootElement, stylesRootElementList) {
        var e, nextSibling, fontFaceName, copy = null, usedFontFaceDeclMap = {};
        if (fontFaceDeclsRootElement) {
          stylesRootElementList.forEach(function(stylesRootElement) {
            styleInfo.collectUsedFontFaces(usedFontFaceDeclMap, stylesRootElement);
          });
          copy = fontFaceDeclsRootElement.cloneNode(true);
          e = copy.firstElementChild;
          while (e) {
            nextSibling = e.nextElementSibling;
            fontFaceName = e.getAttributeNS(stylens, "name");
            if (!usedFontFaceDeclMap[fontFaceName]) {
              copy.removeChild(e);
            }
            e = nextSibling;
          }
        }
        return copy;
      }
      function importRootNode(xmldoc) {
        var doc = self.rootElement.ownerDocument, node;
        if (xmldoc) {
          removeProcessingInstructions(xmldoc.documentElement);
          try {
            node = doc.importNode(xmldoc.documentElement, true);
          } catch (ignore) {
          }
        }
        return node;
      }
      function setState(state) {
        self.state = state;
        if (self.onchange) {
          self.onchange(self);
        }
        if (self.onstatereadychange) {
          self.onstatereadychange(self);
        }
      }
      function setRootElement(root) {
        contentElement = null;
        self.rootElement = root;
        root.fontFaceDecls = domUtils.getDirectChild(root, officens, "font-face-decls");
        root.styles = domUtils.getDirectChild(root, officens, "styles");
        root.automaticStyles = domUtils.getDirectChild(root, officens, "automatic-styles");
        root.masterStyles = domUtils.getDirectChild(root, officens, "master-styles");
        root.body = domUtils.getDirectChild(root, officens, "body");
        root.meta = domUtils.getDirectChild(root, officens, "meta");
        root.settings = domUtils.getDirectChild(root, officens, "settings");
        root.scripts = domUtils.getDirectChild(root, officens, "scripts");
        linkAnnotationStartAndEndElements(root);
      }
      function handleFlatXml(xmldoc) {
        var root = importRootNode(xmldoc);
        if (!root || root.localName !== "document" || root.namespaceURI !== officens) {
          setState(OdfContainer.INVALID);
          return;
        }
        setRootElement(root);
        setState(OdfContainer.DONE);
      }
      function handleStylesXml(xmldoc) {
        var node = importRootNode(xmldoc), root = self.rootElement, n;
        if (!node || node.localName !== "document-styles" || node.namespaceURI !== officens) {
          setState(OdfContainer.INVALID);
          return;
        }
        root.fontFaceDecls = domUtils.getDirectChild(node, officens, "font-face-decls");
        setChild(root, root.fontFaceDecls);
        n = domUtils.getDirectChild(node, officens, "styles");
        root.styles = n || xmldoc.createElementNS(officens, "styles");
        setChild(root, root.styles);
        n = domUtils.getDirectChild(node, officens, "automatic-styles");
        root.automaticStyles = n || xmldoc.createElementNS(officens, "automatic-styles");
        setAutomaticStylesScope(root.automaticStyles, documentStylesScope);
        setChild(root, root.automaticStyles);
        node = domUtils.getDirectChild(node, officens, "master-styles");
        root.masterStyles = node || xmldoc.createElementNS(officens, "master-styles");
        setChild(root, root.masterStyles);
        styleInfo.prefixStyleNames(root.automaticStyles, automaticStylePrefix, root.masterStyles);
      }
      function handleContentXml(xmldoc) {
        var node = importRootNode(xmldoc), root, automaticStyles, fontFaceDecls, fontFaceNameChangeMap, c;
        if (!node || node.localName !== "document-content" || node.namespaceURI !== officens) {
          setState(OdfContainer.INVALID);
          return;
        }
        root = self.rootElement;
        fontFaceDecls = domUtils.getDirectChild(node, officens, "font-face-decls");
        if (root.fontFaceDecls && fontFaceDecls) {
          fontFaceNameChangeMap = mergeFontFaceDecls(root.fontFaceDecls, fontFaceDecls);
        } else {
          if (fontFaceDecls) {
            root.fontFaceDecls = fontFaceDecls;
            setChild(root, fontFaceDecls);
          }
        }
        automaticStyles = domUtils.getDirectChild(node, officens, "automatic-styles");
        setAutomaticStylesScope(automaticStyles, documentContentScope);
        if (fontFaceNameChangeMap) {
          styleInfo.changeFontFaceNames(automaticStyles, fontFaceNameChangeMap);
        }
        if (root.automaticStyles && automaticStyles) {
          c = automaticStyles.firstChild;
          while (c) {
            root.automaticStyles.appendChild(c);
            c = automaticStyles.firstChild;
          }
        } else {
          if (automaticStyles) {
            root.automaticStyles = automaticStyles;
            setChild(root, automaticStyles);
          }
        }
        node = domUtils.getDirectChild(node, officens, "body");
        if (node === null) {
          throw "<office:body/> tag is mising.";
        }
        root.body = node;
        setChild(root, root.body);
      }
      function handleMetaXml(xmldoc) {
        var node = importRootNode(xmldoc), root;
        if (!node || node.localName !== "document-meta" || node.namespaceURI !== officens) {
          return;
        }
        root = self.rootElement;
        root.meta = domUtils.getDirectChild(node, officens, "meta");
        setChild(root, root.meta);
      }
      function handleSettingsXml(xmldoc) {
        var node = importRootNode(xmldoc), root;
        if (!node || node.localName !== "document-settings" || node.namespaceURI !== officens) {
          return;
        }
        root = self.rootElement;
        root.settings = domUtils.getDirectChild(node, officens, "settings");
        setChild(root, root.settings);
      }
      function handleManifestXml(xmldoc) {
        var node = importRootNode(xmldoc), root, e;
        if (!node || node.localName !== "manifest" || node.namespaceURI !== manifestns) {
          return;
        }
        root = self.rootElement;
        root.manifest = node;
        e = root.manifest.firstElementChild;
        while (e) {
          if (e.localName === "file-entry" && e.namespaceURI === manifestns) {
            partMimetypes[e.getAttributeNS(manifestns, "full-path")] = e.getAttributeNS(manifestns, "media-type");
          }
          e = e.nextElementSibling;
        }
      }
      function removeElements(xmldoc, localName, allowedNamespaces) {
        var elements = domUtils.getElementsByTagName(xmldoc, localName), element, i;
        for (i = 0;i < elements.length;i += 1) {
          element = elements[i];
          if (!allowedNamespaces.hasOwnProperty(element.namespaceURI)) {
            element.parentNode.removeChild(element);
          }
        }
      }
      function removeDangerousElements(xmldoc) {
        removeElements(xmldoc, "script", {"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0":true, "urn:oasis:names:tc:opendocument:xmlns:office:1.0":true, "urn:oasis:names:tc:opendocument:xmlns:table:1.0":true, "urn:oasis:names:tc:opendocument:xmlns:text:1.0":true, "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0":true});
        removeElements(xmldoc, "style", {"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0":true, "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0":true, "urn:oasis:names:tc:opendocument:xmlns:style:1.0":true});
      }
      function removeDangerousAttributes(element) {
        var e = element.firstElementChild, as = [], i, n, a, atts = element.attributes, l = atts.length;
        for (i = 0;i < l;i += 1) {
          a = atts.item(i);
          n = a.localName.substr(0, 2).toLowerCase();
          if (a.namespaceURI === null && n === "on") {
            as.push(a);
          }
        }
        l = as.length;
        for (i = 0;i < l;i += 1) {
          element.removeAttributeNode(as[i]);
        }
        while (e) {
          removeDangerousAttributes(e);
          e = e.nextElementSibling;
        }
      }
      function loadNextComponent(remainingComponents) {
        var component = remainingComponents.shift();
        if (component) {
          zip.loadAsDOM(component.path, function(err, xmldoc) {
            if (xmldoc) {
              removeDangerousElements(xmldoc);
              removeDangerousAttributes(xmldoc.documentElement);
            }
            component.handler(xmldoc);
            if (self.state === OdfContainer.INVALID) {
              if (err) {
                runtime.log("ERROR: Unable to load " + component.path + " - " + err);
              } else {
                runtime.log("ERROR: Unable to load " + component.path);
              }
              return;
            }
            if (err) {
              runtime.log("DEBUG: Unable to load " + component.path + " - " + err);
            }
            loadNextComponent(remainingComponents);
          });
        } else {
          linkAnnotationStartAndEndElements(self.rootElement);
          setState(OdfContainer.DONE);
        }
      }
      function loadComponents() {
        var componentOrder = [{path:"styles.xml", handler:handleStylesXml}, {path:"content.xml", handler:handleContentXml}, {path:"meta.xml", handler:handleMetaXml}, {path:"settings.xml", handler:handleSettingsXml}, {path:"META-INF/manifest.xml", handler:handleManifestXml}];
        loadNextComponent(componentOrder);
      }
      function createDocumentElement(name) {
        var s = "";
        function defineNamespace(prefix, ns) {
          s += " xmlns:" + prefix + '="' + ns + '"';
        }
        odf.Namespaces.forEachPrefix(defineNamespace);
        return '<?xml version="1.0" encoding="UTF-8"?><office:' + name + " " + s + ' office:version="1.2">';
      }
      function serializeMetaXml() {
        var serializer = new xmldom.LSSerializer, s = createDocumentElement("document-meta");
        serializer.filter = new odf.OdfNodeFilter;
        s += serializer.writeToString(self.rootElement.meta, odf.Namespaces.namespaceMap);
        s += "</office:document-meta>";
        return s;
      }
      function createManifestEntry(fullPath, mediaType) {
        var element = document.createElementNS(manifestns, "manifest:file-entry");
        element.setAttributeNS(manifestns, "manifest:full-path", fullPath);
        element.setAttributeNS(manifestns, "manifest:media-type", mediaType);
        return element;
      }
      function serializeManifestXml() {
        var header = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n', xml = '<manifest:manifest xmlns:manifest="' + manifestns + '" manifest:version="1.2"></manifest:manifest>', manifest = runtime.parseXML(xml), manifestRoot = manifest.documentElement, serializer = new xmldom.LSSerializer, fullPath;
        for (fullPath in partMimetypes) {
          if (partMimetypes.hasOwnProperty(fullPath)) {
            manifestRoot.appendChild(createManifestEntry(fullPath, partMimetypes[fullPath]));
          }
        }
        serializer.filter = new odf.OdfNodeFilter;
        return header + serializer.writeToString(manifest, odf.Namespaces.namespaceMap);
      }
      function serializeSettingsXml() {
        var serializer = new xmldom.LSSerializer, s = createDocumentElement("document-settings");
        if (self.rootElement.settings && self.rootElement.settings.firstElementChild) {
          serializer.filter = new odf.OdfNodeFilter;
          s += serializer.writeToString(self.rootElement.settings, odf.Namespaces.namespaceMap);
        }
        return s + "</office:document-settings>";
      }
      function serializeStylesXml() {
        var fontFaceDecls, automaticStyles, masterStyles, nsmap = odf.Namespaces.namespaceMap, serializer = new xmldom.LSSerializer, s = createDocumentElement("document-styles");
        automaticStyles = cloneStylesInScope(self.rootElement.automaticStyles, documentStylesScope);
        masterStyles = self.rootElement.masterStyles.cloneNode(true);
        fontFaceDecls = cloneFontFaceDeclsUsedInStyles(self.rootElement.fontFaceDecls, [masterStyles, self.rootElement.styles, automaticStyles]);
        styleInfo.removePrefixFromStyleNames(automaticStyles, automaticStylePrefix, masterStyles);
        serializer.filter = new OdfStylesFilter(masterStyles, automaticStyles);
        s += serializer.writeToString(fontFaceDecls, nsmap);
        s += serializer.writeToString(self.rootElement.styles, nsmap);
        s += serializer.writeToString(automaticStyles, nsmap);
        s += serializer.writeToString(masterStyles, nsmap);
        s += "</office:document-styles>";
        return s;
      }
      function serializeContentXml() {
        var fontFaceDecls, automaticStyles, nsmap = odf.Namespaces.namespaceMap, serializer = new xmldom.LSSerializer, s = createDocumentElement("document-content");
        automaticStyles = cloneStylesInScope(self.rootElement.automaticStyles, documentContentScope);
        fontFaceDecls = cloneFontFaceDeclsUsedInStyles(self.rootElement.fontFaceDecls, [automaticStyles]);
        serializer.filter = new OdfContentFilter(self.rootElement.body, automaticStyles);
        s += serializer.writeToString(fontFaceDecls, nsmap);
        s += serializer.writeToString(automaticStyles, nsmap);
        s += serializer.writeToString(self.rootElement.body, nsmap);
        s += "</office:document-content>";
        return s;
      }
      function createElement(type) {
        var original = document.createElementNS(type.namespaceURI, type.localName), method, iface = new type.Type;
        for (method in iface) {
          if (iface.hasOwnProperty(method)) {
            original[method] = iface[method];
          }
        }
        return original;
      }
      function loadFromXML(url, callback) {
        function handler(err, dom) {
          if (err) {
            callback(err);
          } else {
            if (!dom) {
              callback("No DOM was loaded.");
            } else {
              removeDangerousElements(dom);
              removeDangerousAttributes(dom.documentElement);
              handleFlatXml(dom);
            }
          }
        }
        runtime.loadXML(url, handler);
      }
      this.setRootElement = setRootElement;
      this.getContentElement = function() {
        var body;
        if (!contentElement) {
          body = self.rootElement.body;
          contentElement = domUtils.getDirectChild(body, officens, "text") || domUtils.getDirectChild(body, officens, "presentation") || domUtils.getDirectChild(body, officens, "spreadsheet");
        }
        if (!contentElement) {
          throw "Could not find content element in <office:body/>.";
        }
        return contentElement;
      };
      this.getDocumentType = function() {
        var content = self.getContentElement();
        return content && content.localName;
      };
      this.isTemplate = function() {
        var docMimetype = partMimetypes["/"];
        return docMimetype.substr(-9) === "-template";
      };
      this.setIsTemplate = function(isTemplate) {
        var docMimetype = partMimetypes["/"], oldIsTemplate = docMimetype.substr(-9) === "-template", data;
        if (isTemplate === oldIsTemplate) {
          return;
        }
        if (isTemplate) {
          docMimetype = docMimetype + "-template";
        } else {
          docMimetype = docMimetype.substr(0, docMimetype.length - 9);
        }
        partMimetypes["/"] = docMimetype;
        data = runtime.byteArrayFromString(docMimetype, "utf8");
        zip.save("mimetype", data, false, new Date);
      };
      this.getPart = function(partname) {
        return new odf.OdfPart(partname, partMimetypes[partname], self, zip);
      };
      this.getPartData = function(url, callback) {
        zip.load(url, callback);
      };
      function setMetadata(setProperties, removedPropertyNames) {
        var metaElement = getEnsuredMetaElement();
        if (setProperties) {
          domUtils.mapKeyValObjOntoNode(metaElement, setProperties, odf.Namespaces.lookupNamespaceURI);
        }
        if (removedPropertyNames) {
          domUtils.removeKeyElementsFromNode(metaElement, removedPropertyNames, odf.Namespaces.lookupNamespaceURI);
        }
      }
      this.setMetadata = setMetadata;
      this.incrementEditingCycles = function() {
        var currentValueString = getMetadata(odf.Namespaces.metans, "editing-cycles"), currentCycles = currentValueString ? parseInt(currentValueString, 10) : 0;
        if (isNaN(currentCycles)) {
          currentCycles = 0;
        }
        setMetadata({"meta:editing-cycles":currentCycles + 1}, null);
        return currentCycles + 1;
      };
      function updateMetadataForSaving() {
        var generatorString, window = runtime.getWindow();
        generatorString = "WebODF/" + webodf.Version;
        if (window) {
          generatorString = generatorString + " " + window.navigator.userAgent;
        }
        setMetadata({"meta:generator":generatorString}, null);
      }
      function createEmptyDocument(type, isTemplate) {
        var emptyzip = new core.Zip("", null), mimetype = "application/vnd.oasis.opendocument." + type + (isTemplate === true ? "-template" : ""), data = runtime.byteArrayFromString(mimetype, "utf8"), root = self.rootElement, content = document.createElementNS(officens, type);
        emptyzip.save("mimetype", data, false, new Date);
        function addToplevelElement(memberName, realLocalName) {
          var element;
          if (!realLocalName) {
            realLocalName = memberName;
          }
          element = document.createElementNS(officens, realLocalName);
          root[memberName] = element;
          root.appendChild(element);
        }
        addToplevelElement("meta");
        addToplevelElement("settings");
        addToplevelElement("scripts");
        addToplevelElement("fontFaceDecls", "font-face-decls");
        addToplevelElement("styles");
        addToplevelElement("automaticStyles", "automatic-styles");
        addToplevelElement("masterStyles", "master-styles");
        addToplevelElement("body");
        root.body.appendChild(content);
        partMimetypes["/"] = mimetype;
        partMimetypes["settings.xml"] = "text/xml";
        partMimetypes["meta.xml"] = "text/xml";
        partMimetypes["styles.xml"] = "text/xml";
        partMimetypes["content.xml"] = "text/xml";
        setState(OdfContainer.DONE);
        return emptyzip;
      }
      function fillZip() {
        var data, date = new Date, settings;
        if (partMimetypes["settings.xml"]) {
          settings = serializeSettingsXml();
          data = runtime.byteArrayFromString(settings, "utf8");
          zip.save("settings.xml", data, true, date);
        } else {
          zip.remove("settings.xml");
        }
        updateMetadataForSaving();
        data = runtime.byteArrayFromString(serializeMetaXml(), "utf8");
        zip.save("meta.xml", data, true, date);
        data = runtime.byteArrayFromString(serializeStylesXml(), "utf8");
        zip.save("styles.xml", data, true, date);
        data = runtime.byteArrayFromString(serializeContentXml(), "utf8");
        zip.save("content.xml", data, true, date);
        data = runtime.byteArrayFromString(serializeManifestXml(), "utf8");
        zip.save("META-INF/manifest.xml", data, true, date);
      }
      function createByteArray(successCallback, errorCallback) {
        fillZip();
        zip.createByteArray(successCallback, errorCallback);
      }
      this.createByteArray = createByteArray;
      function saveAs(newurl, callback) {
        fillZip();
        zip.writeAs(newurl, function(err) {
          callback(err);
        });
      }
      this.saveAs = saveAs;
      this.save = function(callback) {
        saveAs(url, callback);
      };
      this.getUrl = function() {
        return url;
      };
      this.setBlob = function(filename, mimetype, content) {
        var data = base64.convertBase64ToByteArray(content), date = new Date;
        zip.save(filename, data, false, date);
        if (partMimetypes.hasOwnProperty(filename)) {
          runtime.log(filename + " has been overwritten.");
        }
        partMimetypes[filename] = mimetype;
      };
      this.removeBlob = function(filename) {
        var foundAndRemoved = zip.remove(filename);
        runtime.assert(foundAndRemoved, "file is not found: " + filename);
        delete partMimetypes[filename];
      };
      this.state = OdfContainer.LOADING;
      this.rootElement = createElement({Type:odf.ODFDocumentElement, namespaceURI:odf.ODFDocumentElement.namespaceURI, localName:odf.ODFDocumentElement.localName});
      if (urlOrType === odf.OdfContainer.DocumentType.TEXT) {
        zip = createEmptyDocument("text");
      } else {
        if (urlOrType === odf.OdfContainer.DocumentType.TEXT_TEMPLATE) {
          zip = createEmptyDocument("text", true);
        } else {
          if (urlOrType === odf.OdfContainer.DocumentType.PRESENTATION) {
            zip = createEmptyDocument("presentation");
          } else {
            if (urlOrType === odf.OdfContainer.DocumentType.PRESENTATION_TEMPLATE) {
              zip = createEmptyDocument("presentation", true);
            } else {
              if (urlOrType === odf.OdfContainer.DocumentType.SPREADSHEET) {
                zip = createEmptyDocument("spreadsheet");
              } else {
                if (urlOrType === odf.OdfContainer.DocumentType.SPREADSHEET_TEMPLATE) {
                  zip = createEmptyDocument("spreadsheet", true);
                } else {
                  url = urlOrType;
                  zip = new core.Zip(url, function(err, zipobject) {
                    zip = zipobject;
                    if (err) {
                      loadFromXML(url, function(xmlerr) {
                        if (err) {
                          zip.error = err + "\n" + xmlerr;
                          setState(OdfContainer.INVALID);
                        }
                      });
                    } else {
                      loadComponents();
                    }
                  });
                }
              }
            }
          }
        }
      }
    };
    odf.OdfContainer.EMPTY = 0;
    odf.OdfContainer.LOADING = 1;
    odf.OdfContainer.DONE = 2;
    odf.OdfContainer.INVALID = 3;
    odf.OdfContainer.SAVING = 4;
    odf.OdfContainer.MODIFIED = 5;
    odf.OdfContainer.getContainer = function(url) {
      return new odf.OdfContainer(url, null);
    };
  })();
  odf.OdfContainer.DocumentType = {TEXT:1, TEXT_TEMPLATE:2, PRESENTATION:3, PRESENTATION_TEMPLATE:4, SPREADSHEET:5, SPREADSHEET_TEMPLATE:6};
  gui.AnnotatableCanvas = function AnnotatableCanvas() {
  };
  gui.AnnotatableCanvas.prototype.refreshSize = function() {
  };
  gui.AnnotatableCanvas.prototype.getZoomLevel = function() {
  };
  gui.AnnotatableCanvas.prototype.getSizer = function() {
  };
  gui.AnnotationViewManager = function AnnotationViewManager(canvas, odfFragment, annotationsPane, showAnnotationRemoveButton) {
    var annotations = [], doc = odfFragment.ownerDocument, odfUtils = odf.OdfUtils, CONNECTOR_MARGIN = 30, NOTE_MARGIN = 20, window = runtime.getWindow(), htmlns = "http://www.w3.org/1999/xhtml";
    runtime.assert(Boolean(window), "Expected to be run in an environment which has a global window, like a browser.");
    function wrapAnnotation(annotation) {
      var annotationWrapper = doc.createElement("div"), annotationNote = doc.createElement("div"), connectorHorizontal = doc.createElement("div"), connectorAngular = doc.createElement("div"), removeButton;
      annotationWrapper.className = "annotationWrapper";
      annotationWrapper.setAttribute("creator", odfUtils.getAnnotationCreator(annotation));
      annotation.parentNode.insertBefore(annotationWrapper, annotation);
      annotationNote.className = "annotationNote";
      annotationNote.appendChild(annotation);
      if (showAnnotationRemoveButton) {
        removeButton = doc.createElement("div");
        removeButton.className = "annotationRemoveButton";
        annotationNote.appendChild(removeButton);
      }
      connectorHorizontal.className = "annotationConnector horizontal";
      connectorAngular.className = "annotationConnector angular";
      annotationWrapper.appendChild(annotationNote);
      annotationWrapper.appendChild(connectorHorizontal);
      annotationWrapper.appendChild(connectorAngular);
    }
    function unwrapAnnotation(annotation) {
      var annotationWrapper = annotation.parentNode.parentNode;
      if (annotationWrapper.localName === "div") {
        annotationWrapper.parentNode.insertBefore(annotation, annotationWrapper);
        annotationWrapper.parentNode.removeChild(annotationWrapper);
      }
    }
    function isNodeWithinAnnotationHighlight(node, annotationName) {
      var iteratingNode = node.parentNode;
      while (!(iteratingNode.namespaceURI === odf.Namespaces.officens && iteratingNode.localName === "body")) {
        if (iteratingNode.namespaceURI === htmlns && iteratingNode.className === "webodf-annotationHighlight" && iteratingNode.getAttribute("annotation") === annotationName) {
          return true;
        }
        iteratingNode = iteratingNode.parentNode;
      }
      return false;
    }
    function highlightAnnotation(annotation) {
      var annotationEnd = annotation.annotationEndElement, range = doc.createRange(), annotationName = annotation.getAttributeNS(odf.Namespaces.officens, "name"), textNodes;
      if (annotationEnd) {
        range.setStart(annotation, annotation.childNodes.length);
        range.setEnd(annotationEnd, 0);
        textNodes = odfUtils.getTextNodes(range, false);
        textNodes.forEach(function(n) {
          if (!isNodeWithinAnnotationHighlight(n, annotationName)) {
            var container = doc.createElement("span");
            container.className = "webodf-annotationHighlight";
            container.setAttribute("annotation", annotationName);
            n.parentNode.replaceChild(container, n);
            container.appendChild(n);
          }
        });
      }
      range.detach();
    }
    function unhighlightAnnotation(annotation) {
      var annotationName = annotation.getAttributeNS(odf.Namespaces.officens, "name"), highlightSpans = doc.querySelectorAll('span.webodf-annotationHighlight[annotation="' + annotationName + '"]'), i, container;
      for (i = 0;i < highlightSpans.length;i += 1) {
        container = highlightSpans.item(i);
        while (container.firstChild) {
          container.parentNode.insertBefore(container.firstChild, container);
        }
        container.parentNode.removeChild(container);
      }
    }
    function lineDistance(point1, point2) {
      var xs = 0, ys = 0;
      xs = point2.x - point1.x;
      xs = xs * xs;
      ys = point2.y - point1.y;
      ys = ys * ys;
      return Math.sqrt(xs + ys);
    }
    function renderAnnotation(annotation) {
      var annotationNote = annotation.parentNode, connectorHorizontal = annotationNote.nextElementSibling, connectorAngular = connectorHorizontal.nextElementSibling, annotationWrapper = annotationNote.parentNode, connectorAngle = 0, previousAnnotation = annotations[annotations.indexOf(annotation) - 1], previousRect, zoomLevel = canvas.getZoomLevel();
      annotationNote.style.left = (annotationsPane.getBoundingClientRect().left - annotationWrapper.getBoundingClientRect().left) / zoomLevel + "px";
      annotationNote.style.width = annotationsPane.getBoundingClientRect().width / zoomLevel + "px";
      connectorHorizontal.style.width = parseFloat(annotationNote.style.left) - CONNECTOR_MARGIN + "px";
      if (previousAnnotation) {
        previousRect = previousAnnotation.parentNode.getBoundingClientRect();
        if ((annotationWrapper.getBoundingClientRect().top - previousRect.bottom) / zoomLevel <= NOTE_MARGIN) {
          annotationNote.style.top = Math.abs(annotationWrapper.getBoundingClientRect().top - previousRect.bottom) / zoomLevel + NOTE_MARGIN + "px";
        } else {
          annotationNote.style.top = "0px";
        }
      } else {
        annotationNote.style.top = "0px";
      }
      connectorAngular.style.left = connectorHorizontal.getBoundingClientRect().width / zoomLevel + "px";
      connectorAngular.style.width = lineDistance({x:connectorAngular.getBoundingClientRect().left / zoomLevel, y:connectorAngular.getBoundingClientRect().top / zoomLevel}, {x:annotationNote.getBoundingClientRect().left / zoomLevel, y:annotationNote.getBoundingClientRect().top / zoomLevel}) + "px";
      connectorAngle = Math.asin((annotationNote.getBoundingClientRect().top - connectorAngular.getBoundingClientRect().top) / (zoomLevel * parseFloat(connectorAngular.style.width)));
      connectorAngular.style.transform = "rotate(" + connectorAngle + "rad)";
      connectorAngular.style.MozTransform = "rotate(" + connectorAngle + "rad)";
      connectorAngular.style.WebkitTransform = "rotate(" + connectorAngle + "rad)";
      connectorAngular.style.msTransform = "rotate(" + connectorAngle + "rad)";
    }
    function showAnnotationsPane(show) {
      var sizer = canvas.getSizer();
      if (show) {
        annotationsPane.style.display = "inline-block";
        sizer.style.paddingRight = window.getComputedStyle(annotationsPane).width;
      } else {
        annotationsPane.style.display = "none";
        sizer.style.paddingRight = 0;
      }
      canvas.refreshSize();
    }
    function sortAnnotations() {
      annotations.sort(function(a, b) {
        if ((a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0) {
          return -1;
        }
        return 1;
      });
    }
    function rerenderAnnotations() {
      var i;
      for (i = 0;i < annotations.length;i += 1) {
        renderAnnotation(annotations[i]);
      }
    }
    this.rerenderAnnotations = rerenderAnnotations;
    function rehighlightAnnotations() {
      annotations.forEach(function(annotation) {
        highlightAnnotation(annotation);
      });
    }
    this.rehighlightAnnotations = rehighlightAnnotations;
    function getMinimumHeightForAnnotationPane() {
      if (annotationsPane.style.display !== "none" && annotations.length > 0) {
        return (annotations[annotations.length - 1].parentNode.getBoundingClientRect().bottom - annotationsPane.getBoundingClientRect().top) / canvas.getZoomLevel() + "px";
      }
      return null;
    }
    this.getMinimumHeightForAnnotationPane = getMinimumHeightForAnnotationPane;
    function addAnnotations(annotationElements) {
      if (annotationElements.length === 0) {
        return;
      }
      showAnnotationsPane(true);
      annotationElements.forEach(function(annotation) {
        annotations.push(annotation);
        wrapAnnotation(annotation);
        if (annotation.annotationEndElement) {
          highlightAnnotation(annotation);
        }
      });
      sortAnnotations();
      rerenderAnnotations();
    }
    this.addAnnotations = addAnnotations;
    function forgetAnnotation(annotation) {
      var index = annotations.indexOf(annotation);
      unwrapAnnotation(annotation);
      unhighlightAnnotation(annotation);
      if (index !== -1) {
        annotations.splice(index, 1);
      }
      if (annotations.length === 0) {
        showAnnotationsPane(false);
      }
    }
    this.forgetAnnotation = forgetAnnotation;
    function forgetAnnotations() {
      while (annotations.length) {
        forgetAnnotation(annotations[0]);
      }
    }
    this.forgetAnnotations = forgetAnnotations;
  };
  gui.Viewport = function Viewport() {
  };
  gui.Viewport.prototype.scrollIntoView = function(clientRect, alignWithTop) {
  };
  gui.SingleScrollViewport = function(scrollPane) {
    var VIEW_PADDING_PX = 5;
    function shrinkClientRectByMargin(clientRect, margin) {
      return {left:clientRect.left + margin.left, top:clientRect.top + margin.top, right:clientRect.right - margin.right, bottom:clientRect.bottom - margin.bottom};
    }
    function height(clientRect) {
      return clientRect.bottom - clientRect.top;
    }
    function width(clientRect) {
      return clientRect.right - clientRect.left;
    }
    this.scrollIntoView = function(clientRect, alignWithTop) {
      var verticalScrollbarHeight = scrollPane.offsetHeight - scrollPane.clientHeight, horizontalScrollbarWidth = scrollPane.offsetWidth - scrollPane.clientWidth, nonNullClientRect, scrollPaneRect = scrollPane.getBoundingClientRect(), paneRect;
      if (!clientRect || !scrollPaneRect) {
        return;
      }
      nonNullClientRect = clientRect;
      paneRect = shrinkClientRectByMargin(scrollPaneRect, {top:VIEW_PADDING_PX, bottom:verticalScrollbarHeight + VIEW_PADDING_PX, left:VIEW_PADDING_PX, right:horizontalScrollbarWidth + VIEW_PADDING_PX});
      if (alignWithTop || nonNullClientRect.top < paneRect.top) {
        scrollPane.scrollTop -= paneRect.top - nonNullClientRect.top;
      } else {
        if (nonNullClientRect.top > paneRect.bottom || nonNullClientRect.bottom > paneRect.bottom) {
          if (height(nonNullClientRect) <= height(paneRect)) {
            scrollPane.scrollTop += nonNullClientRect.bottom - paneRect.bottom;
          } else {
            scrollPane.scrollTop += nonNullClientRect.top - paneRect.top;
          }
        }
      }
      if (nonNullClientRect.left < paneRect.left) {
        scrollPane.scrollLeft -= paneRect.left - nonNullClientRect.left;
      } else {
        if (nonNullClientRect.right > paneRect.right) {
          if (width(nonNullClientRect) <= width(paneRect)) {
            scrollPane.scrollLeft += nonNullClientRect.right - paneRect.right;
          } else {
            scrollPane.scrollLeft -= paneRect.left - nonNullClientRect.left;
          }
        }
      }
    };
  };
  (function() {
    var xpath = xmldom.XPath, odfUtils = odf.OdfUtils, base64 = new core.Base64;
    function getEmbeddedFontDeclarations(fontFaceDecls) {
      var decls = {}, fonts, i, font, name, uris, href, family;
      if (!fontFaceDecls) {
        return decls;
      }
      fonts = xpath.getODFElementsWithXPath(fontFaceDecls, "style:font-face[svg:font-face-src]", odf.Namespaces.lookupNamespaceURI);
      for (i = 0;i < fonts.length;i += 1) {
        font = fonts[i];
        name = font.getAttributeNS(odf.Namespaces.stylens, "name");
        family = odfUtils.getNormalizedFontFamilyName(font.getAttributeNS(odf.Namespaces.svgns, "font-family"));
        uris = xpath.getODFElementsWithXPath(font, "svg:font-face-src/svg:font-face-uri", odf.Namespaces.lookupNamespaceURI);
        if (uris.length > 0) {
          href = uris[0].getAttributeNS(odf.Namespaces.xlinkns, "href");
          decls[name] = {href:href, family:family};
        }
      }
      return decls;
    }
    function addFontToCSS(name, font, fontdata, stylesheet) {
      var cssFamily = font.family || name, rule = "@font-face { font-family: " + cssFamily + "; src: " + "url(data:application/x-font-ttf;charset=binary;base64," + base64.convertUTF8ArrayToBase64(fontdata) + ') format("truetype"); }';
      try {
        stylesheet.insertRule(rule, stylesheet.cssRules.length);
      } catch (e) {
        runtime.log("Problem inserting rule in CSS: " + runtime.toJson(e) + "\nRule: " + rule);
      }
    }
    function loadFontIntoCSS(embeddedFontDeclarations, odfContainer, pos, stylesheet, callback) {
      var name, i = 0, n;
      for (n in embeddedFontDeclarations) {
        if (embeddedFontDeclarations.hasOwnProperty(n)) {
          if (i === pos) {
            name = n;
            break;
          }
          i += 1;
        }
      }
      if (!name) {
        if (callback) {
          callback();
        }
        return;
      }
      odfContainer.getPartData(embeddedFontDeclarations[name].href, function(err, fontdata) {
        if (err) {
          runtime.log(err);
        } else {
          if (!fontdata) {
            runtime.log("missing font data for " + embeddedFontDeclarations[name].href);
          } else {
            addFontToCSS(name, embeddedFontDeclarations[name], fontdata, stylesheet);
          }
        }
        loadFontIntoCSS(embeddedFontDeclarations, odfContainer, pos + 1, stylesheet, callback);
      });
    }
    function loadFontsIntoCSS(embeddedFontDeclarations, odfContainer, stylesheet) {
      loadFontIntoCSS(embeddedFontDeclarations, odfContainer, 0, stylesheet);
    }
    odf.FontLoader = function FontLoader() {
      this.loadFonts = function(odfContainer, stylesheet) {
        var embeddedFontDeclarations, fontFaceDecls = odfContainer.rootElement.fontFaceDecls;
        while (stylesheet.cssRules.length) {
          stylesheet.deleteRule(stylesheet.cssRules.length - 1);
        }
        if (fontFaceDecls) {
          embeddedFontDeclarations = getEmbeddedFontDeclarations(fontFaceDecls);
          loadFontsIntoCSS(embeddedFontDeclarations, odfContainer, stylesheet);
        }
      };
    };
  })();
  odf.Formatting = function Formatting() {
    var odfContainer, styleInfo = new odf.StyleInfo, svgns = odf.Namespaces.svgns, stylens = odf.Namespaces.stylens, textns = odf.Namespaces.textns, numberns = odf.Namespaces.numberns, fons = odf.Namespaces.fons, odfUtils = odf.OdfUtils, domUtils = core.DomUtils, utils = new core.Utils, cssUnits = new core.CSSUnits, builtInDefaultStyleAttributesByFamily = {"paragraph":{"style:paragraph-properties":{"fo:text-align":"left"}}}, defaultPageFormatSettings = {width:"21.001cm", height:"29.7cm", margin:"2cm",
    padding:"0cm"};
    function getSystemDefaultStyleAttributes(styleFamily) {
      var result, builtInDefaultStyleAttributes = builtInDefaultStyleAttributesByFamily[styleFamily];
      if (builtInDefaultStyleAttributes) {
        result = utils.mergeObjects({}, builtInDefaultStyleAttributes);
      } else {
        result = {};
      }
      return result;
    }
    this.getSystemDefaultStyleAttributes = getSystemDefaultStyleAttributes;
    this.setOdfContainer = function(odfcontainer) {
      odfContainer = odfcontainer;
    };
    function getFontMap() {
      var fontFaceDecls = odfContainer.rootElement.fontFaceDecls, fontFaceDeclsMap = {}, node, name, family;
      node = fontFaceDecls && fontFaceDecls.firstElementChild;
      while (node) {
        name = node.getAttributeNS(stylens, "name");
        if (name) {
          family = node.getAttributeNS(svgns, "font-family");
          if (family || node.getElementsByTagNameNS(svgns, "font-face-uri").length > 0) {
            fontFaceDeclsMap[name] = family;
          }
        }
        node = node.nextElementSibling;
      }
      return fontFaceDeclsMap;
    }
    this.getFontMap = getFontMap;
    this.getAvailableParagraphStyles = function() {
      var node = odfContainer.rootElement.styles, p_family, p_name, p_displayName, paragraphStyles = [];
      node = node && node.firstElementChild;
      while (node) {
        if (node.localName === "style" && node.namespaceURI === stylens) {
          p_family = node.getAttributeNS(stylens, "family");
          if (p_family === "paragraph") {
            p_name = node.getAttributeNS(stylens, "name");
            p_displayName = node.getAttributeNS(stylens, "display-name") || p_name;
            if (p_name && p_displayName) {
              paragraphStyles.push({name:p_name, displayName:p_displayName});
            }
          }
        }
        node = node.nextElementSibling;
      }
      return paragraphStyles;
    };
    this.isStyleUsed = function(styleElement) {
      var hasDerivedStyles, isUsed, root = odfContainer.rootElement;
      hasDerivedStyles = styleInfo.hasDerivedStyles(root, odf.Namespaces.lookupNamespaceURI, styleElement);
      isUsed = (new styleInfo.UsedStyleList(root.styles)).uses(styleElement) || (new styleInfo.UsedStyleList(root.automaticStyles)).uses(styleElement) || (new styleInfo.UsedStyleList(root.body)).uses(styleElement);
      return hasDerivedStyles || isUsed;
    };
    function getDefaultStyleElement(family) {
      var node = odfContainer.rootElement.styles.firstElementChild;
      while (node) {
        if (node.namespaceURI === stylens && node.localName === "default-style" && node.getAttributeNS(stylens, "family") === family) {
          return node;
        }
        node = node.nextElementSibling;
      }
      return null;
    }
    this.getDefaultStyleElement = getDefaultStyleElement;
    function getStyleElement(styleName, family, styleElements) {
      var node, nodeStyleName, styleListElement, i;
      styleElements = styleElements || [odfContainer.rootElement.automaticStyles, odfContainer.rootElement.styles];
      for (i = 0;i < styleElements.length;i += 1) {
        styleListElement = styleElements[i];
        node = styleListElement.firstElementChild;
        while (node) {
          nodeStyleName = node.getAttributeNS(stylens, "name");
          if (node.namespaceURI === stylens && node.localName === "style" && node.getAttributeNS(stylens, "family") === family && nodeStyleName === styleName) {
            return node;
          }
          if (family === "list-style" && node.namespaceURI === textns && node.localName === "list-style" && nodeStyleName === styleName) {
            return node;
          }
          if (family === "data" && node.namespaceURI === numberns && nodeStyleName === styleName) {
            return node;
          }
          node = node.nextElementSibling;
        }
      }
      return null;
    }
    this.getStyleElement = getStyleElement;
    function getStyleAttributes(styleNode) {
      var i, a, map, ai, propertiesMap = {}, propertiesNode = styleNode.firstElementChild;
      while (propertiesNode) {
        if (propertiesNode.namespaceURI === stylens) {
          map = propertiesMap[propertiesNode.nodeName] = {};
          a = propertiesNode.attributes;
          for (i = 0;i < a.length;i += 1) {
            ai = a.item(i);
            map[ai.name] = ai.value;
          }
        }
        propertiesNode = propertiesNode.nextElementSibling;
      }
      a = styleNode.attributes;
      for (i = 0;i < a.length;i += 1) {
        ai = a.item(i);
        propertiesMap[ai.name] = ai.value;
      }
      return propertiesMap;
    }
    this.getStyleAttributes = getStyleAttributes;
    function getInheritedStyleAttributes(styleNode, includeSystemDefault) {
      var styleListElement = odfContainer.rootElement.styles, parentStyleName, propertiesMap, inheritedPropertiesMap = {}, styleFamily = styleNode.getAttributeNS(stylens, "family"), node = styleNode;
      while (node) {
        propertiesMap = getStyleAttributes(node);
        inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap);
        parentStyleName = node.getAttributeNS(stylens, "parent-style-name");
        if (parentStyleName) {
          node = getStyleElement(parentStyleName, styleFamily, [styleListElement]);
        } else {
          node = null;
        }
      }
      node = getDefaultStyleElement(styleFamily);
      if (node) {
        propertiesMap = getStyleAttributes(node);
        inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap);
      }
      if (includeSystemDefault !== false) {
        propertiesMap = getSystemDefaultStyleAttributes(styleFamily);
        inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap);
      }
      return inheritedPropertiesMap;
    }
    this.getInheritedStyleAttributes = getInheritedStyleAttributes;
    this.getFirstCommonParentStyleNameOrSelf = function(styleName) {
      var automaticStyleElementList = odfContainer.rootElement.automaticStyles, styleElementList = odfContainer.rootElement.styles, styleElement;
      styleElement = getStyleElement(styleName, "paragraph", [automaticStyleElementList]);
      if (styleElement) {
        styleName = styleElement.getAttributeNS(stylens, "parent-style-name");
        if (!styleName) {
          return null;
        }
      }
      styleElement = getStyleElement(styleName, "paragraph", [styleElementList]);
      if (!styleElement) {
        return null;
      }
      return styleName;
    };
    this.hasParagraphStyle = function(styleName) {
      return Boolean(getStyleElement(styleName, "paragraph"));
    };
    function buildStyleChain(node, collectedChains) {
      var parent = node.nodeType === Node.TEXT_NODE ? node.parentNode : node, nodeStyles, appliedStyles = [], chainKey = "", foundContainer = false;
      while (parent && !odfUtils.isInlineRoot(parent) && parent.parentNode !== odfContainer.rootElement) {
        if (!foundContainer && odfUtils.isGroupingElement(parent)) {
          foundContainer = true;
        }
        nodeStyles = styleInfo.determineStylesForNode(parent);
        if (nodeStyles) {
          appliedStyles.push(nodeStyles);
        }
        parent = parent.parentNode;
      }
      function chainStyles(usedStyleMap) {
        Object.keys(usedStyleMap).forEach(function(styleFamily) {
          Object.keys(usedStyleMap[styleFamily]).forEach(function(styleName) {
            chainKey += "|" + styleFamily + ":" + styleName + "|";
          });
        });
      }
      if (foundContainer) {
        appliedStyles.forEach(chainStyles);
        if (collectedChains) {
          collectedChains[chainKey] = appliedStyles;
        }
      }
      return foundContainer ? appliedStyles : undefined;
    }
    function isCommonStyleElement(styleNode) {
      return styleNode.parentNode === odfContainer.rootElement.styles;
    }
    function calculateAppliedStyle(styleChain) {
      var mergedChildStyle = {orderedStyles:[], styleProperties:{}};
      styleChain.forEach(function(elementStyleSet) {
        Object.keys(elementStyleSet).forEach(function(styleFamily) {
          var styleName = Object.keys(elementStyleSet[styleFamily])[0], styleSummary = {name:styleName, family:styleFamily, displayName:undefined, isCommonStyle:false}, styleElement, parentStyle;
          styleElement = getStyleElement(styleName, styleFamily);
          if (styleElement) {
            parentStyle = getInheritedStyleAttributes(styleElement);
            mergedChildStyle.styleProperties = utils.mergeObjects(parentStyle, mergedChildStyle.styleProperties);
            styleSummary.displayName = styleElement.getAttributeNS(stylens, "display-name") || undefined;
            styleSummary.isCommonStyle = isCommonStyleElement(styleElement);
          } else {
            runtime.log("No style element found for '" + styleName + "' of family '" + styleFamily + "'");
          }
          mergedChildStyle.orderedStyles.push(styleSummary);
        });
      });
      return mergedChildStyle;
    }
    function getAppliedStyles(nodes, calculatedStylesCache) {
      var styleChains = {}, styles = [];
      if (!calculatedStylesCache) {
        calculatedStylesCache = {};
      }
      nodes.forEach(function(n) {
        buildStyleChain(n, styleChains);
      });
      Object.keys(styleChains).forEach(function(key) {
        if (!calculatedStylesCache[key]) {
          calculatedStylesCache[key] = calculateAppliedStyle(styleChains[key]);
        }
        styles.push(calculatedStylesCache[key]);
      });
      return styles;
    }
    this.getAppliedStyles = getAppliedStyles;
    this.getAppliedStylesForElement = function(node, calculatedStylesCache) {
      return getAppliedStyles([node], calculatedStylesCache)[0];
    };
    this.updateStyle = function(styleNode, properties) {
      var fontName, fontFaceNode, textProperties;
      domUtils.mapObjOntoNode(styleNode, properties, odf.Namespaces.lookupNamespaceURI);
      textProperties = properties["style:text-properties"];
      fontName = textProperties && textProperties["style:font-name"];
      if (fontName && !getFontMap().hasOwnProperty(fontName)) {
        fontFaceNode = styleNode.ownerDocument.createElementNS(stylens, "style:font-face");
        fontFaceNode.setAttributeNS(stylens, "style:name", fontName);
        fontFaceNode.setAttributeNS(svgns, "svg:font-family", fontName);
        odfContainer.rootElement.fontFaceDecls.appendChild(fontFaceNode);
      }
    };
    this.createDerivedStyleObject = function(parentStyleName, family, overrides) {
      var originalStyleElement = getStyleElement(parentStyleName, family), newStyleObject;
      runtime.assert(Boolean(originalStyleElement), "No style element found for '" + parentStyleName + "' of family '" + family + "'");
      if (isCommonStyleElement(originalStyleElement)) {
        newStyleObject = {"style:parent-style-name":parentStyleName};
      } else {
        newStyleObject = getStyleAttributes(originalStyleElement);
      }
      newStyleObject["style:family"] = family;
      utils.mergeObjects(newStyleObject, overrides);
      return newStyleObject;
    };
    this.getDefaultTabStopDistance = function() {
      var defaultParagraph = getDefaultStyleElement("paragraph"), paragraphProperties = defaultParagraph && defaultParagraph.firstElementChild, tabStopDistance;
      while (paragraphProperties) {
        if (paragraphProperties.namespaceURI === stylens && paragraphProperties.localName === "paragraph-properties") {
          tabStopDistance = paragraphProperties.getAttributeNS(stylens, "tab-stop-distance");
        }
        paragraphProperties = paragraphProperties.nextElementSibling;
      }
      if (!tabStopDistance) {
        tabStopDistance = "1.25cm";
      }
      return odfUtils.parseNonNegativeLength(tabStopDistance);
    };
    function getMasterPageElement(pageName) {
      var node = odfContainer.rootElement.masterStyles.firstElementChild;
      while (node) {
        if (node.namespaceURI === stylens && node.localName === "master-page" && node.getAttributeNS(stylens, "name") === pageName) {
          break;
        }
        node = node.nextElementSibling;
      }
      return node;
    }
    this.getMasterPageElement = getMasterPageElement;
    function getPageLayoutStyleElement(styleName, styleFamily) {
      var masterPageName, layoutName, pageLayoutElements, node, i, styleElement = getStyleElement(styleName, styleFamily);
      runtime.assert(styleFamily === "paragraph" || styleFamily === "table", "styleFamily must be either paragraph or table");
      if (styleElement) {
        masterPageName = styleElement.getAttributeNS(stylens, "master-page-name");
        if (masterPageName) {
          node = getMasterPageElement(masterPageName);
          if (!node) {
            runtime.log("WARN: No master page definition found for " + masterPageName);
          }
        }
        if (!node) {
          node = getMasterPageElement("Standard");
        }
        if (!node) {
          node = odfContainer.rootElement.masterStyles.getElementsByTagNameNS(stylens, "master-page")[0];
          if (!node) {
            runtime.log("WARN: Document has no master pages defined");
          }
        }
        if (node) {
          layoutName = node.getAttributeNS(stylens, "page-layout-name");
          pageLayoutElements = odfContainer.rootElement.automaticStyles.getElementsByTagNameNS(stylens, "page-layout");
          for (i = 0;i < pageLayoutElements.length;i += 1) {
            node = pageLayoutElements.item(i);
            if (node.getAttributeNS(stylens, "name") === layoutName) {
              return node;
            }
          }
        }
      }
      return null;
    }
    function lengthInPx(length, defaultValue) {
      var measure;
      if (length) {
        measure = cssUnits.convertMeasure(length, "px");
      }
      if (measure === undefined && defaultValue) {
        measure = cssUnits.convertMeasure(defaultValue, "px");
      }
      return measure;
    }
    this.getContentSize = function(styleName, styleFamily) {
      var pageLayoutElement, props, defaultOrientedPageWidth, defaultOrientedPageHeight, pageWidth, pageHeight, margin, marginLeft, marginRight, marginTop, marginBottom, padding, paddingLeft, paddingRight, paddingTop, paddingBottom;
      pageLayoutElement = getPageLayoutStyleElement(styleName, styleFamily);
      if (!pageLayoutElement) {
        pageLayoutElement = domUtils.getDirectChild(odfContainer.rootElement.styles, stylens, "default-page-layout");
      }
      props = domUtils.getDirectChild(pageLayoutElement, stylens, "page-layout-properties");
      if (props) {
        if (props.getAttributeNS(stylens, "print-orientation") === "landscape") {
          defaultOrientedPageWidth = defaultPageFormatSettings.height;
          defaultOrientedPageHeight = defaultPageFormatSettings.width;
        } else {
          defaultOrientedPageWidth = defaultPageFormatSettings.width;
          defaultOrientedPageHeight = defaultPageFormatSettings.height;
        }
        pageWidth = lengthInPx(props.getAttributeNS(fons, "page-width"), defaultOrientedPageWidth);
        pageHeight = lengthInPx(props.getAttributeNS(fons, "page-height"), defaultOrientedPageHeight);
        margin = lengthInPx(props.getAttributeNS(fons, "margin"));
        if (margin === undefined) {
          marginLeft = lengthInPx(props.getAttributeNS(fons, "margin-left"), defaultPageFormatSettings.margin);
          marginRight = lengthInPx(props.getAttributeNS(fons, "margin-right"), defaultPageFormatSettings.margin);
          marginTop = lengthInPx(props.getAttributeNS(fons, "margin-top"), defaultPageFormatSettings.margin);
          marginBottom = lengthInPx(props.getAttributeNS(fons, "margin-bottom"), defaultPageFormatSettings.margin);
        } else {
          marginLeft = marginRight = marginTop = marginBottom = margin;
        }
        padding = lengthInPx(props.getAttributeNS(fons, "padding"));
        if (padding === undefined) {
          paddingLeft = lengthInPx(props.getAttributeNS(fons, "padding-left"), defaultPageFormatSettings.padding);
          paddingRight = lengthInPx(props.getAttributeNS(fons, "padding-right"), defaultPageFormatSettings.padding);
          paddingTop = lengthInPx(props.getAttributeNS(fons, "padding-top"), defaultPageFormatSettings.padding);
          paddingBottom = lengthInPx(props.getAttributeNS(fons, "padding-bottom"), defaultPageFormatSettings.padding);
        } else {
          paddingLeft = paddingRight = paddingTop = paddingBottom = padding;
        }
      } else {
        pageWidth = lengthInPx(defaultPageFormatSettings.width);
        pageHeight = lengthInPx(defaultPageFormatSettings.height);
        margin = lengthInPx(defaultPageFormatSettings.margin);
        marginLeft = marginRight = marginTop = marginBottom = margin;
        padding = lengthInPx(defaultPageFormatSettings.padding);
        paddingLeft = paddingRight = paddingTop = paddingBottom = padding;
      }
      return {width:pageWidth - marginLeft - marginRight - paddingLeft - paddingRight, height:pageHeight - marginTop - marginBottom - paddingTop - paddingBottom};
    };
  };
  odf.Formatting.StyleMetadata;
  odf.Formatting.StyleData;
  odf.Formatting.AppliedStyle;
  (function() {
    var stylens = odf.Namespaces.stylens, textns = odf.Namespaces.textns, familyNamespacePrefixes = {"graphic":"draw", "drawing-page":"draw", "paragraph":"text", "presentation":"presentation", "ruby":"text", "section":"text", "table":"table", "table-cell":"table", "table-column":"table", "table-row":"table", "text":"text", "list":"text", "page":"office"};
    odf.StyleTreeNode = function StyleTreeNode(element) {
      this.derivedStyles = {};
      this.element = element;
    };
    odf.StyleTree = function StyleTree(styles, autoStyles) {
      var tree = {};
      function getStyleMap(stylesNode) {
        var node, name, family, style, styleMap = {};
        if (!stylesNode) {
          return styleMap;
        }
        node = stylesNode.firstElementChild;
        while (node) {
          if (node.namespaceURI === stylens && (node.localName === "style" || node.localName === "default-style")) {
            family = node.getAttributeNS(stylens, "family");
          } else {
            if (node.namespaceURI === textns && node.localName === "list-style") {
              family = "list";
            } else {
              if (node.namespaceURI === stylens && (node.localName === "page-layout" || node.localName === "default-page-layout")) {
                family = "page";
              } else {
                family = undefined;
              }
            }
          }
          if (family) {
            name = node.getAttributeNS(stylens, "name");
            if (!name) {
              name = "";
            }
            if (styleMap.hasOwnProperty(family)) {
              style = styleMap[family];
            } else {
              styleMap[family] = style = {};
            }
            style[name] = node;
          }
          node = node.nextElementSibling;
        }
        return styleMap;
      }
      function findStyleTreeNode(stylesTree, name) {
        if (stylesTree.hasOwnProperty(name)) {
          return stylesTree[name];
        }
        var style = null, styleNames = Object.keys(stylesTree), i;
        for (i = 0;i < styleNames.length;i += 1) {
          style = findStyleTreeNode(stylesTree[styleNames[i]].derivedStyles, name);
          if (style) {
            break;
          }
        }
        return style;
      }
      function createStyleTreeNode(styleName, stylesMap, stylesTree) {
        var style, parentname, parentstyle;
        if (!stylesMap.hasOwnProperty(styleName)) {
          return null;
        }
        style = new odf.StyleTreeNode(stylesMap[styleName]);
        parentname = style.element.getAttributeNS(stylens, "parent-style-name");
        parentstyle = null;
        if (parentname) {
          parentstyle = findStyleTreeNode(stylesTree, parentname) || createStyleTreeNode(parentname, stylesMap, stylesTree);
        }
        if (parentstyle) {
          parentstyle.derivedStyles[styleName] = style;
        } else {
          stylesTree[styleName] = style;
        }
        delete stylesMap[styleName];
        return style;
      }
      function addStyleMapToStyleTree(stylesMap, stylesTree) {
        if (stylesMap) {
          Object.keys(stylesMap).forEach(function(styleName) {
            createStyleTreeNode(styleName, stylesMap, stylesTree);
          });
        }
      }
      this.getStyleTree = function() {
        return tree;
      };
      function init() {
        var subTree, styleNodes, autoStyleNodes;
        styleNodes = getStyleMap(styles);
        autoStyleNodes = getStyleMap(autoStyles);
        Object.keys(familyNamespacePrefixes).forEach(function(family) {
          subTree = tree[family] = {};
          addStyleMapToStyleTree(styleNodes[family], subTree);
          addStyleMapToStyleTree(autoStyleNodes[family], subTree);
        });
      }
      init();
    };
  })();
  odf.StyleTree.Tree;
  (function() {
    var fons = odf.Namespaces.fons, stylens = odf.Namespaces.stylens, textns = odf.Namespaces.textns, xmlns = odf.Namespaces.xmlns, helperns = "urn:webodf:names:helper", listCounterIdSuffix = "webodf-listLevel", stylemap = {1:"decimal", "a":"lower-latin", "A":"upper-latin", "i":"lower-roman", "I":"upper-roman"};
    function appendRule(styleSheet, rule) {
      try {
        styleSheet.insertRule(rule, styleSheet.cssRules.length);
      } catch (e) {
        runtime.log("cannot load rule: " + rule + " - " + e);
      }
    }
    function ParseState(contentRules, continuedCounterIdStack) {
      this.listCounterCount = 0;
      this.contentRules = contentRules;
      this.counterIdStack = [];
      this.continuedCounterIdStack = continuedCounterIdStack;
    }
    function UniqueListCounter(styleSheet) {
      var customListIdIndex = 0, globalCounterResetRule = "", counterIdStacks = {};
      function getCounterIdStack(list) {
        var counterId, stack = [];
        if (list) {
          counterId = list.getAttributeNS(helperns, "counter-id");
          stack = counterIdStacks[counterId].slice(0);
        }
        return stack;
      }
      function createCssRulesForList(topLevelListId, listElement, listLevel, parseState) {
        var newListSelectorId, newListCounterId, newRule, contentRule, i;
        parseState.listCounterCount += 1;
        newListSelectorId = topLevelListId + "-level" + listLevel + "-" + parseState.listCounterCount;
        listElement.setAttributeNS(helperns, "counter-id", newListSelectorId);
        newListCounterId = parseState.continuedCounterIdStack.shift();
        if (!newListCounterId) {
          newListCounterId = newListSelectorId;
          globalCounterResetRule += newListSelectorId + " 1 ";
          newRule = 'text|list[webodfhelper|counter-id="' + newListSelectorId + '"]';
          newRule += " > text|list-item:first-child > :not(text|list):first-child:before";
          newRule += "{";
          newRule += "counter-increment: " + newListCounterId + " 0;";
          newRule += "}";
          appendRule(styleSheet, newRule);
        }
        while (parseState.counterIdStack.length >= listLevel) {
          parseState.counterIdStack.pop();
        }
        parseState.counterIdStack.push(newListCounterId);
        contentRule = parseState.contentRules[listLevel.toString()] || "";
        for (i = 1;i <= listLevel;i += 1) {
          contentRule = contentRule.replace(i + listCounterIdSuffix, parseState.counterIdStack[i - 1]);
        }
        newRule = 'text|list[webodfhelper|counter-id="' + newListSelectorId + '"]';
        newRule += " > text|list-item > :not(text|list):first-child:before";
        newRule += "{";
        newRule += contentRule;
        newRule += "counter-increment: " + newListCounterId + ";";
        newRule += "}";
        appendRule(styleSheet, newRule);
      }
      function iterateOverChildListElements(topLevelListId, element, listLevel, parseState) {
        var isListElement = element.namespaceURI === textns && element.localName === "list", isListItemElement = element.namespaceURI === textns && element.localName === "list-item", childElement;
        if (!isListElement && !isListItemElement) {
          parseState.continuedCounterIdStack = [];
          return;
        }
        if (isListElement) {
          listLevel += 1;
          createCssRulesForList(topLevelListId, element, listLevel, parseState);
        }
        childElement = element.firstElementChild;
        while (childElement) {
          iterateOverChildListElements(topLevelListId, childElement, listLevel, parseState);
          childElement = childElement.nextElementSibling;
        }
      }
      this.createCounterRules = function(contentRules, list, continuedList) {
        var listId = list.getAttributeNS(xmlns, "id"), currentParseState = new ParseState(contentRules, getCounterIdStack(continuedList));
        if (!listId) {
          customListIdIndex += 1;
          listId = "X" + customListIdIndex;
        } else {
          listId = "Y" + listId;
        }
        iterateOverChildListElements(listId, list, 0, currentParseState);
        counterIdStacks[listId + "-level1-1"] = currentParseState.counterIdStack;
      };
      this.initialiseCreatedCounters = function() {
        var newRule;
        newRule = "office|document";
        newRule += "{";
        newRule += "counter-reset: " + globalCounterResetRule + ";";
        newRule += "}";
        appendRule(styleSheet, newRule);
      };
    }
    odf.ListStyleToCss = function ListStyleToCss() {
      var cssUnits = new core.CSSUnits, odfUtils = odf.OdfUtils;
      function convertToPxValue(value) {
        var parsedLength = odfUtils.parseLength(value);
        if (!parsedLength) {
          runtime.log("Could not parse value '" + value + "'.");
          return 0;
        }
        return cssUnits.convert(parsedLength.value, parsedLength.unit, "px");
      }
      function escapeCSSString(value) {
        return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
      }
      function isMatchingListStyle(list, matchingStyleName) {
        var styleName;
        if (list) {
          styleName = list.getAttributeNS(textns, "style-name");
        }
        return styleName === matchingStyleName;
      }
      function getNumberRule(node) {
        var style = node.getAttributeNS(stylens, "num-format"), suffix = node.getAttributeNS(stylens, "num-suffix") || "", prefix = node.getAttributeNS(stylens, "num-prefix") || "", content = "", textLevel = node.getAttributeNS(textns, "level"), displayLevels = node.getAttributeNS(textns, "display-levels");
        if (prefix) {
          content += '"' + escapeCSSString(prefix) + '"\n';
        }
        if (stylemap.hasOwnProperty(style)) {
          textLevel = textLevel ? parseInt(textLevel, 10) : 1;
          displayLevels = displayLevels ? parseInt(displayLevels, 10) : 1;
          while (displayLevels > 0) {
            content += " counter(" + (textLevel - displayLevels + 1) + listCounterIdSuffix + "," + stylemap[style] + ")";
            if (displayLevels > 1) {
              content += '"."';
            }
            displayLevels -= 1;
          }
        } else {
          if (style) {
            content += ' "' + style + '"';
          } else {
            content += ' ""';
          }
        }
        return "content:" + content + ' "' + escapeCSSString(suffix) + '"';
      }
      function getImageRule() {
        return "content: none";
      }
      function getBulletRule(node) {
        var bulletChar = node.getAttributeNS(textns, "bullet-char");
        return 'content: "' + escapeCSSString(bulletChar) + '"';
      }
      function getContentRule(node) {
        var contentRule = "", listLevelProps, listLevelPositionSpaceMode, listLevelLabelAlign, followedBy;
        if (node.localName === "list-level-style-number") {
          contentRule = getNumberRule(node);
        } else {
          if (node.localName === "list-level-style-image") {
            contentRule = getImageRule();
          } else {
            if (node.localName === "list-level-style-bullet") {
              contentRule = getBulletRule(node);
            }
          }
        }
        listLevelProps = node.getElementsByTagNameNS(stylens, "list-level-properties")[0];
        if (listLevelProps) {
          listLevelPositionSpaceMode = listLevelProps.getAttributeNS(textns, "list-level-position-and-space-mode");
          if (listLevelPositionSpaceMode === "label-alignment") {
            listLevelLabelAlign = listLevelProps.getElementsByTagNameNS(stylens, "list-level-label-alignment")[0];
            if (listLevelLabelAlign) {
              followedBy = listLevelLabelAlign.getAttributeNS(textns, "label-followed-by");
            }
            if (followedBy === "space") {
              contentRule += ' "\\a0"';
            }
          }
        }
        return "\n" + contentRule + ";\n";
      }
      function getAllContentRules(listStyleNode) {
        var childNode = listStyleNode.firstElementChild, level, rules = {};
        while (childNode) {
          level = childNode.getAttributeNS(textns, "level");
          level = level && parseInt(level, 10);
          rules[level] = getContentRule(childNode);
          childNode = childNode.nextElementSibling;
        }
        return rules;
      }
      function addListStyleRule(styleSheet, name, node) {
        var selector = 'text|list[text|style-name="' + name + '"]', level = node.getAttributeNS(textns, "level"), selectorLevel, listItemRule, listLevelProps, listLevelPositionSpaceMode, listLevelLabelAlign, listIndent, textAlign, bulletWidth, labelDistance, bulletIndent, followedBy, leftOffset;
        listLevelProps = node.getElementsByTagNameNS(stylens, "list-level-properties")[0];
        listLevelPositionSpaceMode = listLevelProps && listLevelProps.getAttributeNS(textns, "list-level-position-and-space-mode");
        listLevelLabelAlign = listLevelProps && listLevelProps.getElementsByTagNameNS(stylens, "list-level-label-alignment")[0];
        level = level && parseInt(level, 10);
        selectorLevel = level;
        while (selectorLevel > 1) {
          selector += " > text|list-item > text|list";
          selectorLevel -= 1;
        }
        textAlign = listLevelProps && listLevelProps.getAttributeNS(fons, "text-align") || "left";
        switch(textAlign) {
          case "end":
            textAlign = "right";
            break;
          case "start":
            textAlign = "left";
            break;
        }
        if (listLevelPositionSpaceMode === "label-alignment") {
          listIndent = listLevelLabelAlign && listLevelLabelAlign.getAttributeNS(fons, "margin-left") || "0px";
          bulletIndent = listLevelLabelAlign && listLevelLabelAlign.getAttributeNS(fons, "text-indent") || "0px";
          followedBy = listLevelLabelAlign && listLevelLabelAlign.getAttributeNS(textns, "label-followed-by");
          leftOffset = convertToPxValue(listIndent);
        } else {
          listIndent = listLevelProps && listLevelProps.getAttributeNS(textns, "space-before") || "0px";
          bulletWidth = listLevelProps && listLevelProps.getAttributeNS(textns, "min-label-width") || "0px";
          labelDistance = listLevelProps && listLevelProps.getAttributeNS(textns, "min-label-distance") || "0px";
          leftOffset = convertToPxValue(listIndent) + convertToPxValue(bulletWidth);
        }
        listItemRule = selector + " > text|list-item";
        listItemRule += "{";
        listItemRule += "margin-left: " + leftOffset + "px;";
        listItemRule += "}";
        appendRule(styleSheet, listItemRule);
        listItemRule = selector + " > text|list-item > text|list";
        listItemRule += "{";
        listItemRule += "margin-left: " + -leftOffset + "px;";
        listItemRule += "}";
        appendRule(styleSheet, listItemRule);
        listItemRule = selector + " > text|list-item > :not(text|list):first-child:before";
        listItemRule += "{";
        listItemRule += "text-align: " + textAlign + ";";
        listItemRule += "display: inline-block;";
        if (listLevelPositionSpaceMode === "label-alignment") {
          listItemRule += "margin-left: " + bulletIndent + ";";
          if (followedBy === "listtab") {
            listItemRule += "padding-right: 0.2cm;";
          }
        } else {
          listItemRule += "min-width: " + bulletWidth + ";";
          listItemRule += "margin-left: " + (parseFloat(bulletWidth) === 0 ? "" : "-") + bulletWidth + ";";
          listItemRule += "padding-right: " + labelDistance + ";";
        }
        listItemRule += "}";
        appendRule(styleSheet, listItemRule);
      }
      function addRule(styleSheet, name, node) {
        var n = node.firstElementChild;
        while (n) {
          if (n.namespaceURI === textns) {
            addListStyleRule(styleSheet, name, n);
          }
          n = n.nextElementSibling;
        }
      }
      function applyContentBasedStyles(styleSheet, odfBody, listStyles) {
        var lists = odfBody.getElementsByTagNameNS(textns, "list"), listCounter = new UniqueListCounter(styleSheet), list, previousList, continueNumbering, continueListXmlId, xmlId, styleName, contentRules, listsWithXmlId = {}, i;
        for (i = 0;i < lists.length;i += 1) {
          list = lists.item(i);
          styleName = list.getAttributeNS(textns, "style-name");
          if (styleName) {
            continueNumbering = list.getAttributeNS(textns, "continue-numbering");
            continueListXmlId = list.getAttributeNS(textns, "continue-list");
            xmlId = list.getAttributeNS(xmlns, "id");
            if (xmlId) {
              listsWithXmlId[xmlId] = list;
            }
            contentRules = getAllContentRules(listStyles[styleName].element);
            if (continueNumbering && !continueListXmlId && isMatchingListStyle(previousList, styleName)) {
              listCounter.createCounterRules(contentRules, list, previousList);
            } else {
              if (continueListXmlId && isMatchingListStyle(listsWithXmlId[continueListXmlId], styleName)) {
                listCounter.createCounterRules(contentRules, list, listsWithXmlId[continueListXmlId]);
              } else {
                listCounter.createCounterRules(contentRules, list);
              }
            }
            previousList = list;
          }
        }
        listCounter.initialiseCreatedCounters();
      }
      this.applyListStyles = function(styleSheet, styleTree, odfBody) {
        var styleFamilyTree, node;
        styleFamilyTree = styleTree["list"];
        if (styleFamilyTree) {
          Object.keys(styleFamilyTree).forEach(function(styleName) {
            node = styleFamilyTree[styleName];
            addRule(styleSheet, styleName, node.element);
          });
        }
        applyContentBasedStyles(styleSheet, odfBody, styleFamilyTree);
      };
    };
  })();
  odf.LazyStyleProperties = function(parent, getters) {
    var data = {};
    this.value = function(name) {
      var v;
      if (data.hasOwnProperty(name)) {
        v = data[name];
      } else {
        v = getters[name]();
        if (v === undefined && parent) {
          v = parent.value(name);
        }
        data[name] = v;
      }
      return v;
    };
    this.reset = function(p) {
      parent = p;
      data = {};
    };
  };
  odf.StyleParseUtils = function() {
    var stylens = odf.Namespaces.stylens;
    function splitLength(length) {
      var re = /(-?[0-9]*[0-9][0-9]*(\.[0-9]*)?|0+\.[0-9]*[1-9][0-9]*|\.[0-9]*[1-9][0-9]*)((cm)|(mm)|(in)|(pt)|(pc)|(px))/, m = re.exec(length);
      if (!m) {
        return null;
      }
      return {value:parseFloat(m[1]), unit:m[3]};
    }
    function parseLength(val) {
      var n, length, unit;
      length = splitLength(val);
      unit = length && length.unit;
      if (unit === "px") {
        n = length.value;
      } else {
        if (unit === "cm") {
          n = length.value / 2.54 * 96;
        } else {
          if (unit === "mm") {
            n = length.value / 25.4 * 96;
          } else {
            if (unit === "in") {
              n = length.value * 96;
            } else {
              if (unit === "pt") {
                n = length.value / .75;
              } else {
                if (unit === "pc") {
                  n = length.value * 16;
                }
              }
            }
          }
        }
      }
      return n;
    }
    this.parseLength = parseLength;
    function parsePercent(value) {
      var v;
      if (value) {
        v = parseFloat(value.substr(0, value.indexOf("%")));
        if (isNaN(v)) {
          v = undefined;
        }
      }
      return v;
    }
    function parsePositiveLengthOrPercent(value, name, parent) {
      var v = parsePercent(value), parentValue;
      if (v !== undefined) {
        if (parent) {
          parentValue = parent.value(name);
        }
        if (parentValue === undefined) {
          v = undefined;
        } else {
          v *= parentValue / 100;
        }
      } else {
        v = parseLength(value);
      }
      return v;
    }
    this.parsePositiveLengthOrPercent = parsePositiveLengthOrPercent;
    function getPropertiesElement(name, styleElement, previousPropertyElement) {
      var e = previousPropertyElement ? previousPropertyElement.nextElementSibling : styleElement.firstElementChild;
      while (e !== null && (e.localName !== name || e.namespaceURI !== stylens)) {
        e = e.nextElementSibling;
      }
      return e;
    }
    this.getPropertiesElement = getPropertiesElement;
    function parseAttributeList(text) {
      if (text) {
        text = text.replace(/^\s*(.*?)\s*$/g, "$1");
      }
      return text && text.length > 0 ? text.split(/\s+/) : [];
    }
    this.parseAttributeList = parseAttributeList;
  };
  odf.Style2CSS = function Style2CSS() {
    var drawns = odf.Namespaces.drawns, fons = odf.Namespaces.fons, officens = odf.Namespaces.officens, stylens = odf.Namespaces.stylens, svgns = odf.Namespaces.svgns, tablens = odf.Namespaces.tablens, xlinkns = odf.Namespaces.xlinkns, presentationns = odf.Namespaces.presentationns, webodfhelperns = "urn:webodf:names:helper", domUtils = core.DomUtils, styleParseUtils = new odf.StyleParseUtils, familynamespaceprefixes = {"graphic":"draw", "drawing-page":"draw", "paragraph":"text", "presentation":"presentation",
    "ruby":"text", "section":"text", "table":"table", "table-cell":"table", "table-column":"table", "table-row":"table", "text":"text", "list":"text", "page":"office"}, familytagnames = {"graphic":["circle", "connected", "control", "custom-shape", "ellipse", "frame", "g", "line", "measure", "page", "page-thumbnail", "path", "polygon", "polyline", "rect", "regular-polygon"], "paragraph":["alphabetical-index-entry-template", "h", "illustration-index-entry-template", "index-source-style", "object-index-entry-template",
    "p", "table-index-entry-template", "table-of-content-entry-template", "user-index-entry-template"], "presentation":["caption", "circle", "connector", "control", "custom-shape", "ellipse", "frame", "g", "line", "measure", "page-thumbnail", "path", "polygon", "polyline", "rect", "regular-polygon"], "drawing-page":["caption", "circle", "connector", "control", "page", "custom-shape", "ellipse", "frame", "g", "line", "measure", "page-thumbnail", "path", "polygon", "polyline", "rect", "regular-polygon"],
    "ruby":["ruby", "ruby-text"], "section":["alphabetical-index", "bibliography", "illustration-index", "index-title", "object-index", "section", "table-of-content", "table-index", "user-index"], "table":["background", "table"], "table-cell":["body", "covered-table-cell", "even-columns", "even-rows", "first-column", "first-row", "last-column", "last-row", "odd-columns", "odd-rows", "table-cell"], "table-column":["table-column"], "table-row":["table-row"], "text":["a", "index-entry-chapter", "index-entry-link-end",
    "index-entry-link-start", "index-entry-page-number", "index-entry-span", "index-entry-tab-stop", "index-entry-text", "index-title-template", "linenumbering-configuration", "list-level-style-number", "list-level-style-bullet", "outline-level-style", "span"], "list":["list-item"]}, textPropertySimpleMapping = [[fons, "color", "color"], [fons, "background-color", "background-color"], [fons, "font-weight", "font-weight"], [fons, "font-style", "font-style"], [fons, "font-variant", "font-variant"], [fons,
    "letter-spacing", "letter-spacing"], [fons, "text-transform", "text-transform"], [fons, "text-shadow", "text-shadow"]], bgImageSimpleMapping = [[stylens, "repeat", "background-repeat"]], paragraphPropertySimpleMapping = [[fons, "background-color", "background-color"], [fons, "text-align", "text-align"], [fons, "text-indent", "text-indent"], [fons, "padding", "padding"], [fons, "padding-left", "padding-left"], [fons, "padding-right", "padding-right"], [fons, "padding-top", "padding-top"], [fons,
    "padding-bottom", "padding-bottom"], [fons, "border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top", "border-top"], [fons, "border-bottom", "border-bottom"], [fons, "margin", "margin"], [fons, "margin-left", "margin-left"], [fons, "margin-right", "margin-right"], [fons, "margin-top", "margin-top"], [fons, "margin-bottom", "margin-bottom"], [fons, "border", "border"]], graphicPropertySimpleMapping = [[fons, "background-color", "background-color"], [fons, "min-height",
    "min-height"], [drawns, "stroke", "border"], [svgns, "stroke-color", "border-color"], [svgns, "stroke-width", "border-width"], [fons, "border", "border"], [fons, "border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top", "border-top"], [fons, "border-bottom", "border-bottom"]], tablecellPropertySimpleMapping = [[fons, "background-color", "background-color"], [fons, "border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top", "border-top"],
    [fons, "border-bottom", "border-bottom"], [fons, "border", "border"]], tablecolumnPropertySimpleMapping = [[stylens, "column-width", "width"]], tablerowPropertySimpleMapping = [[stylens, "row-height", "height"], [fons, "keep-together", null]], tablePropertySimpleMapping = [[stylens, "width", "width"], [fons, "margin-left", "margin-left"], [fons, "margin-right", "margin-right"], [fons, "margin-top", "margin-top"], [fons, "margin-bottom", "margin-bottom"]], pageContentPropertySimpleMapping = [[fons,
    "background-color", "background-color"], [fons, "padding", "padding"], [fons, "padding-left", "padding-left"], [fons, "padding-right", "padding-right"], [fons, "padding-top", "padding-top"], [fons, "padding-bottom", "padding-bottom"], [fons, "border", "border"], [fons, "border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top", "border-top"], [fons, "border-bottom", "border-bottom"], [fons, "margin", "margin"], [fons, "margin-left", "margin-left"], [fons, "margin-right",
    "margin-right"], [fons, "margin-top", "margin-top"], [fons, "margin-bottom", "margin-bottom"]], pageSizePropertySimpleMapping = [[fons, "page-width", "width"], [fons, "page-height", "height"]], borderPropertyMap = {"border":true, "border-left":true, "border-right":true, "border-top":true, "border-bottom":true, "stroke-width":true}, marginPropertyMap = {"margin":true, "margin-left":true, "margin-right":true, "margin-top":true, "margin-bottom":true}, fontFaceDeclsMap = {}, utils = odf.OdfUtils, documentType,
    odfRoot, defaultFontSize, xpath = xmldom.XPath, cssUnits = new core.CSSUnits;
    function createSelector(family, name) {
      var prefix = familynamespaceprefixes[family], namepart, selector;
      if (prefix === undefined) {
        return null;
      }
      if (name) {
        namepart = "[" + prefix + '|style-name="' + name + '"]';
      } else {
        namepart = "";
      }
      if (prefix === "presentation") {
        prefix = "draw";
        if (name) {
          namepart = '[presentation|style-name="' + name + '"]';
        } else {
          namepart = "";
        }
      }
      selector = prefix + "|" + familytagnames[family].join(namepart + "," + prefix + "|") + namepart;
      return selector;
    }
    function getSelectors(family, name, node) {
      var selectors = [], ss, derivedStyles = node.derivedStyles, n;
      ss = createSelector(family, name);
      if (ss !== null) {
        selectors.push(ss);
      }
      for (n in derivedStyles) {
        if (derivedStyles.hasOwnProperty(n)) {
          ss = getSelectors(family, n, derivedStyles[n]);
          selectors = selectors.concat(ss);
        }
      }
      return selectors;
    }
    function fixBorderWidth(value) {
      var index = value.indexOf(" "), width, theRestOfBorderAttributes;
      if (index !== -1) {
        width = value.substring(0, index);
        theRestOfBorderAttributes = value.substring(index);
      } else {
        width = value;
        theRestOfBorderAttributes = "";
      }
      width = utils.parseLength(width);
      if (width && width.unit === "pt" && width.value < .75) {
        value = "0.75pt" + theRestOfBorderAttributes;
      }
      return value;
    }
    function getParentStyleNode(styleNode) {
      var parentStyleName = "", parentStyleFamily = "", parentStyleNode = null, xp;
      if (styleNode.localName === "default-style") {
        return null;
      }
      parentStyleName = styleNode.getAttributeNS(stylens, "parent-style-name");
      parentStyleFamily = styleNode.getAttributeNS(stylens, "family");
      if (parentStyleName) {
        xp = "//style:*[@style:name='" + parentStyleName + "'][@style:family='" + parentStyleFamily + "']";
      } else {
        xp = "//style:default-style[@style:family='" + parentStyleFamily + "']";
      }
      parentStyleNode = xpath.getODFElementsWithXPath(odfRoot, xp, odf.Namespaces.lookupNamespaceURI)[0];
      return parentStyleNode;
    }
    function fixMargin(props, namespace, name, value) {
      var length = utils.parseLength(value), multiplier, parentStyle, parentLength, result, properties;
      if (!length || length.unit !== "%") {
        return value;
      }
      multiplier = length.value / 100;
      parentStyle = getParentStyleNode(props.parentNode);
      result = "0";
      while (parentStyle) {
        properties = domUtils.getDirectChild(parentStyle, stylens, "paragraph-properties");
        if (properties) {
          parentLength = utils.parseLength(properties.getAttributeNS(namespace, name));
          if (parentLength) {
            if (parentLength.unit !== "%") {
              result = parentLength.value * multiplier + parentLength.unit;
              break;
            }
            multiplier *= parentLength.value / 100;
          }
        }
        parentStyle = getParentStyleNode(parentStyle);
      }
      return result;
    }
    function applySimpleMapping(props, mapping) {
      var rule = "", i, r, value;
      for (i = 0;i < mapping.length;i += 1) {
        r = mapping[i];
        value = props.getAttributeNS(r[0], r[1]);
        if (value) {
          value = value.trim();
          if (borderPropertyMap.hasOwnProperty(r[1])) {
            value = fixBorderWidth(value);
          } else {
            if (marginPropertyMap.hasOwnProperty(r[1])) {
              value = fixMargin(props, r[0], r[1], value);
            }
          }
          if (r[2]) {
            rule += r[2] + ":" + value + ";";
          }
        }
      }
      return rule;
    }
    function getFontSize(styleNode) {
      var props = domUtils.getDirectChild(styleNode, stylens, "text-properties");
      if (props) {
        return utils.parseFoFontSize(props.getAttributeNS(fons, "font-size"));
      }
      return null;
    }
    function parseTextPosition(position) {
      var parts = styleParseUtils.parseAttributeList(position);
      return {verticalTextPosition:parts[0], fontHeight:parts[1]};
    }
    function getTextProperties(props) {
      var rule = "", fontName, fontSize, value, textDecorationLine = "", textDecorationStyle = "", textPosition, fontSizeRule = "", sizeMultiplier = 1, textFamilyStyleNode;
      rule += applySimpleMapping(props, textPropertySimpleMapping);
      value = props.getAttributeNS(stylens, "text-underline-style");
      if (value === "solid") {
        textDecorationLine += " underline";
      }
      value = props.getAttributeNS(stylens, "text-line-through-style");
      if (value === "solid") {
        textDecorationLine += " line-through";
      }
      if (textDecorationLine.length) {
        rule += "text-decoration:" + textDecorationLine + ";\n";
        rule += "text-decoration-line:" + textDecorationLine + ";\n";
        rule += "-moz-text-decoration-line:" + textDecorationLine + ";\n";
      }
      value = props.getAttributeNS(stylens, "text-line-through-type");
      switch(value) {
        case "double":
          textDecorationStyle += " double";
          break;
        case "single":
          textDecorationStyle += " single";
          break;
      }
      if (textDecorationStyle) {
        rule += "text-decoration-style:" + textDecorationStyle + ";\n";
        rule += "-moz-text-decoration-style:" + textDecorationStyle + ";\n";
      }
      fontName = props.getAttributeNS(stylens, "font-name") || props.getAttributeNS(fons, "font-family");
      if (fontName) {
        value = fontFaceDeclsMap[fontName];
        rule += "font-family: " + (value || fontName) + ";";
      }
      value = props.getAttributeNS(stylens, "text-position");
      if (value) {
        textPosition = parseTextPosition(value);
        rule += "vertical-align: " + textPosition.verticalTextPosition + "\n; ";
        if (textPosition.fontHeight) {
          sizeMultiplier = parseFloat(textPosition.fontHeight) / 100;
        }
      }
      if (props.hasAttributeNS(fons, "font-size") || sizeMultiplier !== 1) {
        textFamilyStyleNode = props.parentNode;
        while (textFamilyStyleNode) {
          fontSize = getFontSize(textFamilyStyleNode);
          if (fontSize) {
            if (fontSize.unit !== "%") {
              fontSizeRule = "font-size: " + fontSize.value * sizeMultiplier + fontSize.unit + ";";
              break;
            }
            sizeMultiplier *= fontSize.value / 100;
          }
          textFamilyStyleNode = getParentStyleNode(textFamilyStyleNode);
        }
        if (!fontSizeRule) {
          fontSizeRule = "font-size: " + parseFloat(defaultFontSize) * sizeMultiplier + cssUnits.getUnits(defaultFontSize) + ";";
        }
      }
      rule += fontSizeRule;
      return rule;
    }
    function getParagraphProperties(props) {
      var rule = "", bgimage, url, lineHeight;
      rule += applySimpleMapping(props, paragraphPropertySimpleMapping);
      bgimage = domUtils.getDirectChild(props, stylens, "background-image");
      if (bgimage) {
        url = bgimage.getAttributeNS(xlinkns, "href");
        if (url) {
          rule += "background-image: url('odfkit:" + url + "');";
          rule += applySimpleMapping(bgimage, bgImageSimpleMapping);
        }
      }
      lineHeight = props.getAttributeNS(fons, "line-height");
      if (lineHeight && lineHeight !== "normal") {
        lineHeight = utils.parseFoLineHeight(lineHeight);
        if (lineHeight.unit !== "%") {
          rule += "line-height: " + lineHeight.value + lineHeight.unit + ";";
        } else {
          rule += "line-height: " + lineHeight.value / 100 + ";";
        }
      }
      return rule;
    }
    function matchToRgb(m, r, g, b) {
      return r + r + g + g + b + b;
    }
    function hexToRgb(hex) {
      var result, shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
      hex = hex.replace(shorthandRegex, matchToRgb);
      result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
      return result ? {r:parseInt(result[1], 16), g:parseInt(result[2], 16), b:parseInt(result[3], 16)} : null;
    }
    function isNumber(n) {
      return !isNaN(parseFloat(n));
    }
    function getGraphicProperties(props) {
      var rule = "", alpha, bgcolor, fill;
      rule += applySimpleMapping(props, graphicPropertySimpleMapping);
      alpha = props.getAttributeNS(drawns, "opacity");
      fill = props.getAttributeNS(drawns, "fill");
      bgcolor = props.getAttributeNS(drawns, "fill-color");
      if (fill === "solid" || fill === "hatch") {
        if (bgcolor && bgcolor !== "none") {
          alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1;
          bgcolor = hexToRgb(bgcolor);
          if (bgcolor) {
            rule += "background-color: rgba(" + bgcolor.r + "," + bgcolor.g + "," + bgcolor.b + "," + alpha + ");";
          }
        } else {
          rule += "background: none;";
        }
      } else {
        if (fill === "none") {
          rule += "background: none;";
        }
      }
      return rule;
    }
    function getDrawingPageProperties(props) {
      var rule = "";
      rule += applySimpleMapping(props, graphicPropertySimpleMapping);
      if (props.getAttributeNS(presentationns, "background-visible") === "true") {
        rule += "background: none;";
      }
      return rule;
    }
    function getTableCellProperties(props) {
      var rule = "";
      rule += applySimpleMapping(props, tablecellPropertySimpleMapping);
      return rule;
    }
    function getTableRowProperties(props) {
      var rule = "";
      rule += applySimpleMapping(props, tablerowPropertySimpleMapping);
      return rule;
    }
    function getTableColumnProperties(props) {
      var rule = "";
      rule += applySimpleMapping(props, tablecolumnPropertySimpleMapping);
      return rule;
    }
    function getTableProperties(props) {
      var rule = "", borderModel;
      rule += applySimpleMapping(props, tablePropertySimpleMapping);
      borderModel = props.getAttributeNS(tablens, "border-model");
      if (borderModel === "collapsing") {
        rule += "border-collapse:collapse;";
      } else {
        if (borderModel === "separating") {
          rule += "border-collapse:separate;";
        }
      }
      return rule;
    }
    function getDerivedStyleNames(styleName, node) {
      var styleNames = [styleName], derivedStyles = node.derivedStyles;
      Object.keys(derivedStyles).forEach(function(styleName) {
        var dsn = getDerivedStyleNames(styleName, derivedStyles[styleName]);
        styleNames = styleNames.concat(dsn);
      });
      return styleNames;
    }
    function addDrawPageFrameDisplayRules(sheet, styleName, properties, node) {
      var frameClasses = ["page-number", "date-time", "header", "footer"], styleNames = getDerivedStyleNames(styleName, node), visibleFrameClasses = [], invisibleFrameClasses = [];
      function insertFrameVisibilityRule(controlledFrameClasses, visibility) {
        var selectors = [], rule;
        controlledFrameClasses.forEach(function(frameClass) {
          styleNames.forEach(function(styleName) {
            selectors.push('draw|page[webodfhelper|page-style-name="' + styleName + '"] draw|frame[presentation|class="' + frameClass + '"]');
          });
        });
        if (selectors.length > 0) {
          rule = selectors.join(",") + "{visibility:" + visibility + ";}";
          sheet.insertRule(rule, sheet.cssRules.length);
        }
      }
      frameClasses.forEach(function(frameClass) {
        var displayValue;
        displayValue = properties.getAttributeNS(presentationns, "display-" + frameClass);
        if (displayValue === "true") {
          visibleFrameClasses.push(frameClass);
        } else {
          if (displayValue === "false") {
            invisibleFrameClasses.push(frameClass);
          }
        }
      });
      insertFrameVisibilityRule(visibleFrameClasses, "visible");
      insertFrameVisibilityRule(invisibleFrameClasses, "hidden");
    }
    function addStyleRule(sheet, family, name, node) {
      var selectors = getSelectors(family, name, node), selector = selectors.join(","), rule = "", properties;
      properties = domUtils.getDirectChild(node.element, stylens, "text-properties");
      if (properties) {
        rule += getTextProperties(properties);
      }
      properties = domUtils.getDirectChild(node.element, stylens, "paragraph-properties");
      if (properties) {
        rule += getParagraphProperties(properties);
      }
      properties = domUtils.getDirectChild(node.element, stylens, "graphic-properties");
      if (properties) {
        rule += getGraphicProperties(properties);
      }
      properties = domUtils.getDirectChild(node.element, stylens, "drawing-page-properties");
      if (properties) {
        rule += getDrawingPageProperties(properties);
        addDrawPageFrameDisplayRules(sheet, name, properties, node);
      }
      properties = domUtils.getDirectChild(node.element, stylens, "table-cell-properties");
      if (properties) {
        rule += getTableCellProperties(properties);
      }
      properties = domUtils.getDirectChild(node.element, stylens, "table-row-properties");
      if (properties) {
        rule += getTableRowProperties(properties);
      }
      properties = domUtils.getDirectChild(node.element, stylens, "table-column-properties");
      if (properties) {
        rule += getTableColumnProperties(properties);
      }
      properties = domUtils.getDirectChild(node.element, stylens, "table-properties");
      if (properties) {
        rule += getTableProperties(properties);
      }
      if (rule.length === 0) {
        return;
      }
      rule = selector + "{" + rule + "}";
      sheet.insertRule(rule, sheet.cssRules.length);
    }
    function addPageStyleRules(sheet, node) {
      var rule = "", imageProps, url, contentLayoutRule = "", pageSizeRule = "", props = domUtils.getDirectChild(node, stylens, "page-layout-properties"), stylename, masterStyles, e, masterStyleName;
      if (!props) {
        return;
      }
      stylename = node.getAttributeNS(stylens, "name");
      rule += applySimpleMapping(props, pageContentPropertySimpleMapping);
      imageProps = domUtils.getDirectChild(props, stylens, "background-image");
      if (imageProps) {
        url = imageProps.getAttributeNS(xlinkns, "href");
        if (url) {
          rule += "background-image: url('odfkit:" + url + "');";
          rule += applySimpleMapping(imageProps, bgImageSimpleMapping);
        }
      }
      if (documentType === "presentation") {
        masterStyles = domUtils.getDirectChild(node.parentNode.parentNode, officens, "master-styles");
        e = masterStyles && masterStyles.firstElementChild;
        while (e) {
          if (e.namespaceURI === stylens && e.localName === "master-page" && e.getAttributeNS(stylens, "page-layout-name") === stylename) {
            masterStyleName = e.getAttributeNS(stylens, "name");
            contentLayoutRule = 'draw|page[draw|master-page-name="' + masterStyleName + '"] {' + rule + "}";
            pageSizeRule = 'office|body, draw|page[draw|master-page-name="' + masterStyleName + '"] {' + applySimpleMapping(props, pageSizePropertySimpleMapping) + " }";
            sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
            sheet.insertRule(pageSizeRule, sheet.cssRules.length);
          }
          e = e.nextElementSibling;
        }
      } else {
        if (documentType === "text") {
          contentLayoutRule = "office|text {" + rule + "}";
          rule = "";
          pageSizeRule = "office|body {" + "width: " + props.getAttributeNS(fons, "page-width") + ";" + "}";
          sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
          sheet.insertRule(pageSizeRule, sheet.cssRules.length);
        }
      }
    }
    function addRule(sheet, family, name, node) {
      if (family === "page") {
        addPageStyleRules(sheet, node.element);
      } else {
        addStyleRule(sheet, family, name, node);
      }
    }
    function addRules(sheet, family, name, node) {
      addRule(sheet, family, name, node);
      var n;
      for (n in node.derivedStyles) {
        if (node.derivedStyles.hasOwnProperty(n)) {
          addRules(sheet, family, n, node.derivedStyles[n]);
        }
      }
    }
    this.style2css = function(doctype, rootNode, stylesheet, fontFaceMap, styleTree) {
      var tree, rule, name, family;
      function insertCSSNamespace(prefix, ns) {
        rule = "@namespace " + prefix + " url(" + ns + ");";
        try {
          stylesheet.insertRule(rule, stylesheet.cssRules.length);
        } catch (ignore) {
        }
      }
      odfRoot = rootNode;
      while (stylesheet.cssRules.length) {
        stylesheet.deleteRule(stylesheet.cssRules.length - 1);
      }
      odf.Namespaces.forEachPrefix(insertCSSNamespace);
      insertCSSNamespace("webodfhelper", webodfhelperns);
      fontFaceDeclsMap = fontFaceMap;
      documentType = doctype;
      defaultFontSize = runtime.getWindow().getComputedStyle(document.body, null).getPropertyValue("font-size") || "12pt";
      for (family in familynamespaceprefixes) {
        if (familynamespaceprefixes.hasOwnProperty(family)) {
          tree = styleTree[family];
          for (name in tree) {
            if (tree.hasOwnProperty(name)) {
              addRules(stylesheet, family, name, tree[name]);
            }
          }
        }
      }
    };
  };
  (function() {
    function Point(x, y) {
      var self = this;
      this.getDistance = function(point) {
        var xOffset = self.x - point.x, yOffset = self.y - point.y;
        return Math.sqrt(xOffset * xOffset + yOffset * yOffset);
      };
      this.getCenter = function(point) {
        return new Point((self.x + point.x) / 2, (self.y + point.y) / 2);
      };
      this.x;
      this.y;
      function init() {
        self.x = x;
        self.y = y;
      }
      init();
    }
    gui.ZoomHelper = function() {
      var zoomableElement, panPoint, previousPanPoint, firstPinchDistance, zoom, previousZoom, maxZoom = 4, offsetParent, parentElement, events = new core.EventNotifier([gui.ZoomHelper.signalZoomChanged]), gestures = {NONE:0, SCROLL:1, PINCH:2}, currentGesture = gestures.NONE, requiresCustomScrollBars = runtime.getWindow().hasOwnProperty("ontouchstart"), parentOverflow = "";
      function applyCSSTransform(x, y, scale, is3D) {
        var transformCommand;
        if (is3D) {
          transformCommand = "translate3d(" + x + "px, " + y + "px, 0) scale3d(" + scale + ", " + scale + ", 1)";
        } else {
          transformCommand = "translate(" + x + "px, " + y + "px) scale(" + scale + ")";
        }
        zoomableElement.style.WebkitTransform = transformCommand;
        zoomableElement.style.MozTransform = transformCommand;
        zoomableElement.style.msTransform = transformCommand;
        zoomableElement.style.OTransform = transformCommand;
        zoomableElement.style.transform = transformCommand;
      }
      function applyTransform(is3D) {
        if (is3D) {
          applyCSSTransform(-panPoint.x, -panPoint.y, zoom, true);
        } else {
          applyCSSTransform(0, 0, zoom, true);
          applyCSSTransform(0, 0, zoom, false);
        }
      }
      function applyFastTransform() {
        applyTransform(true);
      }
      function applyDetailedTransform() {
        applyTransform(false);
      }
      function enableScrollBars(enable) {
        if (!offsetParent || !requiresCustomScrollBars) {
          return;
        }
        var initialOverflow = offsetParent.style.overflow, enabled = offsetParent.classList.contains("webodf-customScrollbars");
        if (enable && enabled || !enable && !enabled) {
          return;
        }
        if (enable) {
          offsetParent.classList.add("webodf-customScrollbars");
          offsetParent.style.overflow = "hidden";
          runtime.requestAnimationFrame(function() {
            offsetParent.style.overflow = initialOverflow;
          });
        } else {
          offsetParent.classList.remove("webodf-customScrollbars");
        }
      }
      function removeScroll() {
        applyCSSTransform(-panPoint.x, -panPoint.y, zoom, true);
        offsetParent.scrollLeft = 0;
        offsetParent.scrollTop = 0;
        parentOverflow = parentElement.style.overflow;
        parentElement.style.overflow = "visible";
        enableScrollBars(false);
      }
      function restoreScroll() {
        applyCSSTransform(0, 0, zoom, true);
        offsetParent.scrollLeft = panPoint.x;
        offsetParent.scrollTop = panPoint.y;
        parentElement.style.overflow = parentOverflow || "";
        enableScrollBars(true);
      }
      function getPoint(touch) {
        return new Point(touch.pageX - zoomableElement.offsetLeft, touch.pageY - zoomableElement.offsetTop);
      }
      function sanitizePointForPan(point) {
        return new Point(Math.min(Math.max(point.x, zoomableElement.offsetLeft), (zoomableElement.offsetLeft + zoomableElement.offsetWidth) * zoom - offsetParent.clientWidth), Math.min(Math.max(point.y, zoomableElement.offsetTop), (zoomableElement.offsetTop + zoomableElement.offsetHeight) * zoom - offsetParent.clientHeight));
      }
      function processPan(point) {
        if (previousPanPoint) {
          panPoint.x -= point.x - previousPanPoint.x;
          panPoint.y -= point.y - previousPanPoint.y;
          panPoint = sanitizePointForPan(panPoint);
        }
        previousPanPoint = point;
      }
      function processZoom(zoomPoint, incrementalZoom) {
        var originalZoom = zoom, actuallyIncrementedZoom, minZoom = Math.min(maxZoom, zoomableElement.offsetParent.clientWidth / zoomableElement.offsetWidth);
        zoom = previousZoom * incrementalZoom;
        zoom = Math.min(Math.max(zoom, minZoom), maxZoom);
        actuallyIncrementedZoom = zoom / originalZoom;
        panPoint.x += (actuallyIncrementedZoom - 1) * (zoomPoint.x + panPoint.x);
        panPoint.y += (actuallyIncrementedZoom - 1) * (zoomPoint.y + panPoint.y);
      }
      function processPinch(point1, point2) {
        var zoomPoint = point1.getCenter(point2), pinchDistance = point1.getDistance(point2), incrementalZoom = pinchDistance / firstPinchDistance;
        processPan(zoomPoint);
        processZoom(zoomPoint, incrementalZoom);
      }
      function prepareGesture(event) {
        var fingers = event.touches.length, point1 = fingers > 0 ? getPoint(event.touches[0]) : null, point2 = fingers > 1 ? getPoint(event.touches[1]) : null;
        if (point1 && point2) {
          firstPinchDistance = point1.getDistance(point2);
          previousZoom = zoom;
          previousPanPoint = point1.getCenter(point2);
          removeScroll();
          currentGesture = gestures.PINCH;
        } else {
          if (point1) {
            previousPanPoint = point1;
            currentGesture = gestures.SCROLL;
          }
        }
      }
      function processGesture(event) {
        var fingers = event.touches.length, point1 = fingers > 0 ? getPoint(event.touches[0]) : null, point2 = fingers > 1 ? getPoint(event.touches[1]) : null;
        if (point1 && point2) {
          event.preventDefault();
          if (currentGesture === gestures.SCROLL) {
            currentGesture = gestures.PINCH;
            removeScroll();
            firstPinchDistance = point1.getDistance(point2);
            return;
          }
          processPinch(point1, point2);
          applyFastTransform();
        } else {
          if (point1) {
            if (currentGesture === gestures.PINCH) {
              currentGesture = gestures.SCROLL;
              restoreScroll();
              return;
            }
            processPan(point1);
          }
        }
      }
      function sanitizeGesture() {
        if (currentGesture === gestures.PINCH) {
          events.emit(gui.ZoomHelper.signalZoomChanged, zoom);
          restoreScroll();
          applyDetailedTransform();
        }
        currentGesture = gestures.NONE;
      }
      this.subscribe = function(eventid, cb) {
        events.subscribe(eventid, cb);
      };
      this.unsubscribe = function(eventid, cb) {
        events.unsubscribe(eventid, cb);
      };
      this.getZoomLevel = function() {
        return zoom;
      };
      this.setZoomLevel = function(zoomLevel) {
        if (zoomableElement) {
          zoom = zoomLevel;
          applyDetailedTransform();
          events.emit(gui.ZoomHelper.signalZoomChanged, zoom);
        }
      };
      function registerGestureListeners() {
        if (offsetParent) {
          offsetParent.addEventListener("touchstart", prepareGesture, false);
          offsetParent.addEventListener("touchmove", processGesture, false);
          offsetParent.addEventListener("touchend", sanitizeGesture, false);
        }
      }
      function unregisterGestureListeners() {
        if (offsetParent) {
          offsetParent.removeEventListener("touchstart", prepareGesture, false);
          offsetParent.removeEventListener("touchmove", processGesture, false);
          offsetParent.removeEventListener("touchend", sanitizeGesture, false);
        }
      }
      this.destroy = function(callback) {
        unregisterGestureListeners();
        enableScrollBars(false);
        callback();
      };
      this.setZoomableElement = function(element) {
        unregisterGestureListeners();
        zoomableElement = element;
        offsetParent = zoomableElement.offsetParent;
        parentElement = zoomableElement.parentNode;
        applyDetailedTransform();
        registerGestureListeners();
        enableScrollBars(true);
      };
      function init() {
        zoom = 1;
        previousZoom = 1;
        panPoint = new Point(0, 0);
      }
      init();
    };
    gui.ZoomHelper.signalZoomChanged = "zoomChanged";
  })();
  ops.Canvas = function Canvas() {
  };
  ops.Canvas.prototype.getZoomLevel = function() {
  };
  ops.Canvas.prototype.getElement = function() {
  };
  ops.Canvas.prototype.getSizer = function() {
  };
  ops.Canvas.prototype.getZoomHelper = function() {
  };
  (function() {
    function LoadingQueue() {
      var queue = [], taskRunning = false;
      function run(task) {
        taskRunning = true;
        runtime.setTimeout(function() {
          try {
            task();
          } catch (e) {
            runtime.log(String(e) + "\n" + e.stack);
          }
          taskRunning = false;
          if (queue.length > 0) {
            run(queue.pop());
          }
        }, 10);
      }
      this.clearQueue = function() {
        queue.length = 0;
      };
      this.addToQueue = function(loadingTask) {
        if (queue.length === 0 && !taskRunning) {
          return run(loadingTask);
        }
        queue.push(loadingTask);
      };
    }
    function PageSwitcher(css) {
      var sheet = css.sheet, position = 1;
      function updateCSS() {
        while (sheet.cssRules.length > 0) {
          sheet.deleteRule(0);
        }
        sheet.insertRule("#shadowContent draw|page {display:none;}", 0);
        sheet.insertRule("office|presentation draw|page {display:none;}", 1);
        sheet.insertRule("#shadowContent draw|page:nth-of-type(" + position + ") {display:block;}", 2);
        sheet.insertRule("office|presentation draw|page:nth-of-type(" + position + ") {display:block;}", 3);
      }
      this.showFirstPage = function() {
        position = 1;
        updateCSS();
      };
      this.showNextPage = function() {
        position += 1;
        updateCSS();
      };
      this.showPreviousPage = function() {
        if (position > 1) {
          position -= 1;
          updateCSS();
        }
      };
      this.showPage = function(n) {
        if (n > 0) {
          position = n;
          updateCSS();
        }
      };
      this.css = css;
      this.destroy = function(callback) {
        css.parentNode.removeChild(css);
        callback();
      };
    }
    function listenEvent(eventTarget, eventType, eventHandler) {
      if (eventTarget.addEventListener) {
        eventTarget.addEventListener(eventType, eventHandler, false);
      } else {
        if (eventTarget.attachEvent) {
          eventType = "on" + eventType;
          eventTarget.attachEvent(eventType, eventHandler);
        } else {
          eventTarget["on" + eventType] = eventHandler;
        }
      }
    }
    var drawns = odf.Namespaces.drawns, fons = odf.Namespaces.fons, officens = odf.Namespaces.officens, stylens = odf.Namespaces.stylens, svgns = odf.Namespaces.svgns, tablens = odf.Namespaces.tablens, textns = odf.Namespaces.textns, xlinkns = odf.Namespaces.xlinkns, presentationns = odf.Namespaces.presentationns, webodfhelperns = "urn:webodf:names:helper", xpath = xmldom.XPath, domUtils = core.DomUtils;
    function clearCSSStyleSheet(style) {
      var stylesheet = style.sheet, cssRules = stylesheet.cssRules;
      while (cssRules.length) {
        stylesheet.deleteRule(cssRules.length - 1);
      }
    }
    function handleStyles(odfcontainer, formatting, stylesxmlcss) {
      var style2css = new odf.Style2CSS, list2css = new odf.ListStyleToCss, styleSheet = stylesxmlcss.sheet, styleTree = (new odf.StyleTree(odfcontainer.rootElement.styles, odfcontainer.rootElement.automaticStyles)).getStyleTree();
      style2css.style2css(odfcontainer.getDocumentType(), odfcontainer.rootElement, styleSheet, formatting.getFontMap(), styleTree);
      list2css.applyListStyles(styleSheet, styleTree, odfcontainer.rootElement.body);
    }
    function handleFonts(odfContainer, fontcss) {
      var fontLoader = new odf.FontLoader;
      fontLoader.loadFonts(odfContainer, fontcss.sheet);
    }
    function dropTemplateDrawFrames(clonedNode) {
      var i, element, presentationClass, clonedDrawFrameElements = domUtils.getElementsByTagNameNS(clonedNode, drawns, "frame");
      for (i = 0;i < clonedDrawFrameElements.length;i += 1) {
        element = clonedDrawFrameElements[i];
        presentationClass = element.getAttributeNS(presentationns, "class");
        if (presentationClass && !/^(date-time|footer|header|page-number)$/.test(presentationClass)) {
          element.parentNode.removeChild(element);
        }
      }
    }
    function getHeaderFooter(odfContainer, frame, headerFooterId) {
      var headerFooter = null, i, declElements = odfContainer.rootElement.body.getElementsByTagNameNS(presentationns, headerFooterId + "-decl"), headerFooterName = frame.getAttributeNS(presentationns, "use-" + headerFooterId + "-name"), element;
      if (headerFooterName && declElements.length > 0) {
        for (i = 0;i < declElements.length;i += 1) {
          element = declElements[i];
          if (element.getAttributeNS(presentationns, "name") === headerFooterName) {
            headerFooter = element.textContent;
            break;
          }
        }
      }
      return headerFooter;
    }
    function setContainerValue(rootElement, ns, localName, value) {
      var i, containerList, document = rootElement.ownerDocument, e;
      containerList = domUtils.getElementsByTagNameNS(rootElement, ns, localName);
      for (i = 0;i < containerList.length;i += 1) {
        domUtils.removeAllChildNodes(containerList[i]);
        if (value) {
          e = containerList[i];
          e.appendChild(document.createTextNode(value));
        }
      }
    }
    function setDrawElementPosition(styleid, frame, stylesheet) {
      frame.setAttributeNS(webodfhelperns, "styleid", styleid);
      var rule, anchor = frame.getAttributeNS(textns, "anchor-type"), x = frame.getAttributeNS(svgns, "x"), y = frame.getAttributeNS(svgns, "y"), width = frame.getAttributeNS(svgns, "width"), height = frame.getAttributeNS(svgns, "height"), minheight = frame.getAttributeNS(fons, "min-height"), minwidth = frame.getAttributeNS(fons, "min-width");
      if (anchor === "as-char") {
        rule = "display: inline-block;";
      } else {
        if (anchor || x || y) {
          rule = "position: absolute;";
        } else {
          if (width || height || minheight || minwidth) {
            rule = "display: block;";
          }
        }
      }
      if (x) {
        rule += "left: " + x + ";";
      }
      if (y) {
        rule += "top: " + y + ";";
      }
      if (width) {
        rule += "width: " + width + ";";
      }
      if (height) {
        rule += "height: " + height + ";";
      }
      if (minheight) {
        rule += "min-height: " + minheight + ";";
      }
      if (minwidth) {
        rule += "min-width: " + minwidth + ";";
      }
      if (rule) {
        rule = "draw|" + frame.localName + '[webodfhelper|styleid="' + styleid + '"] {' + rule + "}";
        stylesheet.insertRule(rule, stylesheet.cssRules.length);
      }
    }
    function getUrlFromBinaryDataElement(image) {
      var node = image.firstChild;
      while (node) {
        if (node.namespaceURI === officens && node.localName === "binary-data") {
          return "data:image/png;base64," + node.textContent.replace(/[\r\n\s]/g, "");
        }
        node = node.nextSibling;
      }
      return "";
    }
    function setImage(id, container, image, stylesheet) {
      image.setAttributeNS(webodfhelperns, "styleid", id);
      var url = image.getAttributeNS(xlinkns, "href"), part;
      function callback(url) {
        var rule;
        if (url) {
          rule = "background-image: url(" + url + ");";
          rule = 'draw|image[webodfhelper|styleid="' + id + '"] {' + rule + "}";
          stylesheet.insertRule(rule, stylesheet.cssRules.length);
        }
      }
      function onchange(p) {
        callback(p.url);
      }
      if (url) {
        try {
          part = container.getPart(url);
          part.onchange = onchange;
          part.load();
        } catch (e) {
          runtime.log("slight problem: " + String(e));
        }
      } else {
        url = getUrlFromBinaryDataElement(image);
        callback(url);
      }
    }
    function formatParagraphAnchors(odfbody) {
      var n, i, nodes = xpath.getODFElementsWithXPath(odfbody, ".//*[*[@text:anchor-type='paragraph']]", odf.Namespaces.lookupNamespaceURI);
      for (i = 0;i < nodes.length;i += 1) {
        n = nodes[i];
        if (n.setAttributeNS) {
          n.setAttributeNS(webodfhelperns, "containsparagraphanchor", true);
        }
      }
    }
    function modifyTables(odffragment, documentns) {
      var i, tableCells, node;
      function modifyTableCell(node) {
        if (node.hasAttributeNS(tablens, "number-columns-spanned")) {
          node.setAttributeNS(documentns, "colspan", node.getAttributeNS(tablens, "number-columns-spanned"));
        }
        if (node.hasAttributeNS(tablens, "number-rows-spanned")) {
          node.setAttributeNS(documentns, "rowspan", node.getAttributeNS(tablens, "number-rows-spanned"));
        }
      }
      tableCells = domUtils.getElementsByTagNameNS(odffragment, tablens, "table-cell");
      for (i = 0;i < tableCells.length;i += 1) {
        node = tableCells[i];
        modifyTableCell(node);
      }
    }
    function modifyLineBreakElements(odffragment) {
      var document = odffragment.ownerDocument, lineBreakElements = domUtils.getElementsByTagNameNS(odffragment, textns, "line-break");
      lineBreakElements.forEach(function(lineBreak) {
        if (!lineBreak.hasChildNodes()) {
          lineBreak.appendChild(document.createElement("br"));
        }
      });
    }
    function expandSpaceElements(odffragment) {
      var spaces, doc = odffragment.ownerDocument;
      function expandSpaceElement(space) {
        var j, count;
        domUtils.removeAllChildNodes(space);
        space.appendChild(doc.createTextNode(" "));
        count = parseInt(space.getAttributeNS(textns, "c"), 10);
        if (count > 1) {
          space.removeAttributeNS(textns, "c");
          for (j = 1;j < count;j += 1) {
            space.parentNode.insertBefore(space.cloneNode(true), space);
          }
        }
      }
      spaces = domUtils.getElementsByTagNameNS(odffragment, textns, "s");
      spaces.forEach(expandSpaceElement);
    }
    function expandTabElements(odffragment) {
      var tabs;
      tabs = domUtils.getElementsByTagNameNS(odffragment, textns, "tab");
      tabs.forEach(function(tab) {
        tab.textContent = "\t";
      });
    }
    function modifyDrawElements(odfbody, stylesheet) {
      var node, drawElements = [], i;
      node = odfbody.firstElementChild;
      while (node && node !== odfbody) {
        if (node.namespaceURI === drawns) {
          drawElements[drawElements.length] = node;
        }
        if (node.firstElementChild) {
          node = node.firstElementChild;
        } else {
          while (node && node !== odfbody && !node.nextElementSibling) {
            node = node.parentNode;
          }
          if (node && node.nextElementSibling) {
            node = node.nextElementSibling;
          }
        }
      }
      for (i = 0;i < drawElements.length;i += 1) {
        node = drawElements[i];
        setDrawElementPosition("frame" + String(i), node, stylesheet);
      }
      formatParagraphAnchors(odfbody);
    }
    function cloneMasterPages(formatting, odfContainer, shadowContent, odfbody, stylesheet) {
      var masterPageName, masterPageElement, styleId, clonedPageElement, clonedElement, clonedDrawElements, pageNumber = 0, i, element, elementToClone, document = odfContainer.rootElement.ownerDocument;
      element = odfbody.firstElementChild;
      if (!(element && element.namespaceURI === officens && (element.localName === "presentation" || element.localName === "drawing"))) {
        return;
      }
      element = element.firstElementChild;
      while (element) {
        masterPageName = element.getAttributeNS(drawns, "master-page-name");
        masterPageElement = masterPageName ? formatting.getMasterPageElement(masterPageName) : null;
        if (masterPageElement) {
          styleId = element.getAttributeNS(webodfhelperns, "styleid");
          clonedPageElement = document.createElementNS(drawns, "draw:page");
          elementToClone = masterPageElement.firstElementChild;
          i = 0;
          while (elementToClone) {
            if (elementToClone.getAttributeNS(presentationns, "placeholder") !== "true") {
              clonedElement = elementToClone.cloneNode(true);
              clonedPageElement.appendChild(clonedElement);
            }
            elementToClone = elementToClone.nextElementSibling;
            i += 1;
          }
          dropTemplateDrawFrames(clonedPageElement);
          clonedDrawElements = domUtils.getElementsByTagNameNS(clonedPageElement, drawns, "*");
          for (i = 0;i < clonedDrawElements.length;i += 1) {
            setDrawElementPosition(styleId + "_" + i, clonedDrawElements[i], stylesheet);
          }
          shadowContent.appendChild(clonedPageElement);
          pageNumber = String(shadowContent.getElementsByTagNameNS(drawns, "page").length);
          setContainerValue(clonedPageElement, textns, "page-number", pageNumber);
          setContainerValue(clonedPageElement, presentationns, "header", getHeaderFooter(odfContainer, element, "header"));
          setContainerValue(clonedPageElement, presentationns, "footer", getHeaderFooter(odfContainer, element, "footer"));
          setDrawElementPosition(styleId, clonedPageElement, stylesheet);
          clonedPageElement.setAttributeNS(webodfhelperns, "page-style-name", element.getAttributeNS(drawns, "style-name"));
          clonedPageElement.setAttributeNS(drawns, "draw:master-page-name", masterPageElement.getAttributeNS(stylens, "name"));
        }
        element = element.nextElementSibling;
      }
    }
    function setVideo(container, plugin) {
      var video, source, url, doc = plugin.ownerDocument, part;
      url = plugin.getAttributeNS(xlinkns, "href");
      function callback(url, mimetype) {
        var ns = doc.documentElement.namespaceURI;
        if (mimetype.substr(0, 6) === "video/") {
          video = doc.createElementNS(ns, "video");
          video.setAttribute("controls", "controls");
          source = doc.createElementNS(ns, "source");
          if (url) {
            source.setAttribute("src", url);
          }
          source.setAttribute("type", mimetype);
          video.appendChild(source);
          plugin.parentNode.appendChild(video);
        } else {
          plugin.innerHtml = "Unrecognised Plugin";
        }
      }
      function onchange(p) {
        callback(p.url, p.mimetype);
      }
      if (url) {
        try {
          part = container.getPart(url);
          part.onchange = onchange;
          part.load();
        } catch (e) {
          runtime.log("slight problem: " + String(e));
        }
      } else {
        runtime.log("using MP4 data fallback");
        url = getUrlFromBinaryDataElement(plugin);
        callback(url, "video/mp4");
      }
    }
    function findWebODFStyleSheet(head) {
      var style = head.firstElementChild;
      while (style && !(style.localName === "style" && style.hasAttribute("webodfcss"))) {
        style = style.nextElementSibling;
      }
      return style;
    }
    function addWebODFStyleSheet(document) {
      var head = document.getElementsByTagName("head")[0], css, style, href, count = document.styleSheets.length;
      style = findWebODFStyleSheet(head);
      if (style) {
        count = parseInt(style.getAttribute("webodfcss"), 10);
        style.setAttribute("webodfcss", count + 1);
        return style;
      }
      if (String(typeof webodf_css) === "string") {
        css = webodf_css;
      } else {
        href = "webodf.css";
        if (runtime.currentDirectory) {
          href = runtime.currentDirectory();
          if (href.length > 0 && href.substr(-1) !== "/") {
            href += "/";
          }
          href += "../webodf.css";
        }
        css = runtime.readFileSync(href, "utf-8");
      }
      style = document.createElementNS(head.namespaceURI, "style");
      style.setAttribute("media", "screen, print, handheld, projection");
      style.setAttribute("type", "text/css");
      style.setAttribute("webodfcss", "1");
      style.appendChild(document.createTextNode(css));
      head.appendChild(style);
      return style;
    }
    function removeWebODFStyleSheet(webodfcss) {
      var count = parseInt(webodfcss.getAttribute("webodfcss"), 10);
      if (count === 1) {
        webodfcss.parentNode.removeChild(webodfcss);
      } else {
        webodfcss.setAttribute("count", count - 1);
      }
    }
    function addStyleSheet(document) {
      var head = document.getElementsByTagName("head")[0], style = document.createElementNS(head.namespaceURI, "style"), text = "";
      style.setAttribute("type", "text/css");
      style.setAttribute("media", "screen, print, handheld, projection");
      odf.Namespaces.forEachPrefix(function(prefix, ns) {
        text += "@namespace " + prefix + " url(" + ns + ");\n";
      });
      text += "@namespace webodfhelper url(" + webodfhelperns + ");\n";
      style.appendChild(document.createTextNode(text));
      head.appendChild(style);
      return style;
    }
    odf.OdfCanvas = function OdfCanvas(element, viewport) {
      runtime.assert(element !== null && element !== undefined, "odf.OdfCanvas constructor needs DOM element");
      runtime.assert(element.ownerDocument !== null && element.ownerDocument !== undefined, "odf.OdfCanvas constructor needs DOM");
      var self = this, doc = element.ownerDocument, odfcontainer, formatting = new odf.Formatting, pageSwitcher, sizer = null, annotationsPane = null, allowAnnotations = false, showAnnotationRemoveButton = false, annotationViewManager = null, webodfcss, fontcss, stylesxmlcss, positioncss, shadowContent, eventHandlers = {}, waitingForDoneTimeoutId, redrawContainerTask, shouldRefreshCss = false, shouldRerenderAnnotations = false, loadingQueue = new LoadingQueue, zoomHelper = new gui.ZoomHelper, canvasViewport =
      viewport || new gui.SingleScrollViewport(element.parentNode);
      function loadImages(container, odffragment, stylesheet) {
        var i, images, node;
        function loadImage(name, container, node, stylesheet) {
          loadingQueue.addToQueue(function() {
            setImage(name, container, node, stylesheet);
          });
        }
        images = odffragment.getElementsByTagNameNS(drawns, "image");
        for (i = 0;i < images.length;i += 1) {
          node = images.item(i);
          loadImage("image" + String(i), container, node, stylesheet);
        }
      }
      function loadVideos(container, odffragment) {
        var i, plugins, node;
        function loadVideo(container, node) {
          loadingQueue.addToQueue(function() {
            setVideo(container, node);
          });
        }
        plugins = odffragment.getElementsByTagNameNS(drawns, "plugin");
        for (i = 0;i < plugins.length;i += 1) {
          node = plugins.item(i);
          loadVideo(container, node);
        }
      }
      function addEventListener(eventType, eventHandler) {
        var handlers;
        if (eventHandlers.hasOwnProperty(eventType)) {
          handlers = eventHandlers[eventType];
        } else {
          handlers = eventHandlers[eventType] = [];
        }
        if (eventHandler && handlers.indexOf(eventHandler) === -1) {
          handlers.push(eventHandler);
        }
      }
      function fireEvent(eventType, args) {
        if (!eventHandlers.hasOwnProperty(eventType)) {
          return;
        }
        var handlers = eventHandlers[eventType], i;
        for (i = 0;i < handlers.length;i += 1) {
          handlers[i].apply(null, args);
        }
      }
      function fixContainerSize() {
        var minHeight, odfdoc = sizer.firstChild, zoomLevel = zoomHelper.getZoomLevel();
        if (!odfdoc) {
          return;
        }
        sizer.style.WebkitTransformOrigin = "0% 0%";
        sizer.style.MozTransformOrigin = "0% 0%";
        sizer.style.msTransformOrigin = "0% 0%";
        sizer.style.OTransformOrigin = "0% 0%";
        sizer.style.transformOrigin = "0% 0%";
        if (annotationViewManager) {
          minHeight = annotationViewManager.getMinimumHeightForAnnotationPane();
          if (minHeight) {
            sizer.style.minHeight = minHeight;
          } else {
            sizer.style.removeProperty("min-height");
          }
        }
        element.style.width = Math.round(zoomLevel * sizer.offsetWidth) + "px";
        element.style.height = Math.round(zoomLevel * sizer.offsetHeight) + "px";
        element.style.display = "inline-block";
      }
      function redrawContainer() {
        if (shouldRefreshCss) {
          handleStyles(odfcontainer, formatting, stylesxmlcss);
          shouldRefreshCss = false;
        }
        if (shouldRerenderAnnotations) {
          if (annotationViewManager) {
            annotationViewManager.rerenderAnnotations();
          }
          shouldRerenderAnnotations = false;
        }
        fixContainerSize();
      }
      function handleContent(container, odfnode) {
        var css = positioncss.sheet;
        domUtils.removeAllChildNodes(element);
        sizer = doc.createElementNS(element.namespaceURI, "div");
        sizer.style.display = "inline-block";
        sizer.style.background = "white";
        sizer.style.setProperty("float", "left", "important");
        sizer.appendChild(odfnode);
        element.appendChild(sizer);
        annotationsPane = doc.createElementNS(element.namespaceURI, "div");
        annotationsPane.id = "annotationsPane";
        shadowContent = doc.createElementNS(element.namespaceURI, "div");
        shadowContent.id = "shadowContent";
        shadowContent.style.position = "absolute";
        shadowContent.style.top = 0;
        shadowContent.style.left = 0;
        container.getContentElement().appendChild(shadowContent);
        modifyDrawElements(odfnode.body, css);
        cloneMasterPages(formatting, container, shadowContent, odfnode.body, css);
        modifyTables(odfnode.body, element.namespaceURI);
        modifyLineBreakElements(odfnode.body);
        expandSpaceElements(odfnode.body);
        expandTabElements(odfnode.body);
        loadImages(container, odfnode.body, css);
        loadVideos(container, odfnode.body);
        sizer.insertBefore(shadowContent, sizer.firstChild);
        zoomHelper.setZoomableElement(sizer);
      }
      function handleAnnotations(odfnode) {
        var annotationNodes;
        if (allowAnnotations) {
          if (!annotationsPane.parentNode) {
            sizer.appendChild(annotationsPane);
          }
          if (annotationViewManager) {
            annotationViewManager.forgetAnnotations();
          }
          annotationViewManager = new gui.AnnotationViewManager(self, odfnode.body, annotationsPane, showAnnotationRemoveButton);
          annotationNodes = domUtils.getElementsByTagNameNS(odfnode.body, officens, "annotation");
          annotationViewManager.addAnnotations(annotationNodes);
          fixContainerSize();
        } else {
          if (annotationsPane.parentNode) {
            sizer.removeChild(annotationsPane);
            annotationViewManager.forgetAnnotations();
            fixContainerSize();
          }
        }
      }
      function refreshOdf(suppressEvent) {
        function callback() {
          clearCSSStyleSheet(fontcss);
          clearCSSStyleSheet(stylesxmlcss);
          clearCSSStyleSheet(positioncss);
          domUtils.removeAllChildNodes(element);
          element.style.display = "inline-block";
          var odfnode = odfcontainer.rootElement;
          element.ownerDocument.importNode(odfnode, true);
          formatting.setOdfContainer(odfcontainer);
          handleFonts(odfcontainer, fontcss);
          handleStyles(odfcontainer, formatting, stylesxmlcss);
          handleContent(odfcontainer, odfnode);
          handleAnnotations(odfnode);
          if (!suppressEvent) {
            loadingQueue.addToQueue(function() {
              fireEvent("statereadychange", [odfcontainer]);
            });
          }
        }
        if (odfcontainer.state === odf.OdfContainer.DONE) {
          callback();
        } else {
          runtime.log("WARNING: refreshOdf called but ODF was not DONE.");
          waitingForDoneTimeoutId = runtime.setTimeout(function later_cb() {
            if (odfcontainer.state === odf.OdfContainer.DONE) {
              callback();
            } else {
              runtime.log("will be back later...");
              waitingForDoneTimeoutId = runtime.setTimeout(later_cb, 500);
            }
          }, 100);
        }
      }
      this.refreshCSS = function() {
        shouldRefreshCss = true;
        redrawContainerTask.trigger();
      };
      this.refreshSize = function() {
        redrawContainerTask.trigger();
      };
      this.odfContainer = function() {
        return odfcontainer;
      };
      this.setOdfContainer = function(container, suppressEvent) {
        odfcontainer = container;
        refreshOdf(suppressEvent === true);
      };
      function load(url) {
        loadingQueue.clearQueue();
        domUtils.removeAllChildNodes(element);
        element.appendChild(element.ownerDocument.createTextNode(runtime.tr("Loading") + url + "..."));
        element.removeAttribute("style");
        odfcontainer = new odf.OdfContainer(url, function(container) {
          odfcontainer = container;
          refreshOdf(false);
        });
      }
      this["load"] = load;
      this.load = load;
      this.save = function(callback) {
        odfcontainer.save(callback);
      };
      this.addListener = function(eventName, handler) {
        switch(eventName) {
          case "click":
            listenEvent(element, eventName, handler);
            break;
          default:
            addEventListener(eventName, handler);
            break;
        }
      };
      this.getFormatting = function() {
        return formatting;
      };
      this.getAnnotationViewManager = function() {
        return annotationViewManager;
      };
      this.refreshAnnotations = function() {
        handleAnnotations(odfcontainer.rootElement);
      };
      this.rerenderAnnotations = function() {
        if (annotationViewManager) {
          shouldRerenderAnnotations = true;
          redrawContainerTask.trigger();
        }
      };
      this.getSizer = function() {
        return sizer;
      };
      this.enableAnnotations = function(allow, showRemoveButton) {
        if (allow !== allowAnnotations) {
          allowAnnotations = allow;
          showAnnotationRemoveButton = showRemoveButton;
          if (odfcontainer) {
            handleAnnotations(odfcontainer.rootElement);
          }
        }
      };
      this.addAnnotation = function(annotation) {
        if (annotationViewManager) {
          annotationViewManager.addAnnotations([annotation]);
          fixContainerSize();
        }
      };
      this.forgetAnnotation = function(annotation) {
        if (annotationViewManager) {
          annotationViewManager.forgetAnnotation(annotation);
          fixContainerSize();
        }
      };
      this.getZoomHelper = function() {
        return zoomHelper;
      };
      this.setZoomLevel = function(zoom) {
        zoomHelper.setZoomLevel(zoom);
      };
      this.getZoomLevel = function() {
        return zoomHelper.getZoomLevel();
      };
      this.fitToContainingElement = function(width, height) {
        var zoomLevel = zoomHelper.getZoomLevel(), realWidth = element.offsetWidth / zoomLevel, realHeight = element.offsetHeight / zoomLevel, zoom;
        zoom = width / realWidth;
        if (height / realHeight < zoom) {
          zoom = height / realHeight;
        }
        zoomHelper.setZoomLevel(zoom);
      };
      this.fitToWidth = function(width) {
        var realWidth = element.offsetWidth / zoomHelper.getZoomLevel();
        zoomHelper.setZoomLevel(width / realWidth);
      };
      this.fitSmart = function(width, height) {
        var realWidth, realHeight, newScale, zoomLevel = zoomHelper.getZoomLevel();
        realWidth = element.offsetWidth / zoomLevel;
        realHeight = element.offsetHeight / zoomLevel;
        newScale = width / realWidth;
        if (height !== undefined) {
          if (height / realHeight < newScale) {
            newScale = height / realHeight;
          }
        }
        zoomHelper.setZoomLevel(Math.min(1, newScale));
      };
      this.fitToHeight = function(height) {
        var realHeight = element.offsetHeight / zoomHelper.getZoomLevel();
        zoomHelper.setZoomLevel(height / realHeight);
      };
      this.showFirstPage = function() {
        pageSwitcher.showFirstPage();
      };
      this.showNextPage = function() {
        pageSwitcher.showNextPage();
      };
      this.showPreviousPage = function() {
        pageSwitcher.showPreviousPage();
      };
      this.showPage = function(n) {
        pageSwitcher.showPage(n);
        fixContainerSize();
      };
      this.getElement = function() {
        return element;
      };
      this.getViewport = function() {
        return canvasViewport;
      };
      this.addCssForFrameWithImage = function(frame) {
        var frameName = frame.getAttributeNS(drawns, "name"), fc = frame.firstElementChild;
        setDrawElementPosition(frameName, frame, positioncss.sheet);
        if (fc) {
          setImage(frameName + "img", odfcontainer, fc, positioncss.sheet);
        }
      };
      this.destroy = function(callback) {
        var head = doc.getElementsByTagName("head")[0], cleanup = [pageSwitcher.destroy, redrawContainerTask.destroy];
        runtime.clearTimeout(waitingForDoneTimeoutId);
        if (annotationsPane && annotationsPane.parentNode) {
          annotationsPane.parentNode.removeChild(annotationsPane);
        }
        zoomHelper.destroy(function() {
          if (sizer) {
            element.removeChild(sizer);
            sizer = null;
          }
        });
        removeWebODFStyleSheet(webodfcss);
        head.removeChild(fontcss);
        head.removeChild(stylesxmlcss);
        head.removeChild(positioncss);
        core.Async.destroyAll(cleanup, callback);
      };
      function init() {
        webodfcss = addWebODFStyleSheet(doc);
        pageSwitcher = new PageSwitcher(addStyleSheet(doc));
        fontcss = addStyleSheet(doc);
        stylesxmlcss = addStyleSheet(doc);
        positioncss = addStyleSheet(doc);
        redrawContainerTask = core.Task.createRedrawTask(redrawContainer);
        zoomHelper.subscribe(gui.ZoomHelper.signalZoomChanged, fixContainerSize);
      }
      init();
    };
  })();
  odf.StepUtils = function StepUtils() {
    function getContentBounds(stepIterator) {
      var container = stepIterator.container(), offset, contentBounds;
      runtime.assert(stepIterator.isStep(), "Step iterator must be on a step");
      if (container.nodeType === Node.TEXT_NODE && stepIterator.offset() > 0) {
        offset = stepIterator.offset();
      } else {
        container = stepIterator.leftNode();
        if (container && container.nodeType === Node.TEXT_NODE) {
          offset = container.length;
        }
      }
      if (container) {
        if (container.nodeType === Node.TEXT_NODE) {
          runtime.assert(offset > 0, "Empty text node found");
          contentBounds = {container:container, startOffset:offset - 1, endOffset:offset};
        } else {
          contentBounds = {container:container, startOffset:0, endOffset:container.childNodes.length};
        }
      }
      return contentBounds;
    }
    this.getContentBounds = getContentBounds;
  };
  ops.MemberProperties = function() {
    this.fullName;
    this.color;
    this.imageUrl;
  };
  ops.Member = function Member(memberId, properties) {
    var props = new ops.MemberProperties;
    function getMemberId() {
      return memberId;
    }
    function getProperties() {
      return props;
    }
    function setProperties(newProperties) {
      Object.keys(newProperties).forEach(function(key) {
        props[key] = newProperties[key];
      });
    }
    function removeProperties(removedProperties) {
      Object.keys(removedProperties).forEach(function(key) {
        if (key !== "fullName" && key !== "color" && key !== "imageUrl" && props.hasOwnProperty(key)) {
          delete props[key];
        }
      });
    }
    this.getMemberId = getMemberId;
    this.getProperties = getProperties;
    this.setProperties = setProperties;
    this.removeProperties = removeProperties;
    function init() {
      runtime.assert(Boolean(memberId), "No memberId was supplied!");
      if (!properties.fullName) {
        properties.fullName = runtime.tr("Unknown Author");
      }
      if (!properties.color) {
        properties.color = "black";
      }
      if (!properties.imageUrl) {
        properties.imageUrl = "avatar-joe.png";
      }
      props = properties;
    }
    init();
  };
  ops.Document = function Document() {
  };
  ops.Document.prototype.getMemberIds = function() {
  };
  ops.Document.prototype.removeCursor = function(memberid) {
  };
  ops.Document.prototype.getDocumentElement = function() {
  };
  ops.Document.prototype.getRootNode = function() {
  };
  ops.Document.prototype.getDOMDocument = function() {
  };
  ops.Document.prototype.cloneDocumentElement = function() {
  };
  ops.Document.prototype.setDocumentElement = function(element) {
  };
  ops.Document.prototype.subscribe = function(eventid, cb) {
  };
  ops.Document.prototype.unsubscribe = function(eventid, cb) {
  };
  ops.Document.prototype.getCanvas = function() {
  };
  ops.Document.prototype.createRootFilter = function(inputMemberId) {
  };
  ops.Document.prototype.createPositionIterator = function(rootNode) {
  };
  ops.Document.prototype.hasCursor = function(memberid) {
  };
  ops.Document.signalCursorAdded = "cursor/added";
  ops.Document.signalCursorRemoved = "cursor/removed";
  ops.Document.signalCursorMoved = "cursor/moved";
  ops.Document.signalMemberAdded = "member/added";
  ops.Document.signalMemberUpdated = "member/updated";
  ops.Document.signalMemberRemoved = "member/removed";
  ops.OdtCursor = function OdtCursor(memberId, document) {
    var self = this, validSelectionTypes = {}, selectionType, cursor, events = new core.EventNotifier([ops.OdtCursor.signalCursorUpdated]);
    this.removeFromDocument = function() {
      cursor.remove();
    };
    this.subscribe = function(eventid, cb) {
      events.subscribe(eventid, cb);
    };
    this.unsubscribe = function(eventid, cb) {
      events.unsubscribe(eventid, cb);
    };
    this.getMemberId = function() {
      return memberId;
    };
    this.getNode = function() {
      return cursor.getNode();
    };
    this.getAnchorNode = function() {
      return cursor.getAnchorNode();
    };
    this.getSelectedRange = function() {
      return cursor.getSelectedRange();
    };
    this.setSelectedRange = function(range, isForwardSelection) {
      cursor.setSelectedRange(range, isForwardSelection);
      events.emit(ops.OdtCursor.signalCursorUpdated, self);
    };
    this.hasForwardSelection = function() {
      return cursor.hasForwardSelection();
    };
    this.getDocument = function() {
      return document;
    };
    this.getSelectionType = function() {
      return selectionType;
    };
    this.setSelectionType = function(value) {
      if (validSelectionTypes.hasOwnProperty(value)) {
        selectionType = value;
      } else {
        runtime.log("Invalid selection type: " + value);
      }
    };
    this.resetSelectionType = function() {
      self.setSelectionType(ops.OdtCursor.RangeSelection);
    };
    function init() {
      cursor = new core.Cursor(document.getDOMDocument(), memberId);
      validSelectionTypes[ops.OdtCursor.RangeSelection] = true;
      validSelectionTypes[ops.OdtCursor.RegionSelection] = true;
      self.resetSelectionType();
    }
    init();
  };
  ops.OdtCursor.RangeSelection = "Range";
  ops.OdtCursor.RegionSelection = "Region";
  ops.OdtCursor.signalCursorUpdated = "cursorUpdated";
  (function() {
    var nextNodeId = 0;
    ops.StepsCache = function StepsCache(rootElement, bucketSize, restoreBookmarkPosition) {
      var coordinatens = "urn:webodf:names:steps", stepToDomPoint = {}, nodeToBookmark = {}, domUtils = core.DomUtils, basePoint, lastUndamagedCacheStep, DOCUMENT_POSITION_FOLLOWING = Node.DOCUMENT_POSITION_FOLLOWING, DOCUMENT_POSITION_PRECEDING = Node.DOCUMENT_POSITION_PRECEDING;
      function NodeBookmark(nodeId, bookmarkNode) {
        var self = this;
        this.nodeId = nodeId;
        this.steps = -1;
        this.node = bookmarkNode;
        this.nextBookmark = null;
        this.previousBookmark = null;
        this.setIteratorPosition = function(iterator) {
          iterator.setPositionBeforeElement(bookmarkNode);
          restoreBookmarkPosition(self.steps, iterator);
        };
      }
      function RootBookmark(nodeId, steps, rootNode) {
        var self = this;
        this.nodeId = nodeId;
        this.steps = steps;
        this.node = rootNode;
        this.nextBookmark = null;
        this.previousBookmark = null;
        this.setIteratorPosition = function(iterator) {
          iterator.setUnfilteredPosition(rootNode, 0);
          restoreBookmarkPosition(self.steps, iterator);
        };
      }
      function inspectBookmarks(bookmark1, bookmark2) {
        var parts = "[" + bookmark1.nodeId;
        if (bookmark2) {
          parts += " => " + bookmark2.nodeId;
        }
        return parts + "]";
      }
      function isUndamagedBookmark(bookmark) {
        return lastUndamagedCacheStep === undefined || bookmark === basePoint || bookmark.steps <= lastUndamagedCacheStep;
      }
      function verifyCache() {
        if (ops.StepsCache.ENABLE_CACHE_VERIFICATION !== true) {
          return;
        }
        var bookmark = basePoint, previousBookmark, nextBookmark, documentPosition, loopCheck = new core.LoopWatchDog(0, 1E5), stepToDomPointNodeIds = {};
        while (bookmark) {
          loopCheck.check();
          previousBookmark = bookmark.previousBookmark;
          if (previousBookmark) {
            runtime.assert(previousBookmark.nextBookmark === bookmark, "Broken bookmark link to previous @" + inspectBookmarks(previousBookmark, bookmark));
          } else {
            runtime.assert(bookmark === basePoint, "Broken bookmark link @" + inspectBookmarks(bookmark));
            runtime.assert(isUndamagedBookmark(basePoint), "Base point is damaged @" + inspectBookmarks(bookmark));
          }
          nextBookmark = bookmark.nextBookmark;
          if (nextBookmark) {
            runtime.assert(nextBookmark.previousBookmark === bookmark, "Broken bookmark link to next @" + inspectBookmarks(bookmark, nextBookmark));
          }
          if (isUndamagedBookmark(bookmark)) {
            runtime.assert(domUtils.containsNode(rootElement, bookmark.node), "Disconnected node is being reported as undamaged @" + inspectBookmarks(bookmark));
            if (previousBookmark) {
              documentPosition = bookmark.node.compareDocumentPosition(previousBookmark.node);
              runtime.assert(documentPosition === 0 || (documentPosition & DOCUMENT_POSITION_PRECEDING) !== 0, "Bookmark order with previous does not reflect DOM order @" + inspectBookmarks(previousBookmark, bookmark));
            }
            if (nextBookmark) {
              if (domUtils.containsNode(rootElement, nextBookmark.node)) {
                documentPosition = bookmark.node.compareDocumentPosition(nextBookmark.node);
                runtime.assert(documentPosition === 0 || (documentPosition & DOCUMENT_POSITION_FOLLOWING) !== 0, "Bookmark order with next does not reflect DOM order @" + inspectBookmarks(bookmark, nextBookmark));
              }
            }
          }
          bookmark = bookmark.nextBookmark;
        }
        Object.keys(stepToDomPoint).forEach(function(step) {
          var domPointBookmark = stepToDomPoint[step];
          if (lastUndamagedCacheStep === undefined || step <= lastUndamagedCacheStep) {
            runtime.assert(domPointBookmark.steps <= step, "Bookmark step of " + domPointBookmark.steps + " exceeds cached step lookup for " + step + " @" + inspectBookmarks(domPointBookmark));
          }
          runtime.assert(stepToDomPointNodeIds.hasOwnProperty(domPointBookmark.nodeId) === false, "Bookmark " + inspectBookmarks(domPointBookmark) + " appears twice in cached step lookup at steps " + stepToDomPointNodeIds[domPointBookmark.nodeId] + " and " + step);
          stepToDomPointNodeIds[domPointBookmark.nodeId] = step;
        });
      }
      function getBucket(steps) {
        return Math.floor(steps / bucketSize) * bucketSize;
      }
      function getDestinationBucket(steps) {
        return Math.ceil(steps / bucketSize) * bucketSize;
      }
      function clearNodeId(node) {
        node.removeAttributeNS(coordinatens, "nodeId");
      }
      function getNodeId(node) {
        var id = "";
        if (node.nodeType === Node.ELEMENT_NODE) {
          id = node.getAttributeNS(coordinatens, "nodeId") || "";
        }
        return id;
      }
      function setNodeId(node) {
        var nodeId = nextNodeId.toString();
        node.setAttributeNS(coordinatens, "nodeId", nodeId);
        nextNodeId += 1;
        return nodeId;
      }
      function isValidBookmarkForNode(node, bookmark) {
        return bookmark.node === node;
      }
      function getNodeBookmark(node) {
        var nodeId = getNodeId(node) || setNodeId(node), existingBookmark;
        existingBookmark = nodeToBookmark[nodeId];
        if (!existingBookmark) {
          existingBookmark = nodeToBookmark[nodeId] = new NodeBookmark(nodeId, node);
        } else {
          if (!isValidBookmarkForNode(node, existingBookmark)) {
            runtime.log("Cloned node detected. Creating new bookmark");
            nodeId = setNodeId(node);
            existingBookmark = nodeToBookmark[nodeId] = new NodeBookmark(nodeId, node);
          }
        }
        return existingBookmark;
      }
      function getClosestBookmark(steps) {
        var cacheBucket, cachePoint, loopGuard = new core.LoopWatchDog(0, 1E4);
        if (lastUndamagedCacheStep !== undefined && steps > lastUndamagedCacheStep) {
          steps = lastUndamagedCacheStep;
        }
        cacheBucket = getBucket(steps);
        while (!cachePoint && cacheBucket >= 0) {
          cachePoint = stepToDomPoint[cacheBucket];
          cacheBucket -= bucketSize;
        }
        cachePoint = cachePoint || basePoint;
        while (cachePoint.nextBookmark && cachePoint.nextBookmark.steps <= steps) {
          loopGuard.check();
          cachePoint = cachePoint.nextBookmark;
        }
        runtime.assert(steps === -1 || cachePoint.steps <= steps, "Bookmark @" + inspectBookmarks(cachePoint) + " at step " + cachePoint.steps + " exceeds requested step of " + steps);
        return cachePoint;
      }
      function getUndamagedBookmark(bookmark) {
        if (lastUndamagedCacheStep !== undefined && bookmark.steps > lastUndamagedCacheStep) {
          bookmark = getClosestBookmark(lastUndamagedCacheStep);
        }
        return bookmark;
      }
      function removeBookmark(currentBookmark) {
        if (currentBookmark.previousBookmark) {
          currentBookmark.previousBookmark.nextBookmark = currentBookmark.nextBookmark;
        }
        if (currentBookmark.nextBookmark) {
          currentBookmark.nextBookmark.previousBookmark = currentBookmark.previousBookmark;
        }
      }
      function isAlreadyInOrder(previousBookmark, newBookmark) {
        return previousBookmark === newBookmark || previousBookmark.nextBookmark === newBookmark;
      }
      function insertBookmark(previousBookmark, newBookmark) {
        var nextBookmark;
        if (!isAlreadyInOrder(previousBookmark, newBookmark)) {
          if (previousBookmark.steps === newBookmark.steps) {
            while ((newBookmark.node.compareDocumentPosition(previousBookmark.node) & DOCUMENT_POSITION_FOLLOWING) !== 0 && previousBookmark !== basePoint) {
              previousBookmark = previousBookmark.previousBookmark;
            }
          }
          if (!isAlreadyInOrder(previousBookmark, newBookmark)) {
            removeBookmark(newBookmark);
            nextBookmark = previousBookmark.nextBookmark;
            newBookmark.nextBookmark = previousBookmark.nextBookmark;
            newBookmark.previousBookmark = previousBookmark;
            previousBookmark.nextBookmark = newBookmark;
            if (nextBookmark) {
              nextBookmark.previousBookmark = newBookmark;
            }
          }
        }
      }
      function repairCacheUpToStep(currentIteratorStep) {
        var damagedBookmark, undamagedBookmark, nextBookmark, stepsBucket;
        if (lastUndamagedCacheStep !== undefined && lastUndamagedCacheStep < currentIteratorStep) {
          undamagedBookmark = getClosestBookmark(lastUndamagedCacheStep);
          damagedBookmark = undamagedBookmark.nextBookmark;
          while (damagedBookmark && damagedBookmark.steps <= currentIteratorStep) {
            nextBookmark = damagedBookmark.nextBookmark;
            stepsBucket = getDestinationBucket(damagedBookmark.steps);
            if (stepToDomPoint[stepsBucket] === damagedBookmark) {
              delete stepToDomPoint[stepsBucket];
            }
            if (!domUtils.containsNode(rootElement, damagedBookmark.node)) {
              removeBookmark(damagedBookmark);
              delete nodeToBookmark[damagedBookmark.nodeId];
            } else {
              damagedBookmark.steps = currentIteratorStep + 1;
            }
            damagedBookmark = nextBookmark;
          }
          lastUndamagedCacheStep = currentIteratorStep;
        } else {
          undamagedBookmark = getClosestBookmark(currentIteratorStep);
        }
        return undamagedBookmark;
      }
      this.updateBookmark = function(steps, node) {
        var previousCacheBucket, newCacheBucket = getDestinationBucket(steps), existingCachePoint, bookmark, closestPriorBookmark;
        closestPriorBookmark = repairCacheUpToStep(steps);
        bookmark = getNodeBookmark(node);
        if (bookmark.steps !== steps) {
          previousCacheBucket = getDestinationBucket(bookmark.steps);
          if (previousCacheBucket !== newCacheBucket && stepToDomPoint[previousCacheBucket] === bookmark) {
            delete stepToDomPoint[previousCacheBucket];
          }
          bookmark.steps = steps;
        }
        insertBookmark(closestPriorBookmark, bookmark);
        existingCachePoint = stepToDomPoint[newCacheBucket];
        if (!existingCachePoint || bookmark.steps > existingCachePoint.steps) {
          stepToDomPoint[newCacheBucket] = bookmark;
        }
        verifyCache();
      };
      this.setToClosestStep = function(steps, iterator) {
        var cachePoint;
        verifyCache();
        cachePoint = getClosestBookmark(steps);
        cachePoint.setIteratorPosition(iterator);
        return cachePoint.steps;
      };
      function findBookmarkedAncestor(node) {
        var currentNode = node, nodeId, bookmark = null;
        while (!bookmark && currentNode && currentNode !== rootElement) {
          nodeId = getNodeId(currentNode);
          if (nodeId) {
            bookmark = nodeToBookmark[nodeId];
            if (bookmark && !isValidBookmarkForNode(currentNode, bookmark)) {
              runtime.log("Cloned node detected. Creating new bookmark");
              bookmark = null;
              clearNodeId(currentNode);
            }
          }
          currentNode = currentNode.parentNode;
        }
        return bookmark;
      }
      this.setToClosestDomPoint = function(node, offset, iterator) {
        var bookmark, b, key;
        verifyCache();
        if (node === rootElement && offset === 0) {
          bookmark = basePoint;
        } else {
          if (node === rootElement && offset === rootElement.childNodes.length) {
            bookmark = basePoint;
            for (key in stepToDomPoint) {
              if (stepToDomPoint.hasOwnProperty(key)) {
                b = stepToDomPoint[key];
                if (b.steps > bookmark.steps) {
                  bookmark = b;
                }
              }
            }
          } else {
            bookmark = findBookmarkedAncestor(node.childNodes.item(offset) || node);
            if (!bookmark) {
              iterator.setUnfilteredPosition(node, offset);
              while (!bookmark && iterator.previousNode()) {
                bookmark = findBookmarkedAncestor(iterator.getCurrentNode());
              }
            }
          }
        }
        bookmark = getUndamagedBookmark(bookmark || basePoint);
        bookmark.setIteratorPosition(iterator);
        return bookmark.steps;
      };
      this.damageCacheAfterStep = function(inflectionStep) {
        if (inflectionStep < 0) {
          inflectionStep = -1;
        }
        if (lastUndamagedCacheStep === undefined) {
          lastUndamagedCacheStep = inflectionStep;
        } else {
          if (inflectionStep < lastUndamagedCacheStep) {
            lastUndamagedCacheStep = inflectionStep;
          }
        }
        verifyCache();
      };
      function init() {
        var rootElementId = getNodeId(rootElement) || setNodeId(rootElement);
        basePoint = new RootBookmark(rootElementId, 0, rootElement);
      }
      init();
    };
    ops.StepsCache.ENABLE_CACHE_VERIFICATION = false;
    ops.StepsCache.Bookmark = function Bookmark() {
    };
    ops.StepsCache.Bookmark.prototype.nodeId;
    ops.StepsCache.Bookmark.prototype.node;
    ops.StepsCache.Bookmark.prototype.steps;
    ops.StepsCache.Bookmark.prototype.previousBookmark;
    ops.StepsCache.Bookmark.prototype.nextBookmark;
    ops.StepsCache.Bookmark.prototype.setIteratorPosition = function(iterator) {
    };
  })();
  (function() {
    ops.OdtStepsTranslator = function OdtStepsTranslator(rootNode, iterator, filter, bucketSize) {
      var stepsCache, odfUtils = odf.OdfUtils, domUtils = core.DomUtils, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, PREVIOUS = core.StepDirection.PREVIOUS, NEXT = core.StepDirection.NEXT;
      function updateCache(steps, iterator, isStep) {
        var node = iterator.getCurrentNode();
        if (iterator.isBeforeNode() && odfUtils.isParagraph(node)) {
          if (!isStep) {
            steps += 1;
          }
          stepsCache.updateBookmark(steps, node);
        }
      }
      function roundUpToStep(steps, iterator) {
        do {
          if (filter.acceptPosition(iterator) === FILTER_ACCEPT) {
            updateCache(steps, iterator, true);
            break;
          }
          updateCache(steps - 1, iterator, false);
        } while (iterator.nextPosition());
      }
      this.convertStepsToDomPoint = function(steps) {
        var stepsFromRoot, isStep;
        if (isNaN(steps)) {
          throw new TypeError("Requested steps is not numeric (" + steps + ")");
        }
        if (steps < 0) {
          throw new RangeError("Requested steps is negative (" + steps + ")");
        }
        stepsFromRoot = stepsCache.setToClosestStep(steps, iterator);
        while (stepsFromRoot < steps && iterator.nextPosition()) {
          isStep = filter.acceptPosition(iterator) === FILTER_ACCEPT;
          if (isStep) {
            stepsFromRoot += 1;
          }
          updateCache(stepsFromRoot, iterator, isStep);
        }
        if (stepsFromRoot !== steps) {
          throw new RangeError("Requested steps (" + steps + ") exceeds available steps (" + stepsFromRoot + ")");
        }
        return {node:iterator.container(), offset:iterator.unfilteredDomOffset()};
      };
      function roundToPreferredStep(iterator, roundDirection) {
        if (!roundDirection || filter.acceptPosition(iterator) === FILTER_ACCEPT) {
          return true;
        }
        while (iterator.previousPosition()) {
          if (filter.acceptPosition(iterator) === FILTER_ACCEPT) {
            if (roundDirection(PREVIOUS, iterator.container(), iterator.unfilteredDomOffset())) {
              return true;
            }
            break;
          }
        }
        while (iterator.nextPosition()) {
          if (filter.acceptPosition(iterator) === FILTER_ACCEPT) {
            if (roundDirection(NEXT, iterator.container(), iterator.unfilteredDomOffset())) {
              return true;
            }
            break;
          }
        }
        return false;
      }
      this.convertDomPointToSteps = function(node, offset, roundDirection) {
        var stepsFromRoot, beforeRoot, destinationNode, destinationOffset, rounding = 0, isStep;
        if (!domUtils.containsNode(rootNode, node)) {
          beforeRoot = domUtils.comparePoints(rootNode, 0, node, offset) < 0;
          node = rootNode;
          offset = beforeRoot ? 0 : rootNode.childNodes.length;
        }
        iterator.setUnfilteredPosition(node, offset);
        if (!roundToPreferredStep(iterator, roundDirection)) {
          iterator.setUnfilteredPosition(node, offset);
        }
        destinationNode = iterator.container();
        destinationOffset = iterator.unfilteredDomOffset();
        stepsFromRoot = stepsCache.setToClosestDomPoint(destinationNode, destinationOffset, iterator);
        if (domUtils.comparePoints(iterator.container(), iterator.unfilteredDomOffset(), destinationNode, destinationOffset) < 0) {
          return stepsFromRoot > 0 ? stepsFromRoot - 1 : stepsFromRoot;
        }
        while (!(iterator.container() === destinationNode && iterator.unfilteredDomOffset() === destinationOffset) && iterator.nextPosition()) {
          isStep = filter.acceptPosition(iterator) === FILTER_ACCEPT;
          if (isStep) {
            stepsFromRoot += 1;
          }
          updateCache(stepsFromRoot, iterator, isStep);
        }
        return stepsFromRoot + rounding;
      };
      this.prime = function() {
        var stepsFromRoot, isStep;
        stepsFromRoot = stepsCache.setToClosestStep(0, iterator);
        while (iterator.nextPosition()) {
          isStep = filter.acceptPosition(iterator) === FILTER_ACCEPT;
          if (isStep) {
            stepsFromRoot += 1;
          }
          updateCache(stepsFromRoot, iterator, isStep);
        }
      };
      this.handleStepsInserted = function(eventArgs) {
        stepsCache.damageCacheAfterStep(eventArgs.position);
      };
      this.handleStepsRemoved = function(eventArgs) {
        stepsCache.damageCacheAfterStep(eventArgs.position - 1);
      };
      function init() {
        stepsCache = new ops.StepsCache(rootNode, bucketSize, roundUpToStep);
      }
      init();
    };
  })();
  ops.Operation = function Operation() {
  };
  ops.Operation.prototype.init = function(data) {
  };
  ops.Operation.prototype.isEdit;
  ops.Operation.prototype.group;
  ops.Operation.prototype.execute = function(document) {
  };
  ops.Operation.prototype.spec = function() {
  };
  ops.TextPositionFilter = function TextPositionFilter() {
    var odfUtils = odf.OdfUtils, ELEMENT_NODE = Node.ELEMENT_NODE, TEXT_NODE = Node.TEXT_NODE, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT;
    function previousSibling(node, nodeFilter) {
      while (node && nodeFilter(node) !== FILTER_ACCEPT) {
        node = node.previousSibling;
      }
      return node;
    }
    function checkLeftRight(container, leftNode, rightNode, nodeFilter) {
      var r, firstPos, rightOfChar;
      if (leftNode) {
        if (odfUtils.isInlineRoot(leftNode) && odfUtils.isGroupingElement(rightNode)) {
          return FILTER_REJECT;
        }
        r = odfUtils.lookLeftForCharacter(leftNode);
        if (r === 1) {
          return FILTER_ACCEPT;
        }
        if (r === 2 && (odfUtils.scanRightForAnyCharacter(rightNode) || odfUtils.scanRightForAnyCharacter(odfUtils.nextNode(container)))) {
          return FILTER_ACCEPT;
        }
      } else {
        if (odfUtils.isGroupingElement(container) && odfUtils.isInlineRoot(previousSibling(container.previousSibling, nodeFilter))) {
          return FILTER_ACCEPT;
        }
      }
      firstPos = leftNode === null && odfUtils.isParagraph(container);
      rightOfChar = odfUtils.lookRightForCharacter(rightNode);
      if (firstPos) {
        if (rightOfChar) {
          return FILTER_ACCEPT;
        }
        return odfUtils.scanRightForAnyCharacter(rightNode) ? FILTER_REJECT : FILTER_ACCEPT;
      }
      if (!rightOfChar) {
        return FILTER_REJECT;
      }
      leftNode = leftNode || odfUtils.previousNode(container);
      return odfUtils.scanLeftForAnyCharacter(leftNode) ? FILTER_REJECT : FILTER_ACCEPT;
    }
    this.acceptPosition = function(iterator) {
      var container = iterator.container(), nodeType = container.nodeType, offset, text, leftChar, rightChar, leftNode, rightNode, r;
      if (nodeType !== ELEMENT_NODE && nodeType !== TEXT_NODE) {
        return FILTER_REJECT;
      }
      if (nodeType === TEXT_NODE) {
        offset = iterator.unfilteredDomOffset();
        text = container.data;
        runtime.assert(offset !== text.length, "Unexpected offset.");
        if (offset > 0) {
          leftChar = text[offset - 1];
          if (!odfUtils.isODFWhitespace(leftChar)) {
            return FILTER_ACCEPT;
          }
          if (offset > 1) {
            leftChar = text[offset - 2];
            if (!odfUtils.isODFWhitespace(leftChar)) {
              r = FILTER_ACCEPT;
            } else {
              if (!odfUtils.isODFWhitespace(text.substr(0, offset))) {
                return FILTER_REJECT;
              }
            }
          } else {
            leftNode = odfUtils.previousNode(container);
            if (odfUtils.scanLeftForNonSpace(leftNode)) {
              r = FILTER_ACCEPT;
            }
          }
          if (r === FILTER_ACCEPT) {
            return odfUtils.isTrailingWhitespace(container, offset) ? FILTER_REJECT : FILTER_ACCEPT;
          }
          rightChar = text[offset];
          if (odfUtils.isODFWhitespace(rightChar)) {
            return FILTER_REJECT;
          }
          return odfUtils.scanLeftForAnyCharacter(odfUtils.previousNode(container)) ? FILTER_REJECT : FILTER_ACCEPT;
        }
        leftNode = iterator.leftNode();
        rightNode = container;
        container = container.parentNode;
        r = checkLeftRight(container, leftNode, rightNode, iterator.getNodeFilter());
      } else {
        if (!odfUtils.isGroupingElement(container)) {
          r = FILTER_REJECT;
        } else {
          leftNode = iterator.leftNode();
          rightNode = iterator.rightNode();
          r = checkLeftRight(container, leftNode, rightNode, iterator.getNodeFilter());
        }
      }
      return r;
    };
  };
  function RootFilter(anchor, cursors, getRoot) {
    var FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT;
    this.acceptPosition = function(iterator) {
      var node = iterator.container(), anchorNode;
      if (typeof anchor === "string") {
        anchorNode = cursors[anchor].getNode();
      } else {
        anchorNode = anchor;
      }
      if (getRoot(node) === getRoot(anchorNode)) {
        return FILTER_ACCEPT;
      }
      return FILTER_REJECT;
    };
  }
  ops.OdtDocument = function OdtDocument(odfCanvas) {
    var self = this, stepUtils, odfUtils = odf.OdfUtils, domUtils = core.DomUtils, cursors = {}, members = {}, eventNotifier = new core.EventNotifier([ops.Document.signalMemberAdded, ops.Document.signalMemberUpdated, ops.Document.signalMemberRemoved, ops.Document.signalCursorAdded, ops.Document.signalCursorRemoved, ops.Document.signalCursorMoved, ops.OdtDocument.signalParagraphChanged, ops.OdtDocument.signalParagraphStyleModified, ops.OdtDocument.signalCommonStyleCreated, ops.OdtDocument.signalCommonStyleDeleted,
    ops.OdtDocument.signalTableAdded, ops.OdtDocument.signalOperationStart, ops.OdtDocument.signalOperationEnd, ops.OdtDocument.signalProcessingBatchStart, ops.OdtDocument.signalProcessingBatchEnd, ops.OdtDocument.signalUndoStackChanged, ops.OdtDocument.signalStepsInserted, ops.OdtDocument.signalStepsRemoved, ops.OdtDocument.signalMetadataUpdated, ops.OdtDocument.signalAnnotationAdded]), NEXT = core.StepDirection.NEXT, filter, stepsTranslator, lastEditingOp, unsupportedMetadataRemoved = false, SHOW_ALL =
    NodeFilter.SHOW_ALL, blacklistedNodes = new gui.BlacklistNamespaceNodeFilter(["urn:webodf:names:cursor", "urn:webodf:names:editinfo"]), odfTextBodyFilter = new gui.OdfTextBodyNodeFilter, defaultNodeFilter = new core.NodeFilterChain([blacklistedNodes, odfTextBodyFilter]);
    function createPositionIterator(rootNode) {
      return new core.PositionIterator(rootNode, SHOW_ALL, defaultNodeFilter, false);
    }
    this.createPositionIterator = createPositionIterator;
    function getRootNode() {
      var element = odfCanvas.odfContainer().getContentElement(), localName = element && element.localName;
      runtime.assert(localName === "text", "Unsupported content element type '" + localName + "' for OdtDocument");
      return element;
    }
    this.getDocumentElement = function() {
      return odfCanvas.odfContainer().rootElement;
    };
    this.cloneDocumentElement = function() {
      var rootElement = self.getDocumentElement(), annotationViewManager = odfCanvas.getAnnotationViewManager(), initialDoc;
      if (annotationViewManager) {
        annotationViewManager.forgetAnnotations();
      }
      initialDoc = rootElement.cloneNode(true);
      odfCanvas.refreshAnnotations();
      self.fixCursorPositions();
      return initialDoc;
    };
    this.setDocumentElement = function(documentElement) {
      var odfContainer = odfCanvas.odfContainer(), rootNode;
      eventNotifier.unsubscribe(ops.OdtDocument.signalStepsInserted, stepsTranslator.handleStepsInserted);
      eventNotifier.unsubscribe(ops.OdtDocument.signalStepsRemoved, stepsTranslator.handleStepsRemoved);
      odfContainer.setRootElement(documentElement);
      odfCanvas.setOdfContainer(odfContainer, true);
      odfCanvas.refreshCSS();
      rootNode = getRootNode();
      stepsTranslator = new ops.OdtStepsTranslator(rootNode, createPositionIterator(rootNode), filter, 500);
      eventNotifier.subscribe(ops.OdtDocument.signalStepsInserted, stepsTranslator.handleStepsInserted);
      eventNotifier.subscribe(ops.OdtDocument.signalStepsRemoved, stepsTranslator.handleStepsRemoved);
    };
    function getDOMDocument() {
      return self.getDocumentElement().ownerDocument;
    }
    this.getDOMDocument = getDOMDocument;
    function isRoot(node) {
      if (node.namespaceURI === odf.Namespaces.officens && node.localName === "text" || node.namespaceURI === odf.Namespaces.officens && node.localName === "annotation") {
        return true;
      }
      return false;
    }
    function getRoot(node) {
      while (node && !isRoot(node)) {
        node = node.parentNode;
      }
      return node;
    }
    this.getRootElement = getRoot;
    function createStepIterator(container, offset, filters, subTree) {
      var positionIterator = createPositionIterator(subTree), filterOrChain, stepIterator;
      if (filters.length === 1) {
        filterOrChain = filters[0];
      } else {
        filterOrChain = new core.PositionFilterChain;
        filters.forEach(filterOrChain.addFilter);
      }
      stepIterator = new core.StepIterator(filterOrChain, positionIterator);
      stepIterator.setPosition(container, offset);
      return stepIterator;
    }
    this.createStepIterator = createStepIterator;
    function getIteratorAtPosition(position) {
      var iterator = createPositionIterator(getRootNode()), point = stepsTranslator.convertStepsToDomPoint(position);
      iterator.setUnfilteredPosition(point.node, point.offset);
      return iterator;
    }
    this.getIteratorAtPosition = getIteratorAtPosition;
    this.convertCursorStepToDomPoint = function(step) {
      return stepsTranslator.convertStepsToDomPoint(step);
    };
    function roundUp(step) {
      return step === NEXT;
    }
    this.convertDomPointToCursorStep = function(node, offset, roundDirection) {
      var roundingFunc;
      if (roundDirection === NEXT) {
        roundingFunc = roundUp;
      }
      return stepsTranslator.convertDomPointToSteps(node, offset, roundingFunc);
    };
    this.convertDomToCursorRange = function(selection) {
      var point1, point2;
      point1 = stepsTranslator.convertDomPointToSteps(selection.anchorNode, selection.anchorOffset);
      if (selection.anchorNode === selection.focusNode && selection.anchorOffset === selection.focusOffset) {
        point2 = point1;
      } else {
        point2 = stepsTranslator.convertDomPointToSteps(selection.focusNode, selection.focusOffset);
      }
      return {position:point1, length:point2 - point1};
    };
    this.convertCursorToDomRange = function(position, length) {
      var range = getDOMDocument().createRange(), point1, point2;
      point1 = stepsTranslator.convertStepsToDomPoint(position);
      if (length) {
        point2 = stepsTranslator.convertStepsToDomPoint(position + length);
        if (length > 0) {
          range.setStart(point1.node, point1.offset);
          range.setEnd(point2.node, point2.offset);
        } else {
          range.setStart(point2.node, point2.offset);
          range.setEnd(point1.node, point1.offset);
        }
      } else {
        range.setStart(point1.node, point1.offset);
      }
      return range;
    };
    function getTextNodeAtStep(steps, memberid) {
      var iterator = getIteratorAtPosition(steps), node = iterator.container(), lastTextNode, nodeOffset = 0, cursorNode = null, text;
      if (node.nodeType === Node.TEXT_NODE) {
        lastTextNode = node;
        nodeOffset = iterator.unfilteredDomOffset();
        if (lastTextNode.length > 0) {
          if (nodeOffset > 0) {
            lastTextNode = lastTextNode.splitText(nodeOffset);
          }
          lastTextNode.parentNode.insertBefore(getDOMDocument().createTextNode(""), lastTextNode);
          lastTextNode = lastTextNode.previousSibling;
          nodeOffset = 0;
        }
      } else {
        lastTextNode = getDOMDocument().createTextNode("");
        nodeOffset = 0;
        node.insertBefore(lastTextNode, iterator.rightNode());
      }
      if (memberid) {
        if (cursors[memberid] && self.getCursorPosition(memberid) === steps) {
          cursorNode = cursors[memberid].getNode();
          while (cursorNode.nextSibling && cursorNode.nextSibling.localName === "cursor") {
            cursorNode.parentNode.insertBefore(cursorNode.nextSibling, cursorNode);
          }
          if (lastTextNode.length > 0 && lastTextNode.nextSibling !== cursorNode) {
            lastTextNode = getDOMDocument().createTextNode("");
            nodeOffset = 0;
          }
          cursorNode.parentNode.insertBefore(lastTextNode, cursorNode);
        }
      } else {
        while (lastTextNode.nextSibling && lastTextNode.nextSibling.localName === "cursor") {
          lastTextNode.parentNode.insertBefore(lastTextNode.nextSibling, lastTextNode);
        }
      }
      while (lastTextNode.previousSibling && lastTextNode.previousSibling.nodeType === Node.TEXT_NODE) {
        text = lastTextNode.previousSibling;
        text.appendData(lastTextNode.data);
        nodeOffset = text.length;
        lastTextNode = text;
        lastTextNode.parentNode.removeChild(lastTextNode.nextSibling);
      }
      while (lastTextNode.nextSibling && lastTextNode.nextSibling.nodeType === Node.TEXT_NODE) {
        text = lastTextNode.nextSibling;
        lastTextNode.appendData(text.data);
        lastTextNode.parentNode.removeChild(text);
      }
      return {textNode:lastTextNode, offset:nodeOffset};
    }
    function handleOperationExecuted(op) {
      var opspec = op.spec(), memberId = opspec.memberid, date = (new Date(opspec.timestamp)).toISOString(), odfContainer = odfCanvas.odfContainer(), changedMetadata, fullName;
      if (op.isEdit) {
        fullName = self.getMember(memberId).getProperties().fullName;
        odfContainer.setMetadata({"dc:creator":fullName, "dc:date":date}, null);
        changedMetadata = {setProperties:{"dc:creator":fullName, "dc:date":date}, removedProperties:[]};
        if (!lastEditingOp) {
          changedMetadata.setProperties["meta:editing-cycles"] = odfContainer.incrementEditingCycles();
          if (!unsupportedMetadataRemoved) {
            odfContainer.setMetadata(null, ["meta:editing-duration", "meta:document-statistic"]);
          }
        }
        lastEditingOp = op;
        self.emit(ops.OdtDocument.signalMetadataUpdated, changedMetadata);
      }
    }
    function upgradeWhitespaceToElement(textNode, offset) {
      runtime.assert(textNode.data[offset] === " ", "upgradeWhitespaceToElement: textNode.data[offset] should be a literal space");
      var space = textNode.ownerDocument.createElementNS(odf.Namespaces.textns, "text:s"), container = textNode.parentNode, adjacentNode = textNode;
      space.appendChild(textNode.ownerDocument.createTextNode(" "));
      if (textNode.length === 1) {
        container.replaceChild(space, textNode);
      } else {
        textNode.deleteData(offset, 1);
        if (offset > 0) {
          if (offset < textNode.length) {
            textNode.splitText(offset);
          }
          adjacentNode = textNode.nextSibling;
        }
        container.insertBefore(space, adjacentNode);
      }
      return space;
    }
    function upgradeWhitespacesAtPosition(step) {
      var positionIterator = getIteratorAtPosition(step), stepIterator = new core.StepIterator(filter, positionIterator), contentBounds, container, offset, stepsToUpgrade = 2;
      runtime.assert(stepIterator.isStep(), "positionIterator is not at a step (requested step: " + step + ")");
      do {
        contentBounds = stepUtils.getContentBounds(stepIterator);
        if (contentBounds) {
          container = contentBounds.container;
          offset = contentBounds.startOffset;
          if (container.nodeType === Node.TEXT_NODE && odfUtils.isSignificantWhitespace(container, offset)) {
            container = upgradeWhitespaceToElement(container, offset);
            stepIterator.setPosition(container, container.childNodes.length);
            stepIterator.roundToPreviousStep();
          }
        }
        stepsToUpgrade -= 1;
      } while (stepsToUpgrade > 0 && stepIterator.nextStep());
    }
    this.upgradeWhitespacesAtPosition = upgradeWhitespacesAtPosition;
    function maxOffset(node) {
      return node.nodeType === Node.TEXT_NODE ? node.length : node.childNodes.length;
    }
    function downgradeWhitespaces(stepIterator) {
      var contentBounds, container, modifiedNodes = [], lastChild, stepsToUpgrade = 2;
      runtime.assert(stepIterator.isStep(), "positionIterator is not at a step");
      do {
        contentBounds = stepUtils.getContentBounds(stepIterator);
        if (contentBounds) {
          container = contentBounds.container;
          if (odfUtils.isDowngradableSpaceElement(container)) {
            lastChild = container.lastChild;
            while (container.firstChild) {
              modifiedNodes.push(container.firstChild);
              container.parentNode.insertBefore(container.firstChild, container);
            }
            container.parentNode.removeChild(container);
            stepIterator.setPosition(lastChild, maxOffset(lastChild));
            stepIterator.roundToPreviousStep();
          }
        }
        stepsToUpgrade -= 1;
      } while (stepsToUpgrade > 0 && stepIterator.nextStep());
      modifiedNodes.forEach(domUtils.normalizeTextNodes);
    }
    this.downgradeWhitespaces = downgradeWhitespaces;
    this.downgradeWhitespacesAtPosition = function(step) {
      var positionIterator = getIteratorAtPosition(step), stepIterator = new core.StepIterator(filter, positionIterator);
      downgradeWhitespaces(stepIterator);
    };
    this.getTextNodeAtStep = getTextNodeAtStep;
    function paragraphOrRoot(container, offset, root) {
      var node = container.childNodes.item(offset) || container, paragraph = odfUtils.getParagraphElement(node);
      if (paragraph && domUtils.containsNode(root, paragraph)) {
        return paragraph;
      }
      return root;
    }
    this.fixCursorPositions = function() {
      Object.keys(cursors).forEach(function(memberId) {
        var cursor = cursors[memberId], root = getRoot(cursor.getNode()), rootFilter = self.createRootFilter(root), subTree, startPoint, endPoint, selectedRange, cursorMoved = false;
        selectedRange = cursor.getSelectedRange();
        subTree = paragraphOrRoot(selectedRange.startContainer, selectedRange.startOffset, root);
        startPoint = createStepIterator(selectedRange.startContainer, selectedRange.startOffset, [filter, rootFilter], subTree);
        if (!selectedRange.collapsed) {
          subTree = paragraphOrRoot(selectedRange.endContainer, selectedRange.endOffset, root);
          endPoint = createStepIterator(selectedRange.endContainer, selectedRange.endOffset, [filter, rootFilter], subTree);
        } else {
          endPoint = startPoint;
        }
        if (!startPoint.isStep() || !endPoint.isStep()) {
          cursorMoved = true;
          runtime.assert(startPoint.roundToClosestStep(), "No walkable step found for cursor owned by " + memberId);
          selectedRange.setStart(startPoint.container(), startPoint.offset());
          runtime.assert(endPoint.roundToClosestStep(), "No walkable step found for cursor owned by " + memberId);
          selectedRange.setEnd(endPoint.container(), endPoint.offset());
        } else {
          if (startPoint.container() === endPoint.container() && startPoint.offset() === endPoint.offset()) {
            if (!selectedRange.collapsed || cursor.getAnchorNode() !== cursor.getNode()) {
              cursorMoved = true;
              selectedRange.setStart(startPoint.container(), startPoint.offset());
              selectedRange.collapse(true);
            }
          }
        }
        if (cursorMoved) {
          cursor.setSelectedRange(selectedRange, cursor.hasForwardSelection());
          self.emit(ops.Document.signalCursorMoved, cursor);
        }
      });
    };
    this.getCursorPosition = function(memberid) {
      var cursor = cursors[memberid];
      return cursor ? stepsTranslator.convertDomPointToSteps(cursor.getNode(), 0) : 0;
    };
    this.getCursorSelection = function(memberid) {
      var cursor = cursors[memberid], focusPosition = 0, anchorPosition = 0;
      if (cursor) {
        focusPosition = stepsTranslator.convertDomPointToSteps(cursor.getNode(), 0);
        anchorPosition = stepsTranslator.convertDomPointToSteps(cursor.getAnchorNode(), 0);
      }
      return {position:anchorPosition, length:focusPosition - anchorPosition};
    };
    this.getPositionFilter = function() {
      return filter;
    };
    this.getOdfCanvas = function() {
      return odfCanvas;
    };
    this.getCanvas = function() {
      return odfCanvas;
    };
    this.getRootNode = getRootNode;
    this.addMember = function(member) {
      runtime.assert(members[member.getMemberId()] === undefined, "This member already exists");
      members[member.getMemberId()] = member;
    };
    this.getMember = function(memberId) {
      return members.hasOwnProperty(memberId) ? members[memberId] : null;
    };
    this.removeMember = function(memberId) {
      delete members[memberId];
    };
    this.getCursor = function(memberid) {
      return cursors[memberid];
    };
    this.hasCursor = function(memberid) {
      return cursors.hasOwnProperty(memberid);
    };
    this.getMemberIds = function() {
      return Object.keys(members);
    };
    this.addCursor = function(cursor) {
      runtime.assert(Boolean(cursor), "OdtDocument::addCursor without cursor");
      var memberid = cursor.getMemberId(), initialSelection = self.convertCursorToDomRange(0, 0);
      runtime.assert(typeof memberid === "string", "OdtDocument::addCursor has cursor without memberid");
      runtime.assert(!cursors[memberid], "OdtDocument::addCursor is adding a duplicate cursor with memberid " + memberid);
      cursor.setSelectedRange(initialSelection, true);
      cursors[memberid] = cursor;
    };
    this.removeCursor = function(memberid) {
      var cursor = cursors[memberid];
      if (cursor) {
        cursor.removeFromDocument();
        delete cursors[memberid];
        self.emit(ops.Document.signalCursorRemoved, memberid);
        return true;
      }
      return false;
    };
    this.moveCursor = function(memberid, position, length, selectionType) {
      var cursor = cursors[memberid], selectionRange = self.convertCursorToDomRange(position, length);
      if (cursor) {
        cursor.setSelectedRange(selectionRange, length >= 0);
        cursor.setSelectionType(selectionType || ops.OdtCursor.RangeSelection);
      }
    };
    this.getFormatting = function() {
      return odfCanvas.getFormatting();
    };
    this.emit = function(eventid, args) {
      eventNotifier.emit(eventid, args);
    };
    this.subscribe = function(eventid, cb) {
      eventNotifier.subscribe(eventid, cb);
    };
    this.unsubscribe = function(eventid, cb) {
      eventNotifier.unsubscribe(eventid, cb);
    };
    this.createRootFilter = function(inputMemberId) {
      return new RootFilter(inputMemberId, cursors, getRoot);
    };
    this.close = function(callback) {
      callback();
    };
    this.destroy = function(callback) {
      callback();
    };
    function init() {
      var rootNode = getRootNode();
      filter = new ops.TextPositionFilter;
      stepUtils = new odf.StepUtils;
      stepsTranslator = new ops.OdtStepsTranslator(rootNode, createPositionIterator(rootNode), filter, 500);
      eventNotifier.subscribe(ops.OdtDocument.signalStepsInserted, stepsTranslator.handleStepsInserted);
      eventNotifier.subscribe(ops.OdtDocument.signalStepsRemoved, stepsTranslator.handleStepsRemoved);
      eventNotifier.subscribe(ops.OdtDocument.signalOperationEnd, handleOperationExecuted);
      eventNotifier.subscribe(ops.OdtDocument.signalProcessingBatchEnd, core.Task.processTasks);
    }
    init();
  };
  ops.OdtDocument.signalParagraphChanged = "paragraph/changed";
  ops.OdtDocument.signalTableAdded = "table/added";
  ops.OdtDocument.signalCommonStyleCreated = "style/created";
  ops.OdtDocument.signalCommonStyleDeleted = "style/deleted";
  ops.OdtDocument.signalParagraphStyleModified = "paragraphstyle/modified";
  ops.OdtDocument.signalOperationStart = "operation/start";
  ops.OdtDocument.signalOperationEnd = "operation/end";
  ops.OdtDocument.signalProcessingBatchStart = "router/batchstart";
  ops.OdtDocument.signalProcessingBatchEnd = "router/batchend";
  ops.OdtDocument.signalUndoStackChanged = "undo/changed";
  ops.OdtDocument.signalStepsInserted = "steps/inserted";
  ops.OdtDocument.signalStepsRemoved = "steps/removed";
  ops.OdtDocument.signalMetadataUpdated = "metadata/updated";
  ops.OdtDocument.signalAnnotationAdded = "annotation/added";
  ops.OpAddAnnotation = function OpAddAnnotation() {
    var memberid, timestamp, position, length, name, doc;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = parseInt(data.timestamp, 10);
      position = parseInt(data.position, 10);
      length = data.length !== undefined ? parseInt(data.length, 10) || 0 : undefined;
      name = data.name;
    };
    this.isEdit = true;
    this.group = undefined;
    function createAnnotationNode(odtDocument, date) {
      var annotationNode, creatorNode, dateNode, listNode, listItemNode, paragraphNode;
      annotationNode = doc.createElementNS(odf.Namespaces.officens, "office:annotation");
      annotationNode.setAttributeNS(odf.Namespaces.officens, "office:name", name);
      creatorNode = doc.createElementNS(odf.Namespaces.dcns, "dc:creator");
      creatorNode.setAttributeNS("urn:webodf:names:editinfo", "editinfo:memberid", memberid);
      creatorNode.textContent = odtDocument.getMember(memberid).getProperties().fullName;
      dateNode = doc.createElementNS(odf.Namespaces.dcns, "dc:date");
      dateNode.appendChild(doc.createTextNode(date.toISOString()));
      listNode = doc.createElementNS(odf.Namespaces.textns, "text:list");
      listItemNode = doc.createElementNS(odf.Namespaces.textns, "text:list-item");
      paragraphNode = doc.createElementNS(odf.Namespaces.textns, "text:p");
      listItemNode.appendChild(paragraphNode);
      listNode.appendChild(listItemNode);
      annotationNode.appendChild(creatorNode);
      annotationNode.appendChild(dateNode);
      annotationNode.appendChild(listNode);
      return annotationNode;
    }
    function createAnnotationEnd() {
      var annotationEnd;
      annotationEnd = doc.createElementNS(odf.Namespaces.officens, "office:annotation-end");
      annotationEnd.setAttributeNS(odf.Namespaces.officens, "office:name", name);
      return annotationEnd;
    }
    function insertNodeAtPosition(odtDocument, node, insertPosition) {
      var previousNode, parentNode, domPosition = odtDocument.getTextNodeAtStep(insertPosition, memberid);
      if (domPosition) {
        previousNode = domPosition.textNode;
        parentNode = previousNode.parentNode;
        if (domPosition.offset !== previousNode.length) {
          previousNode.splitText(domPosition.offset);
        }
        parentNode.insertBefore(node, previousNode.nextSibling);
        if (previousNode.length === 0) {
          parentNode.removeChild(previousNode);
        }
      }
    }
    this.execute = function(document) {
      var odtDocument = document, annotation, annotationEnd, cursor = odtDocument.getCursor(memberid), selectedRange, paragraphElement;
      doc = odtDocument.getDOMDocument();
      annotation = createAnnotationNode(odtDocument, new Date(timestamp));
      if (length !== undefined) {
        annotationEnd = createAnnotationEnd();
        annotation.annotationEndElement = annotationEnd;
        insertNodeAtPosition(odtDocument, annotationEnd, position + length);
      }
      insertNodeAtPosition(odtDocument, annotation, position);
      odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position});
      if (cursor) {
        selectedRange = doc.createRange();
        paragraphElement = annotation.getElementsByTagNameNS(odf.Namespaces.textns, "p")[0];
        selectedRange.selectNodeContents(paragraphElement);
        cursor.setSelectedRange(selectedRange, false);
        cursor.setSelectionType(ops.OdtCursor.RangeSelection);
        odtDocument.emit(ops.Document.signalCursorMoved, cursor);
      }
      odtDocument.getOdfCanvas().addAnnotation(annotation);
      odtDocument.fixCursorPositions();
      odtDocument.emit(ops.OdtDocument.signalAnnotationAdded, {memberId:memberid, annotation:annotation});
      return true;
    };
    this.spec = function() {
      return {optype:"AddAnnotation", memberid:memberid, timestamp:timestamp, position:position, length:length, name:name};
    };
  };
  ops.OpAddAnnotation.Spec;
  ops.OpAddAnnotation.InitSpec;
  ops.OpAddCursor = function OpAddCursor() {
    var memberid, timestamp;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
    };
    this.isEdit = false;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, cursor = odtDocument.getCursor(memberid);
      if (cursor) {
        return false;
      }
      cursor = new ops.OdtCursor(memberid, odtDocument);
      odtDocument.addCursor(cursor);
      odtDocument.emit(ops.Document.signalCursorAdded, cursor);
      return true;
    };
    this.spec = function() {
      return {optype:"AddCursor", memberid:memberid, timestamp:timestamp};
    };
  };
  ops.OpAddCursor.Spec;
  ops.OpAddCursor.InitSpec;
  ops.OpAddMember = function OpAddMember() {
    var memberid, timestamp, setProperties;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = parseInt(data.timestamp, 10);
      setProperties = data.setProperties;
    };
    this.isEdit = false;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, member;
      if (odtDocument.getMember(memberid)) {
        return false;
      }
      member = new ops.Member(memberid, setProperties);
      odtDocument.addMember(member);
      odtDocument.emit(ops.Document.signalMemberAdded, member);
      return true;
    };
    this.spec = function() {
      return {optype:"AddMember", memberid:memberid, timestamp:timestamp, setProperties:setProperties};
    };
  };
  ops.OpAddMember.Spec;
  ops.OpAddMember.InitSpec;
  ops.OpAddStyle = function OpAddStyle() {
    var memberid, timestamp, styleName, styleFamily, isAutomaticStyle, setProperties, stylens = odf.Namespaces.stylens;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      styleName = data.styleName;
      styleFamily = data.styleFamily;
      isAutomaticStyle = data.isAutomaticStyle === "true" || data.isAutomaticStyle === true;
      setProperties = data.setProperties;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, odfContainer = odtDocument.getOdfCanvas().odfContainer(), formatting = odtDocument.getFormatting(), dom = odtDocument.getDOMDocument(), styleNode = dom.createElementNS(stylens, "style:style");
      if (!styleNode) {
        return false;
      }
      if (setProperties) {
        formatting.updateStyle(styleNode, setProperties);
      }
      styleNode.setAttributeNS(stylens, "style:family", styleFamily);
      styleNode.setAttributeNS(stylens, "style:name", styleName);
      if (isAutomaticStyle) {
        odfContainer.rootElement.automaticStyles.appendChild(styleNode);
      } else {
        odfContainer.rootElement.styles.appendChild(styleNode);
      }
      odtDocument.getOdfCanvas().refreshCSS();
      if (!isAutomaticStyle) {
        odtDocument.emit(ops.OdtDocument.signalCommonStyleCreated, {name:styleName, family:styleFamily});
      }
      return true;
    };
    this.spec = function() {
      return {optype:"AddStyle", memberid:memberid, timestamp:timestamp, styleName:styleName, styleFamily:styleFamily, isAutomaticStyle:isAutomaticStyle, setProperties:setProperties};
    };
  };
  ops.OpAddStyle.Spec;
  ops.OpAddStyle.InitSpec;
  odf.ObjectNameGenerator = function ObjectNameGenerator(odfContainer, memberId) {
    var stylens = odf.Namespaces.stylens, drawns = odf.Namespaces.drawns, xlinkns = odf.Namespaces.xlinkns, utils = new core.Utils, memberIdHash = utils.hashString(memberId), styleNameGenerator = null, frameNameGenerator = null, imageNameGenerator = null, existingFrameNames = {}, existingImageNames = {};
    function NameGenerator(prefix, findExistingNames) {
      var reportedNames = {};
      this.generateName = function() {
        var existingNames = findExistingNames(), startIndex = 0, name;
        do {
          name = prefix + startIndex;
          startIndex += 1;
        } while (reportedNames[name] || existingNames[name]);
        reportedNames[name] = true;
        return name;
      };
    }
    function getAllStyleNames() {
      var styleElements = [odfContainer.rootElement.automaticStyles, odfContainer.rootElement.styles], styleNames = {};
      function getStyleNames(styleListElement) {
        var e = styleListElement.firstElementChild;
        while (e) {
          if (e.namespaceURI === stylens && e.localName === "style") {
            styleNames[e.getAttributeNS(stylens, "name")] = true;
          }
          e = e.nextElementSibling;
        }
      }
      styleElements.forEach(getStyleNames);
      return styleNames;
    }
    this.generateStyleName = function() {
      if (styleNameGenerator === null) {
        styleNameGenerator = new NameGenerator("auto" + memberIdHash + "_", function() {
          return getAllStyleNames();
        });
      }
      return styleNameGenerator.generateName();
    };
    this.generateFrameName = function() {
      var i, nodes, node;
      if (frameNameGenerator === null) {
        nodes = odfContainer.rootElement.body.getElementsByTagNameNS(drawns, "frame");
        for (i = 0;i < nodes.length;i += 1) {
          node = nodes.item(i);
          existingFrameNames[node.getAttributeNS(drawns, "name")] = true;
        }
        frameNameGenerator = new NameGenerator("fr" + memberIdHash + "_", function() {
          return existingFrameNames;
        });
      }
      return frameNameGenerator.generateName();
    };
    this.generateImageName = function() {
      var i, path, nodes, node;
      if (imageNameGenerator === null) {
        nodes = odfContainer.rootElement.body.getElementsByTagNameNS(drawns, "image");
        for (i = 0;i < nodes.length;i += 1) {
          node = nodes.item(i);
          path = node.getAttributeNS(xlinkns, "href");
          path = path.substring("Pictures/".length, path.lastIndexOf("."));
          existingImageNames[path] = true;
        }
        imageNameGenerator = new NameGenerator("img" + memberIdHash + "_", function() {
          return existingImageNames;
        });
      }
      return imageNameGenerator.generateName();
    };
  };
  odf.TextStyleApplicator = function TextStyleApplicator(objectNameGenerator, formatting, automaticStyles) {
    var domUtils = core.DomUtils, textns = odf.Namespaces.textns, stylens = odf.Namespaces.stylens, textProperties = "style:text-properties", webodfns = "urn:webodf:names:scope";
    function StyleLookup(info) {
      var cachedAppliedStyles = {};
      function compare(expected, actual) {
        if (typeof expected === "object" && typeof actual === "object") {
          return Object.keys(expected).every(function(key) {
            return compare(expected[key], actual[key]);
          });
        }
        return expected === actual;
      }
      this.isStyleApplied = function(textNode) {
        var appliedStyle = formatting.getAppliedStylesForElement(textNode, cachedAppliedStyles).styleProperties;
        return compare(info, appliedStyle);
      };
    }
    function StyleManager(info) {
      var createdStyles = {};
      function createDirectFormat(existingStyleName, document) {
        var derivedStyleInfo, derivedStyleNode;
        derivedStyleInfo = existingStyleName ? formatting.createDerivedStyleObject(existingStyleName, "text", info) : info;
        derivedStyleNode = document.createElementNS(stylens, "style:style");
        formatting.updateStyle(derivedStyleNode, derivedStyleInfo);
        derivedStyleNode.setAttributeNS(stylens, "style:name", objectNameGenerator.generateStyleName());
        derivedStyleNode.setAttributeNS(stylens, "style:family", "text");
        derivedStyleNode.setAttributeNS(webodfns, "scope", "document-content");
        automaticStyles.appendChild(derivedStyleNode);
        return derivedStyleNode;
      }
      function getDirectStyle(existingStyleName, document) {
        existingStyleName = existingStyleName || "";
        if (!createdStyles.hasOwnProperty(existingStyleName)) {
          createdStyles[existingStyleName] = createDirectFormat(existingStyleName, document);
        }
        return createdStyles[existingStyleName].getAttributeNS(stylens, "name");
      }
      this.applyStyleToContainer = function(container) {
        var name = getDirectStyle(container.getAttributeNS(textns, "style-name"), container.ownerDocument);
        container.setAttributeNS(textns, "text:style-name", name);
      };
    }
    function isTextSpan(node) {
      return node.localName === "span" && node.namespaceURI === textns;
    }
    function moveToNewSpan(startNode, range) {
      var document = startNode.ownerDocument, originalContainer = startNode.parentNode, styledContainer, trailingContainer, moveTrailing, node, nextNode, loopGuard = new core.LoopWatchDog(1E4), styledNodes = [];
      styledNodes.push(startNode);
      node = startNode.nextSibling;
      while (node && domUtils.rangeContainsNode(range, node)) {
        loopGuard.check();
        styledNodes.push(node);
        node = node.nextSibling;
      }
      if (!isTextSpan(originalContainer)) {
        styledContainer = document.createElementNS(textns, "text:span");
        originalContainer.insertBefore(styledContainer, startNode);
        moveTrailing = false;
      } else {
        if (startNode.previousSibling && !domUtils.rangeContainsNode(range, originalContainer.firstChild)) {
          styledContainer = originalContainer.cloneNode(false);
          originalContainer.parentNode.insertBefore(styledContainer, originalContainer.nextSibling);
          moveTrailing = true;
        } else {
          styledContainer = originalContainer;
          moveTrailing = true;
        }
      }
      styledNodes.forEach(function(n) {
        if (n.parentNode !== styledContainer) {
          styledContainer.appendChild(n);
        }
      });
      if (node && moveTrailing) {
        trailingContainer = styledContainer.cloneNode(false);
        styledContainer.parentNode.insertBefore(trailingContainer, styledContainer.nextSibling);
        while (node) {
          loopGuard.check();
          nextNode = node.nextSibling;
          trailingContainer.appendChild(node);
          node = nextNode;
        }
      }
      return styledContainer;
    }
    this.applyStyle = function(textNodes, range, info) {
      var textPropsOnly = {}, isStyled, container, styleCache, styleLookup;
      runtime.assert(info && info.hasOwnProperty(textProperties), "applyStyle without any text properties");
      textPropsOnly[textProperties] = info[textProperties];
      styleCache = new StyleManager(textPropsOnly);
      styleLookup = new StyleLookup(textPropsOnly);
      function apply(n) {
        isStyled = styleLookup.isStyleApplied(n);
        if (isStyled === false) {
          container = moveToNewSpan(n, range);
          styleCache.applyStyleToContainer(container);
        }
      }
      textNodes.forEach(apply);
    };
  };
  ops.OpApplyDirectStyling = function OpApplyDirectStyling() {
    var memberid, timestamp, position, length, setProperties, odfUtils = odf.OdfUtils, domUtils = core.DomUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = parseInt(data.position, 10);
      length = parseInt(data.length, 10);
      setProperties = data.setProperties;
    };
    this.isEdit = true;
    this.group = undefined;
    function applyStyle(odtDocument, range, info) {
      var odfCanvas = odtDocument.getOdfCanvas(), odfContainer = odfCanvas.odfContainer(), nextTextNodes = domUtils.splitBoundaries(range), textNodes = odfUtils.getTextNodes(range, false), textStyles;
      textStyles = new odf.TextStyleApplicator(new odf.ObjectNameGenerator(odfContainer, memberid), odtDocument.getFormatting(), odfContainer.rootElement.automaticStyles);
      textStyles.applyStyle(textNodes, range, info);
      nextTextNodes.forEach(domUtils.normalizeTextNodes);
    }
    this.execute = function(document) {
      var odtDocument = document, range = odtDocument.convertCursorToDomRange(position, length), impactedParagraphs = odfUtils.getParagraphElements(range);
      applyStyle(odtDocument, range, setProperties);
      range.detach();
      odtDocument.getOdfCanvas().refreshCSS();
      odtDocument.fixCursorPositions();
      impactedParagraphs.forEach(function(n) {
        odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:n, memberId:memberid, timeStamp:timestamp});
      });
      odtDocument.getOdfCanvas().rerenderAnnotations();
      return true;
    };
    this.spec = function() {
      return {optype:"ApplyDirectStyling", memberid:memberid, timestamp:timestamp, position:position, length:length, setProperties:setProperties};
    };
  };
  ops.OpApplyDirectStyling.Spec;
  ops.OpApplyDirectStyling.InitSpec;
  ops.OpApplyHyperlink = function OpApplyHyperlink() {
    var memberid, timestamp, position, length, hyperlink, domUtils = core.DomUtils, odfUtils = odf.OdfUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      length = data.length;
      hyperlink = data.hyperlink;
    };
    this.isEdit = true;
    this.group = undefined;
    function createHyperlink(document, hyperlink) {
      var node = document.createElementNS(odf.Namespaces.textns, "text:a");
      node.setAttributeNS(odf.Namespaces.xlinkns, "xlink:type", "simple");
      node.setAttributeNS(odf.Namespaces.xlinkns, "xlink:href", hyperlink);
      return node;
    }
    function isPartOfLink(node) {
      while (node) {
        if (odfUtils.isHyperlink(node)) {
          return true;
        }
        node = node.parentNode;
      }
      return false;
    }
    this.execute = function(document) {
      var odtDocument = document, ownerDocument = odtDocument.getDOMDocument(), range = odtDocument.convertCursorToDomRange(position, length), boundaryNodes = domUtils.splitBoundaries(range), modifiedParagraphs = [], textNodes = odfUtils.getTextNodes(range, false);
      if (textNodes.length === 0) {
        return false;
      }
      textNodes.forEach(function(node) {
        var linkNode, paragraph = odfUtils.getParagraphElement(node);
        runtime.assert(isPartOfLink(node) === false, "The given range should not contain any link.");
        linkNode = createHyperlink(ownerDocument, hyperlink);
        node.parentNode.insertBefore(linkNode, node);
        linkNode.appendChild(node);
        if (modifiedParagraphs.indexOf(paragraph) === -1) {
          modifiedParagraphs.push(paragraph);
        }
      });
      boundaryNodes.forEach(domUtils.normalizeTextNodes);
      range.detach();
      odtDocument.fixCursorPositions();
      odtDocument.getOdfCanvas().refreshSize();
      odtDocument.getOdfCanvas().rerenderAnnotations();
      modifiedParagraphs.forEach(function(paragraph) {
        odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraph, memberId:memberid, timeStamp:timestamp});
      });
      return true;
    };
    this.spec = function() {
      return {optype:"ApplyHyperlink", memberid:memberid, timestamp:timestamp, position:position, length:length, hyperlink:hyperlink};
    };
  };
  ops.OpApplyHyperlink.Spec;
  ops.OpApplyHyperlink.InitSpec;
  ops.OpInsertImage = function OpInsertImage() {
    var memberid, timestamp, position, filename, frameWidth, frameHeight, frameStyleName, frameName, drawns = odf.Namespaces.drawns, svgns = odf.Namespaces.svgns, textns = odf.Namespaces.textns, xlinkns = odf.Namespaces.xlinkns, odfUtils = odf.OdfUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      filename = data.filename;
      frameWidth = data.frameWidth;
      frameHeight = data.frameHeight;
      frameStyleName = data.frameStyleName;
      frameName = data.frameName;
    };
    this.isEdit = true;
    this.group = undefined;
    function createFrameElement(document) {
      var imageNode = document.createElementNS(drawns, "draw:image"), frameNode = document.createElementNS(drawns, "draw:frame");
      imageNode.setAttributeNS(xlinkns, "xlink:href", filename);
      imageNode.setAttributeNS(xlinkns, "xlink:type", "simple");
      imageNode.setAttributeNS(xlinkns, "xlink:show", "embed");
      imageNode.setAttributeNS(xlinkns, "xlink:actuate", "onLoad");
      frameNode.setAttributeNS(drawns, "draw:style-name", frameStyleName);
      frameNode.setAttributeNS(drawns, "draw:name", frameName);
      frameNode.setAttributeNS(textns, "text:anchor-type", "as-char");
      frameNode.setAttributeNS(svgns, "svg:width", frameWidth);
      frameNode.setAttributeNS(svgns, "svg:height", frameHeight);
      frameNode.appendChild(imageNode);
      return frameNode;
    }
    this.execute = function(document) {
      var odtDocument = document, odfCanvas = odtDocument.getOdfCanvas(), domPosition = odtDocument.getTextNodeAtStep(position, memberid), textNode, refNode, paragraphElement, frameElement;
      if (!domPosition) {
        return false;
      }
      textNode = domPosition.textNode;
      paragraphElement = odfUtils.getParagraphElement(textNode);
      refNode = domPosition.offset !== textNode.length ? textNode.splitText(domPosition.offset) : textNode.nextSibling;
      frameElement = createFrameElement(odtDocument.getDOMDocument());
      textNode.parentNode.insertBefore(frameElement, refNode);
      odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position});
      if (textNode.length === 0) {
        textNode.parentNode.removeChild(textNode);
      }
      odfCanvas.addCssForFrameWithImage(frameElement);
      odfCanvas.refreshCSS();
      odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphElement, memberId:memberid, timeStamp:timestamp});
      odfCanvas.rerenderAnnotations();
      return true;
    };
    this.spec = function() {
      return {optype:"InsertImage", memberid:memberid, timestamp:timestamp, filename:filename, position:position, frameWidth:frameWidth, frameHeight:frameHeight, frameStyleName:frameStyleName, frameName:frameName};
    };
  };
  ops.OpInsertImage.Spec;
  ops.OpInsertImage.InitSpec;
  ops.OpInsertTable = function OpInsertTable() {
    var memberid, timestamp, initialRows, initialColumns, position, tableName, tableStyleName, tableColumnStyleName, tableCellStyleMatrix, tablens = "urn:oasis:names:tc:opendocument:xmlns:table:1.0", textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0", odfUtils = odf.OdfUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      initialRows = data.initialRows;
      initialColumns = data.initialColumns;
      tableName = data.tableName;
      tableStyleName = data.tableStyleName;
      tableColumnStyleName = data.tableColumnStyleName;
      tableCellStyleMatrix = data.tableCellStyleMatrix;
    };
    this.isEdit = true;
    this.group = undefined;
    function getCellStyleName(row, column) {
      var rowStyles;
      if (tableCellStyleMatrix.length === 1) {
        rowStyles = tableCellStyleMatrix[0];
      } else {
        if (tableCellStyleMatrix.length === 3) {
          switch(row) {
            case 0:
              rowStyles = tableCellStyleMatrix[0];
              break;
            case initialRows - 1:
              rowStyles = tableCellStyleMatrix[2];
              break;
            default:
              rowStyles = tableCellStyleMatrix[1];
              break;
          }
        } else {
          rowStyles = tableCellStyleMatrix[row];
        }
      }
      if (rowStyles.length === 1) {
        return rowStyles[0];
      }
      if (rowStyles.length === 3) {
        switch(column) {
          case 0:
            return rowStyles[0];
          case initialColumns - 1:
            return rowStyles[2];
          default:
            return rowStyles[1];
        }
      }
      return rowStyles[column];
    }
    function createTableNode(document) {
      var tableNode = document.createElementNS(tablens, "table:table"), columns = document.createElementNS(tablens, "table:table-column"), row, cell, paragraph, rowCounter, columnCounter, cellStyleName;
      if (tableStyleName) {
        tableNode.setAttributeNS(tablens, "table:style-name", tableStyleName);
      }
      if (tableName) {
        tableNode.setAttributeNS(tablens, "table:name", tableName);
      }
      columns.setAttributeNS(tablens, "table:number-columns-repeated", initialColumns);
      if (tableColumnStyleName) {
        columns.setAttributeNS(tablens, "table:style-name", tableColumnStyleName);
      }
      tableNode.appendChild(columns);
      for (rowCounter = 0;rowCounter < initialRows;rowCounter += 1) {
        row = document.createElementNS(tablens, "table:table-row");
        for (columnCounter = 0;columnCounter < initialColumns;columnCounter += 1) {
          cell = document.createElementNS(tablens, "table:table-cell");
          cellStyleName = getCellStyleName(rowCounter, columnCounter);
          if (cellStyleName) {
            cell.setAttributeNS(tablens, "table:style-name", cellStyleName);
          }
          paragraph = document.createElementNS(textns, "text:p");
          cell.appendChild(paragraph);
          row.appendChild(cell);
        }
        tableNode.appendChild(row);
      }
      return tableNode;
    }
    this.execute = function(document) {
      var odtDocument = document, domPosition = odtDocument.getTextNodeAtStep(position), rootNode = odtDocument.getRootNode(), previousSibling, tableNode;
      if (domPosition) {
        tableNode = createTableNode(odtDocument.getDOMDocument());
        previousSibling = odfUtils.getParagraphElement(domPosition.textNode);
        rootNode.insertBefore(tableNode, previousSibling.nextSibling);
        odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position});
        odtDocument.getOdfCanvas().refreshSize();
        odtDocument.emit(ops.OdtDocument.signalTableAdded, {tableElement:tableNode, memberId:memberid, timeStamp:timestamp});
        odtDocument.getOdfCanvas().rerenderAnnotations();
        return true;
      }
      return false;
    };
    this.spec = function() {
      return {optype:"InsertTable", memberid:memberid, timestamp:timestamp, position:position, initialRows:initialRows, initialColumns:initialColumns, tableName:tableName, tableStyleName:tableStyleName, tableColumnStyleName:tableColumnStyleName, tableCellStyleMatrix:tableCellStyleMatrix};
    };
  };
  ops.OpInsertTable.Spec;
  ops.OpInsertTable.InitSpec;
  ops.OpInsertText = function OpInsertText() {
    var tab = "\t", memberid, timestamp, position, moveCursor, text, odfUtils = odf.OdfUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      text = data.text;
      moveCursor = data.moveCursor === "true" || data.moveCursor === true;
    };
    this.isEdit = true;
    this.group = undefined;
    function triggerLayoutInWebkit(textNode) {
      var parent = textNode.parentNode, next = textNode.nextSibling;
      parent.removeChild(textNode);
      parent.insertBefore(textNode, next);
    }
    function isNonTabWhiteSpace(character) {
      return character !== tab && odfUtils.isODFWhitespace(character);
    }
    function requiresSpaceElement(text, index) {
      return isNonTabWhiteSpace(text[index]) && (index === 0 || index === text.length - 1 || isNonTabWhiteSpace(text[index - 1]));
    }
    this.execute = function(document) {
      var odtDocument = document, domPosition, previousNode, parentElement, nextNode = null, ownerDocument = odtDocument.getDOMDocument(), paragraphElement, textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0", toInsertIndex = 0, spaceElement, cursor = odtDocument.getCursor(memberid), i;
      function insertTextNode(toInsertText) {
        parentElement.insertBefore(ownerDocument.createTextNode(toInsertText), nextNode);
      }
      odtDocument.upgradeWhitespacesAtPosition(position);
      domPosition = odtDocument.getTextNodeAtStep(position);
      if (domPosition) {
        previousNode = domPosition.textNode;
        nextNode = previousNode.nextSibling;
        parentElement = previousNode.parentNode;
        paragraphElement = odfUtils.getParagraphElement(previousNode);
        for (i = 0;i < text.length;i += 1) {
          if (text[i] === tab || requiresSpaceElement(text, i)) {
            if (toInsertIndex === 0) {
              if (domPosition.offset !== previousNode.length) {
                nextNode = previousNode.splitText(domPosition.offset);
              }
              if (0 < i) {
                previousNode.appendData(text.substring(0, i));
              }
            } else {
              if (toInsertIndex < i) {
                insertTextNode(text.substring(toInsertIndex, i));
              }
            }
            toInsertIndex = i + 1;
            if (text[i] === tab) {
              spaceElement = ownerDocument.createElementNS(textns, "text:tab");
              spaceElement.appendChild(ownerDocument.createTextNode("\t"));
            } else {
              if (text[i] !== " ") {
                runtime.log("WARN: InsertText operation contains non-tab, non-space whitespace character (character code " + text.charCodeAt(i) + ")");
              }
              spaceElement = ownerDocument.createElementNS(textns, "text:s");
              spaceElement.appendChild(ownerDocument.createTextNode(" "));
            }
            parentElement.insertBefore(spaceElement, nextNode);
          }
        }
        if (toInsertIndex === 0) {
          previousNode.insertData(domPosition.offset, text);
        } else {
          if (toInsertIndex < text.length) {
            insertTextNode(text.substring(toInsertIndex));
          }
        }
        triggerLayoutInWebkit(previousNode);
        if (previousNode.length === 0) {
          previousNode.parentNode.removeChild(previousNode);
        }
        odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position});
        if (cursor && moveCursor) {
          odtDocument.moveCursor(memberid, position + text.length, 0);
          odtDocument.emit(ops.Document.signalCursorMoved, cursor);
        }
        odtDocument.downgradeWhitespacesAtPosition(position);
        odtDocument.downgradeWhitespacesAtPosition(position + text.length);
        odtDocument.getOdfCanvas().refreshSize();
        odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphElement, memberId:memberid, timeStamp:timestamp});
        odtDocument.getOdfCanvas().rerenderAnnotations();
        return true;
      }
      return false;
    };
    this.spec = function() {
      return {optype:"InsertText", memberid:memberid, timestamp:timestamp, position:position, text:text, moveCursor:moveCursor};
    };
  };
  ops.OpInsertText.Spec;
  ops.OpInsertText.InitSpec;
  odf.CollapsingRules = function CollapsingRules(rootNode) {
    var odfUtils = odf.OdfUtils, domUtils = core.DomUtils;
    function filterOdfNodesToRemove(node) {
      var isToRemove = odfUtils.isODFNode(node) || node.localName === "br" && odfUtils.isLineBreak(node.parentNode) || node.nodeType === Node.TEXT_NODE && odfUtils.isODFNode(node.parentNode);
      return isToRemove ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
    }
    function isCollapsibleContainer(node) {
      return !odfUtils.isParagraph(node) && node !== rootNode && odfUtils.hasNoODFContent(node);
    }
    function mergeChildrenIntoParent(targetNode) {
      var parent;
      if (targetNode.nodeType === Node.TEXT_NODE) {
        parent = targetNode.parentNode;
        parent.removeChild(targetNode);
      } else {
        parent = domUtils.removeUnwantedNodes(targetNode, filterOdfNodesToRemove);
      }
      if (parent && isCollapsibleContainer(parent)) {
        return mergeChildrenIntoParent(parent);
      }
      return parent;
    }
    this.mergeChildrenIntoParent = mergeChildrenIntoParent;
  };
  ops.OpMergeParagraph = function OpMergeParagraph() {
    var memberid, timestamp, moveCursor, paragraphStyleName, sourceStartPosition, destinationStartPosition, odfUtils = odf.OdfUtils, domUtils = core.DomUtils, textns = odf.Namespaces.textns;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      moveCursor = data.moveCursor;
      paragraphStyleName = data.paragraphStyleName;
      sourceStartPosition = parseInt(data.sourceStartPosition, 10);
      destinationStartPosition = parseInt(data.destinationStartPosition, 10);
    };
    this.isEdit = true;
    this.group = undefined;
    function filterEmptyGroupingElementToRemove(element) {
      if (odf.OdfUtils.isInlineRoot(element)) {
        return NodeFilter.FILTER_SKIP;
      }
      return odfUtils.isGroupingElement(element) && odfUtils.hasNoODFContent(element) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
    }
    function mergeParagraphs(destination, source) {
      var child;
      child = source.firstChild;
      while (child) {
        if (child.localName === "editinfo") {
          source.removeChild(child);
        } else {
          destination.appendChild(child);
          domUtils.removeUnwantedNodes(child, filterEmptyGroupingElementToRemove);
        }
        child = source.firstChild;
      }
    }
    function isInsignificantWhitespace(node) {
      var textNode, badNodeDescription;
      if (node.nodeType === Node.TEXT_NODE) {
        textNode = node;
        if (textNode.length === 0) {
          runtime.log("WARN: Empty text node found during merge operation");
          return true;
        }
        if (odfUtils.isODFWhitespace(textNode.data) && odfUtils.isSignificantWhitespace(textNode, 0) === false) {
          return true;
        }
        badNodeDescription = "#text";
      } else {
        badNodeDescription = (node.prefix ? node.prefix + ":" : "") + node.localName;
      }
      runtime.log("WARN: Unexpected text element found near paragraph boundary [" + badNodeDescription + "]");
      return false;
    }
    function removeTextNodes(range) {
      var emptyTextNodes;
      if (range.collapsed) {
        return;
      }
      domUtils.splitBoundaries(range);
      emptyTextNodes = odfUtils.getTextElements(range, false, true).filter(isInsignificantWhitespace);
      emptyTextNodes.forEach(function(node) {
        node.parentNode.removeChild(node);
      });
    }
    function trimLeadingInsignificantWhitespace(stepIterator, paragraphElement) {
      var range = paragraphElement.ownerDocument.createRange();
      stepIterator.setPosition(paragraphElement, 0);
      stepIterator.roundToNextStep();
      range.setStart(paragraphElement, 0);
      range.setEnd(stepIterator.container(), stepIterator.offset());
      removeTextNodes(range);
    }
    function trimTrailingInsignificantWhitespace(stepIterator, paragraphElement) {
      var range = paragraphElement.ownerDocument.createRange();
      stepIterator.setPosition(paragraphElement, paragraphElement.childNodes.length);
      stepIterator.roundToPreviousStep();
      range.setStart(stepIterator.container(), stepIterator.offset());
      range.setEnd(paragraphElement, paragraphElement.childNodes.length);
      removeTextNodes(range);
    }
    function getParagraphAtStep(odtDocument, steps, stepIterator) {
      var domPoint = odtDocument.convertCursorStepToDomPoint(steps), paragraph = odfUtils.getParagraphElement(domPoint.node, domPoint.offset);
      runtime.assert(Boolean(paragraph), "Paragraph not found at step " + steps);
      if (stepIterator) {
        stepIterator.setPosition(domPoint.node, domPoint.offset);
      }
      return paragraph;
    }
    this.execute = function(document) {
      var odtDocument = document, sourceParagraph, destinationParagraph, cursor = odtDocument.getCursor(memberid), rootNode = odtDocument.getRootNode(), collapseRules = new odf.CollapsingRules(rootNode), stepIterator = odtDocument.createStepIterator(rootNode, 0, [odtDocument.getPositionFilter()], rootNode), downgradeOffset;
      runtime.assert(destinationStartPosition < sourceStartPosition, "Destination paragraph (" + destinationStartPosition + ") must be " + "before source paragraph (" + sourceStartPosition + ")");
      destinationParagraph = getParagraphAtStep(odtDocument, destinationStartPosition);
      sourceParagraph = getParagraphAtStep(odtDocument, sourceStartPosition, stepIterator);
      stepIterator.previousStep();
      runtime.assert(domUtils.containsNode(destinationParagraph, stepIterator.container()), "Destination paragraph must be adjacent to the source paragraph");
      trimTrailingInsignificantWhitespace(stepIterator, destinationParagraph);
      downgradeOffset = destinationParagraph.childNodes.length;
      trimLeadingInsignificantWhitespace(stepIterator, sourceParagraph);
      mergeParagraphs(destinationParagraph, sourceParagraph);
      runtime.assert(sourceParagraph.childNodes.length === 0, "Source paragraph should be empty before it is removed");
      collapseRules.mergeChildrenIntoParent(sourceParagraph);
      odtDocument.emit(ops.OdtDocument.signalStepsRemoved, {position:sourceStartPosition - 1});
      stepIterator.setPosition(destinationParagraph, downgradeOffset);
      stepIterator.roundToClosestStep();
      if (!stepIterator.previousStep()) {
        stepIterator.roundToNextStep();
      }
      odtDocument.downgradeWhitespaces(stepIterator);
      if (paragraphStyleName) {
        destinationParagraph.setAttributeNS(textns, "text:style-name", paragraphStyleName);
      } else {
        destinationParagraph.removeAttributeNS(textns, "style-name");
      }
      if (cursor && moveCursor) {
        odtDocument.moveCursor(memberid, sourceStartPosition - 1, 0);
        odtDocument.emit(ops.Document.signalCursorMoved, cursor);
      }
      odtDocument.fixCursorPositions();
      odtDocument.getOdfCanvas().refreshSize();
      odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:destinationParagraph, memberId:memberid, timeStamp:timestamp});
      odtDocument.getOdfCanvas().rerenderAnnotations();
      return true;
    };
    this.spec = function() {
      return {optype:"MergeParagraph", memberid:memberid, timestamp:timestamp, moveCursor:moveCursor, paragraphStyleName:paragraphStyleName, sourceStartPosition:sourceStartPosition, destinationStartPosition:destinationStartPosition};
    };
  };
  ops.OpMergeParagraph.Spec;
  ops.OpMergeParagraph.InitSpec;
  ops.OpMoveCursor = function OpMoveCursor() {
    var memberid, timestamp, position, length, selectionType;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      length = data.length || 0;
      selectionType = data.selectionType || ops.OdtCursor.RangeSelection;
    };
    this.isEdit = false;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, cursor = odtDocument.getCursor(memberid), selectedRange;
      if (!cursor) {
        return false;
      }
      selectedRange = odtDocument.convertCursorToDomRange(position, length);
      cursor.setSelectedRange(selectedRange, length >= 0);
      cursor.setSelectionType(selectionType);
      odtDocument.emit(ops.Document.signalCursorMoved, cursor);
      return true;
    };
    this.spec = function() {
      return {optype:"MoveCursor", memberid:memberid, timestamp:timestamp, position:position, length:length, selectionType:selectionType};
    };
  };
  ops.OpMoveCursor.Spec;
  ops.OpMoveCursor.InitSpec;
  ops.OpRemoveAnnotation = function OpRemoveAnnotation() {
    var memberid, timestamp, position, length, domUtils = core.DomUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = parseInt(data.position, 10);
      length = parseInt(data.length, 10);
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, iterator = odtDocument.getIteratorAtPosition(position), container = iterator.container(), annotationNode, annotationEnd;
      while (!(container.namespaceURI === odf.Namespaces.officens && container.localName === "annotation")) {
        container = container.parentNode;
      }
      if (container === null) {
        return false;
      }
      annotationNode = container;
      annotationEnd = annotationNode.annotationEndElement;
      odtDocument.getOdfCanvas().forgetAnnotation(annotationNode);
      function insert(node) {
        annotationNode.parentNode.insertBefore(node, annotationNode);
      }
      domUtils.getElementsByTagNameNS(annotationNode, "urn:webodf:names:cursor", "cursor").forEach(insert);
      domUtils.getElementsByTagNameNS(annotationNode, "urn:webodf:names:cursor", "anchor").forEach(insert);
      annotationNode.parentNode.removeChild(annotationNode);
      if (annotationEnd) {
        annotationEnd.parentNode.removeChild(annotationEnd);
      }
      odtDocument.emit(ops.OdtDocument.signalStepsRemoved, {position:position > 0 ? position - 1 : position});
      odtDocument.getOdfCanvas().rerenderAnnotations();
      odtDocument.fixCursorPositions();
      return true;
    };
    this.spec = function() {
      return {optype:"RemoveAnnotation", memberid:memberid, timestamp:timestamp, position:position, length:length};
    };
  };
  ops.OpRemoveAnnotation.Spec;
  ops.OpRemoveAnnotation.InitSpec;
  ops.OpRemoveBlob = function OpRemoveBlob() {
    var memberid, timestamp, filename;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      filename = data.filename;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document;
      odtDocument.getOdfCanvas().odfContainer().removeBlob(filename);
      return true;
    };
    this.spec = function() {
      return {optype:"RemoveBlob", memberid:memberid, timestamp:timestamp, filename:filename};
    };
  };
  ops.OpRemoveBlob.Spec;
  ops.OpRemoveBlob.InitSpec;
  ops.OpRemoveCursor = function OpRemoveCursor() {
    var memberid, timestamp;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
    };
    this.isEdit = false;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document;
      if (!odtDocument.removeCursor(memberid)) {
        return false;
      }
      return true;
    };
    this.spec = function() {
      return {optype:"RemoveCursor", memberid:memberid, timestamp:timestamp};
    };
  };
  ops.OpRemoveCursor.Spec;
  ops.OpRemoveCursor.InitSpec;
  ops.OpRemoveHyperlink = function OpRemoveHyperlink() {
    var memberid, timestamp, position, length, domUtils = core.DomUtils, odfUtils = odf.OdfUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      length = data.length;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, range = odtDocument.convertCursorToDomRange(position, length), links = odfUtils.getHyperlinkElements(range), node;
      runtime.assert(links.length === 1, "The given range should only contain a single link.");
      node = domUtils.mergeIntoParent(links[0]);
      range.detach();
      odtDocument.fixCursorPositions();
      odtDocument.getOdfCanvas().refreshSize();
      odtDocument.getOdfCanvas().rerenderAnnotations();
      odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:odfUtils.getParagraphElement(node), memberId:memberid, timeStamp:timestamp});
      return true;
    };
    this.spec = function() {
      return {optype:"RemoveHyperlink", memberid:memberid, timestamp:timestamp, position:position, length:length};
    };
  };
  ops.OpRemoveHyperlink.Spec;
  ops.OpRemoveHyperlink.InitSpec;
  ops.OpRemoveMember = function OpRemoveMember() {
    var memberid, timestamp;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = parseInt(data.timestamp, 10);
    };
    this.isEdit = false;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document;
      if (!odtDocument.getMember(memberid)) {
        return false;
      }
      odtDocument.removeMember(memberid);
      odtDocument.emit(ops.Document.signalMemberRemoved, memberid);
      return true;
    };
    this.spec = function() {
      return {optype:"RemoveMember", memberid:memberid, timestamp:timestamp};
    };
  };
  ops.OpRemoveMember.Spec;
  ops.OpRemoveMember.InitSpec;
  ops.OpRemoveStyle = function OpRemoveStyle() {
    var memberid, timestamp, styleName, styleFamily;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      styleName = data.styleName;
      styleFamily = data.styleFamily;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, styleNode = odtDocument.getFormatting().getStyleElement(styleName, styleFamily);
      if (!styleNode) {
        return false;
      }
      styleNode.parentNode.removeChild(styleNode);
      odtDocument.getOdfCanvas().refreshCSS();
      odtDocument.emit(ops.OdtDocument.signalCommonStyleDeleted, {name:styleName, family:styleFamily});
      return true;
    };
    this.spec = function() {
      return {optype:"RemoveStyle", memberid:memberid, timestamp:timestamp, styleName:styleName, styleFamily:styleFamily};
    };
  };
  ops.OpRemoveStyle.Spec;
  ops.OpRemoveStyle.InitSpec;
  ops.OpRemoveText = function OpRemoveText() {
    var memberid, timestamp, position, length, odfUtils = odf.OdfUtils, domUtils = core.DomUtils;
    this.init = function(data) {
      runtime.assert(data.length >= 0, "OpRemoveText only supports positive lengths");
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = parseInt(data.position, 10);
      length = parseInt(data.length, 10);
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, range, textNodes, paragraph, cursor = odtDocument.getCursor(memberid), collapseRules = new odf.CollapsingRules(odtDocument.getRootNode());
      odtDocument.upgradeWhitespacesAtPosition(position);
      odtDocument.upgradeWhitespacesAtPosition(position + length);
      range = odtDocument.convertCursorToDomRange(position, length);
      domUtils.splitBoundaries(range);
      textNodes = odfUtils.getTextElements(range, false, true);
      paragraph = odfUtils.getParagraphElement(range.startContainer, range.startOffset);
      runtime.assert(paragraph !== undefined, "Attempting to remove text outside a paragraph element");
      range.detach();
      textNodes.forEach(function(element) {
        if (element.parentNode) {
          runtime.assert(domUtils.containsNode(paragraph, element), "RemoveText only supports removing elements within the same paragraph");
          collapseRules.mergeChildrenIntoParent(element);
        } else {
          runtime.log("WARN: text element has already been removed from it's container");
        }
      });
      odtDocument.emit(ops.OdtDocument.signalStepsRemoved, {position:position});
      odtDocument.downgradeWhitespacesAtPosition(position);
      odtDocument.fixCursorPositions();
      odtDocument.getOdfCanvas().refreshSize();
      odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraph, memberId:memberid, timeStamp:timestamp});
      if (cursor) {
        cursor.resetSelectionType();
        odtDocument.emit(ops.Document.signalCursorMoved, cursor);
      }
      odtDocument.getOdfCanvas().rerenderAnnotations();
      return true;
    };
    this.spec = function() {
      return {optype:"RemoveText", memberid:memberid, timestamp:timestamp, position:position, length:length};
    };
  };
  ops.OpRemoveText.Spec;
  ops.OpRemoveText.InitSpec;
  ops.OpSetBlob = function OpSetBlob() {
    var memberid, timestamp, filename, mimetype, content;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      filename = data.filename;
      mimetype = data.mimetype;
      content = data.content;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document;
      odtDocument.getOdfCanvas().odfContainer().setBlob(filename, mimetype, content);
      return true;
    };
    this.spec = function() {
      return {optype:"SetBlob", memberid:memberid, timestamp:timestamp, filename:filename, mimetype:mimetype, content:content};
    };
  };
  ops.OpSetBlob.Spec;
  ops.OpSetBlob.InitSpec;
  ops.OpSetParagraphStyle = function OpSetParagraphStyle() {
    var memberid, timestamp, position, styleName, textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0", odfUtils = odf.OdfUtils;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      styleName = data.styleName;
    };
    this.isEdit = true;
    this.group = undefined;
    function isFirstStep(odtDocument, paragraphNode, iterator) {
      var filters = [odtDocument.getPositionFilter()], container = iterator.container(), offset = iterator.unfilteredDomOffset(), stepIterator = odtDocument.createStepIterator(container, offset, filters, paragraphNode);
      return stepIterator.previousStep() === false;
    }
    this.execute = function(document) {
      var odtDocument = document, iterator, paragraphNode;
      iterator = odtDocument.getIteratorAtPosition(position);
      paragraphNode = odfUtils.getParagraphElement(iterator.container());
      if (paragraphNode) {
        runtime.assert(isFirstStep(odtDocument, paragraphNode, iterator), "SetParagraphStyle position should be the first position in the paragraph");
        if (styleName) {
          paragraphNode.setAttributeNS(textns, "text:style-name", styleName);
        } else {
          paragraphNode.removeAttributeNS(textns, "style-name");
        }
        odtDocument.getOdfCanvas().refreshSize();
        odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphNode, timeStamp:timestamp, memberId:memberid});
        odtDocument.getOdfCanvas().rerenderAnnotations();
        return true;
      }
      return false;
    };
    this.spec = function() {
      return {optype:"SetParagraphStyle", memberid:memberid, timestamp:timestamp, position:position, styleName:styleName};
    };
  };
  ops.OpSetParagraphStyle.Spec;
  ops.OpSetParagraphStyle.InitSpec;
  ops.OpSplitParagraph = function OpSplitParagraph() {
    var memberid, timestamp, sourceParagraphPosition, position, moveCursor, paragraphStyleName, odfUtils = odf.OdfUtils, textns = odf.Namespaces.textns;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      position = data.position;
      sourceParagraphPosition = data.sourceParagraphPosition;
      paragraphStyleName = data.paragraphStyleName;
      moveCursor = data.moveCursor === "true" || data.moveCursor === true;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, domPosition, paragraphNode, targetNode, node, splitNode, splitChildNode, keptChildNode, cursor = odtDocument.getCursor(memberid);
      odtDocument.upgradeWhitespacesAtPosition(position);
      domPosition = odtDocument.getTextNodeAtStep(position);
      if (!domPosition) {
        return false;
      }
      paragraphNode = odfUtils.getParagraphElement(domPosition.textNode);
      if (!paragraphNode) {
        return false;
      }
      if (odfUtils.isListItem(paragraphNode.parentNode)) {
        targetNode = paragraphNode.parentNode;
      } else {
        targetNode = paragraphNode;
      }
      if (domPosition.offset === 0) {
        keptChildNode = domPosition.textNode.previousSibling;
        splitChildNode = null;
      } else {
        keptChildNode = domPosition.textNode;
        if (domPosition.offset >= domPosition.textNode.length) {
          splitChildNode = null;
        } else {
          splitChildNode = domPosition.textNode.splitText(domPosition.offset);
        }
      }
      node = domPosition.textNode;
      while (node !== targetNode) {
        node = node.parentNode;
        splitNode = node.cloneNode(false);
        if (splitChildNode) {
          splitNode.appendChild(splitChildNode);
        }
        if (keptChildNode) {
          while (keptChildNode && keptChildNode.nextSibling) {
            splitNode.appendChild(keptChildNode.nextSibling);
          }
        } else {
          while (node.firstChild) {
            splitNode.appendChild(node.firstChild);
          }
        }
        node.parentNode.insertBefore(splitNode, node.nextSibling);
        keptChildNode = node;
        splitChildNode = splitNode;
      }
      if (odfUtils.isListItem(splitChildNode)) {
        splitChildNode = splitChildNode.childNodes.item(0);
      }
      if (paragraphStyleName) {
        splitChildNode.setAttributeNS(textns, "text:style-name", paragraphStyleName);
      } else {
        splitChildNode.removeAttributeNS(textns, "style-name");
      }
      if (domPosition.textNode.length === 0) {
        domPosition.textNode.parentNode.removeChild(domPosition.textNode);
      }
      odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position});
      if (cursor && moveCursor) {
        odtDocument.moveCursor(memberid, position + 1, 0);
        odtDocument.emit(ops.Document.signalCursorMoved, cursor);
      }
      odtDocument.fixCursorPositions();
      odtDocument.getOdfCanvas().refreshSize();
      odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphNode, memberId:memberid, timeStamp:timestamp});
      odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:splitChildNode, memberId:memberid, timeStamp:timestamp});
      odtDocument.getOdfCanvas().rerenderAnnotations();
      return true;
    };
    this.spec = function() {
      return {optype:"SplitParagraph", memberid:memberid, timestamp:timestamp, position:position, sourceParagraphPosition:sourceParagraphPosition, paragraphStyleName:paragraphStyleName, moveCursor:moveCursor};
    };
  };
  ops.OpSplitParagraph.Spec;
  ops.OpSplitParagraph.InitSpec;
  ops.OpUpdateMember = function OpUpdateMember() {
    var memberid, timestamp, setProperties, removedProperties;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = parseInt(data.timestamp, 10);
      setProperties = data.setProperties;
      removedProperties = data.removedProperties;
    };
    this.isEdit = false;
    this.group = undefined;
    function updateCreators(doc) {
      var xpath = xmldom.XPath, xp = "//dc:creator[@editinfo:memberid='" + memberid + "']", creators = xpath.getODFElementsWithXPath(doc.getRootNode(), xp, function(prefix) {
        if (prefix === "editinfo") {
          return "urn:webodf:names:editinfo";
        }
        return odf.Namespaces.lookupNamespaceURI(prefix);
      }), i;
      for (i = 0;i < creators.length;i += 1) {
        creators[i].textContent = setProperties.fullName;
      }
    }
    this.execute = function(document) {
      var odtDocument = document, member = odtDocument.getMember(memberid);
      if (!member) {
        return false;
      }
      if (removedProperties) {
        member.removeProperties(removedProperties);
      }
      if (setProperties) {
        member.setProperties(setProperties);
        if (setProperties.fullName) {
          updateCreators(odtDocument);
        }
      }
      odtDocument.emit(ops.Document.signalMemberUpdated, member);
      return true;
    };
    this.spec = function() {
      return {optype:"UpdateMember", memberid:memberid, timestamp:timestamp, setProperties:setProperties, removedProperties:removedProperties};
    };
  };
  ops.OpUpdateMember.Spec;
  ops.OpUpdateMember.InitSpec;
  ops.OpUpdateMetadata = function OpUpdateMetadata() {
    var memberid, timestamp, setProperties, removedProperties;
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = parseInt(data.timestamp, 10);
      setProperties = data.setProperties;
      removedProperties = data.removedProperties;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, odfContainer = odtDocument.getOdfCanvas().odfContainer(), removedPropertiesArray = null;
      if (removedProperties) {
        removedPropertiesArray = removedProperties.attributes.split(",");
      }
      odfContainer.setMetadata(setProperties, removedPropertiesArray);
      odtDocument.emit(ops.OdtDocument.signalMetadataUpdated, {setProperties:setProperties !== null ? setProperties : {}, removedProperties:removedPropertiesArray !== null ? removedPropertiesArray : []});
      return true;
    };
    this.spec = function() {
      return {optype:"UpdateMetadata", memberid:memberid, timestamp:timestamp, setProperties:setProperties, removedProperties:removedProperties};
    };
  };
  ops.OpUpdateMetadata.Spec;
  ops.OpUpdateMetadata.InitSpec;
  ops.OpUpdateParagraphStyle = function OpUpdateParagraphStyle() {
    var memberid, timestamp, styleName, setProperties, removedProperties, paragraphPropertiesName = "style:paragraph-properties", textPropertiesName = "style:text-properties", stylens = odf.Namespaces.stylens;
    function removedAttributesFromStyleNode(node, removedAttributeNames) {
      var i, attributeNameParts, attributeNameList = removedAttributeNames ? removedAttributeNames.split(",") : [];
      for (i = 0;i < attributeNameList.length;i += 1) {
        attributeNameParts = attributeNameList[i].split(":");
        node.removeAttributeNS(odf.Namespaces.lookupNamespaceURI(attributeNameParts[0]), attributeNameParts[1]);
      }
    }
    this.init = function(data) {
      memberid = data.memberid;
      timestamp = data.timestamp;
      styleName = data.styleName;
      setProperties = data.setProperties;
      removedProperties = data.removedProperties;
    };
    this.isEdit = true;
    this.group = undefined;
    this.execute = function(document) {
      var odtDocument = document, formatting = odtDocument.getFormatting(), styleNode, object, paragraphPropertiesNode, textPropertiesNode;
      if (styleName !== "") {
        styleNode = formatting.getStyleElement(styleName, "paragraph");
      } else {
        styleNode = formatting.getDefaultStyleElement("paragraph");
      }
      if (styleNode) {
        paragraphPropertiesNode = styleNode.getElementsByTagNameNS(stylens, "paragraph-properties").item(0);
        textPropertiesNode = styleNode.getElementsByTagNameNS(stylens, "text-properties").item(0);
        if (setProperties) {
          formatting.updateStyle(styleNode, setProperties);
        }
        if (removedProperties) {
          object = removedProperties[paragraphPropertiesName];
          if (paragraphPropertiesNode && object) {
            removedAttributesFromStyleNode(paragraphPropertiesNode, object.attributes);
            if (paragraphPropertiesNode.attributes.length === 0) {
              styleNode.removeChild(paragraphPropertiesNode);
            }
          }
          object = removedProperties[textPropertiesName];
          if (textPropertiesNode && object) {
            removedAttributesFromStyleNode(textPropertiesNode, object.attributes);
            if (textPropertiesNode.attributes.length === 0) {
              styleNode.removeChild(textPropertiesNode);
            }
          }
          removedAttributesFromStyleNode(styleNode, removedProperties.attributes);
        }
        odtDocument.getOdfCanvas().refreshCSS();
        odtDocument.emit(ops.OdtDocument.signalParagraphStyleModified, styleName);
        odtDocument.getOdfCanvas().rerenderAnnotations();
        return true;
      }
      return false;
    };
    this.spec = function() {
      return {optype:"UpdateParagraphStyle", memberid:memberid, timestamp:timestamp, styleName:styleName, setProperties:setProperties, removedProperties:removedProperties};
    };
  };
  ops.OpUpdateParagraphStyle.Spec;
  ops.OpUpdateParagraphStyle.InitSpec;
  ops.OperationFactory = function OperationFactory() {
    var specs;
    function construct(Constructor) {
      return function(spec) {
        return new Constructor;
      };
    }
    this.register = function(specName, specConstructor) {
      specs[specName] = specConstructor;
    };
    this.create = function(spec) {
      var op = null, constructor = specs[spec.optype];
      if (constructor) {
        op = constructor(spec);
        op.init(spec);
      }
      return op;
    };
    function init() {
      specs = {AddMember:construct(ops.OpAddMember), UpdateMember:construct(ops.OpUpdateMember), RemoveMember:construct(ops.OpRemoveMember), AddCursor:construct(ops.OpAddCursor), ApplyDirectStyling:construct(ops.OpApplyDirectStyling), SetBlob:construct(ops.OpSetBlob), RemoveBlob:construct(ops.OpRemoveBlob), InsertImage:construct(ops.OpInsertImage), InsertTable:construct(ops.OpInsertTable), InsertText:construct(ops.OpInsertText), RemoveText:construct(ops.OpRemoveText), MergeParagraph:construct(ops.OpMergeParagraph),
      SplitParagraph:construct(ops.OpSplitParagraph), SetParagraphStyle:construct(ops.OpSetParagraphStyle), UpdateParagraphStyle:construct(ops.OpUpdateParagraphStyle), AddStyle:construct(ops.OpAddStyle), RemoveStyle:construct(ops.OpRemoveStyle), MoveCursor:construct(ops.OpMoveCursor), RemoveCursor:construct(ops.OpRemoveCursor), AddAnnotation:construct(ops.OpAddAnnotation), RemoveAnnotation:construct(ops.OpRemoveAnnotation), UpdateMetadata:construct(ops.OpUpdateMetadata), ApplyHyperlink:construct(ops.OpApplyHyperlink),
      RemoveHyperlink:construct(ops.OpRemoveHyperlink)};
    }
    init();
  };
  ops.OperationFactory.SpecConstructor;
  ops.OperationRouter = function OperationRouter() {
  };
  ops.OperationRouter.prototype.setOperationFactory = function(f) {
  };
  ops.OperationRouter.prototype.setPlaybackFunction = function(playback_func) {
  };
  ops.OperationRouter.prototype.push = function(operations) {
  };
  ops.OperationRouter.prototype.close = function(callback) {
  };
  ops.OperationRouter.prototype.subscribe = function(eventId, cb) {
  };
  ops.OperationRouter.prototype.unsubscribe = function(eventId, cb) {
  };
  ops.OperationRouter.prototype.hasLocalUnsyncedOps = function() {
  };
  ops.OperationRouter.prototype.hasSessionHostConnection = function() {
  };
  ops.OperationRouter.signalProcessingBatchStart = "router/batchstart";
  ops.OperationRouter.signalProcessingBatchEnd = "router/batchend";
  ops.TrivialOperationRouter = function TrivialOperationRouter() {
    var events = new core.EventNotifier([ops.OperationRouter.signalProcessingBatchStart, ops.OperationRouter.signalProcessingBatchEnd]), operationFactory, playbackFunction, groupIdentifier = 0;
    this.setOperationFactory = function(f) {
      operationFactory = f;
    };
    this.setPlaybackFunction = function(playback_func) {
      playbackFunction = playback_func;
    };
    this.push = function(operations) {
      groupIdentifier += 1;
      events.emit(ops.OperationRouter.signalProcessingBatchStart, {});
      operations.forEach(function(op) {
        var timedOp, opspec = op.spec();
        opspec.timestamp = Date.now();
        timedOp = operationFactory.create(opspec);
        timedOp.group = "g" + groupIdentifier;
        playbackFunction(timedOp);
      });
      events.emit(ops.OperationRouter.signalProcessingBatchEnd, {});
    };
    this.close = function(cb) {
      cb();
    };
    this.subscribe = function(eventId, cb) {
      events.subscribe(eventId, cb);
    };
    this.unsubscribe = function(eventId, cb) {
      events.unsubscribe(eventId, cb);
    };
    this.hasLocalUnsyncedOps = function() {
      return false;
    };
    this.hasSessionHostConnection = function() {
      return true;
    };
  };
  ops.Session = function Session(odfCanvas) {
    var self = this, operationFactory = new ops.OperationFactory, odtDocument = new ops.OdtDocument(odfCanvas), operationRouter = null;
    function forwardBatchStart(args) {
      odtDocument.emit(ops.OdtDocument.signalProcessingBatchStart, args);
    }
    function forwardBatchEnd(args) {
      odtDocument.emit(ops.OdtDocument.signalProcessingBatchEnd, args);
    }
    this.setOperationFactory = function(opFactory) {
      operationFactory = opFactory;
      if (operationRouter) {
        operationRouter.setOperationFactory(operationFactory);
      }
    };
    this.setOperationRouter = function(opRouter) {
      if (operationRouter) {
        operationRouter.unsubscribe(ops.OperationRouter.signalProcessingBatchStart, forwardBatchStart);
        operationRouter.unsubscribe(ops.OperationRouter.signalProcessingBatchEnd, forwardBatchEnd);
      }
      operationRouter = opRouter;
      operationRouter.subscribe(ops.OperationRouter.signalProcessingBatchStart, forwardBatchStart);
      operationRouter.subscribe(ops.OperationRouter.signalProcessingBatchEnd, forwardBatchEnd);
      opRouter.setPlaybackFunction(function(op) {
        odtDocument.emit(ops.OdtDocument.signalOperationStart, op);
        if (op.execute(odtDocument)) {
          odtDocument.emit(ops.OdtDocument.signalOperationEnd, op);
          return true;
        }
        return false;
      });
      opRouter.setOperationFactory(operationFactory);
    };
    this.getOperationFactory = function() {
      return operationFactory;
    };
    this.getOdtDocument = function() {
      return odtDocument;
    };
    this.enqueue = function(ops) {
      operationRouter.push(ops);
    };
    this.close = function(callback) {
      operationRouter.close(function(err) {
        if (err) {
          callback(err);
        } else {
          odtDocument.close(callback);
        }
      });
    };
    this.destroy = function(callback) {
      odtDocument.destroy(callback);
    };
    function init() {
      self.setOperationRouter(new ops.TrivialOperationRouter);
    }
    init();
  };
  gui.AnnotationController = function AnnotationController(session, sessionConstraints, inputMemberId) {
    var odtDocument = session.getOdtDocument(), isAnnotatable = false, eventNotifier = new core.EventNotifier([gui.AnnotationController.annotatableChanged]), odfUtils = odf.OdfUtils, NEXT = core.StepDirection.NEXT;
    function updatedCachedValues() {
      var cursor = odtDocument.getCursor(inputMemberId), cursorNode = cursor && cursor.getNode(), newIsAnnotatable = false;
      if (cursorNode) {
        newIsAnnotatable = !odfUtils.isWithinAnnotation(cursorNode, odtDocument.getRootNode());
      }
      if (newIsAnnotatable !== isAnnotatable) {
        isAnnotatable = newIsAnnotatable;
        eventNotifier.emit(gui.AnnotationController.annotatableChanged, isAnnotatable);
      }
    }
    function onCursorAdded(cursor) {
      if (cursor.getMemberId() === inputMemberId) {
        updatedCachedValues();
      }
    }
    function onCursorRemoved(memberId) {
      if (memberId === inputMemberId) {
        updatedCachedValues();
      }
    }
    function onCursorMoved(cursor) {
      if (cursor.getMemberId() === inputMemberId) {
        updatedCachedValues();
      }
    }
    this.isAnnotatable = function() {
      return isAnnotatable;
    };
    this.addAnnotation = function() {
      var op = new ops.OpAddAnnotation, selection = odtDocument.getCursorSelection(inputMemberId), length = selection.length, position = selection.position;
      if (!isAnnotatable) {
        return;
      }
      if (length === 0) {
        length = undefined;
      } else {
        position = length >= 0 ? position : position + length;
        length = Math.abs(length);
      }
      op.init({memberid:inputMemberId, position:position, length:length, name:inputMemberId + Date.now()});
      session.enqueue([op]);
    };
    this.removeAnnotation = function(annotationNode) {
      var startStep, endStep, op, moveCursor, currentUserName = odtDocument.getMember(inputMemberId).getProperties().fullName;
      if (sessionConstraints.getState(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN) === true) {
        if (currentUserName !== odfUtils.getAnnotationCreator(annotationNode)) {
          return;
        }
      }
      startStep = odtDocument.convertDomPointToCursorStep(annotationNode, 0, NEXT);
      endStep = odtDocument.convertDomPointToCursorStep(annotationNode, annotationNode.childNodes.length);
      op = new ops.OpRemoveAnnotation;
      op.init({memberid:inputMemberId, position:startStep, length:endStep - startStep});
      moveCursor = new ops.OpMoveCursor;
      moveCursor.init({memberid:inputMemberId, position:startStep > 0 ? startStep - 1 : startStep, length:0});
      session.enqueue([op, moveCursor]);
    };
    this.subscribe = function(eventid, cb) {
      eventNotifier.subscribe(eventid, cb);
    };
    this.unsubscribe = function(eventid, cb) {
      eventNotifier.unsubscribe(eventid, cb);
    };
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorAdded);
      odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
      odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorMoved);
      callback();
    };
    function init() {
      sessionConstraints.registerConstraint(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN);
      odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorAdded);
      odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
      odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorMoved);
      updatedCachedValues();
    }
    init();
  };
  gui.AnnotationController.annotatableChanged = "annotatable/changed";
  gui.Avatar = function Avatar(parentElement, avatarInitiallyVisible) {
    var self = this, handle, image, pendingImageUrl, displayShown = "block", displayHidden = "none";
    this.setColor = function(color) {
      image.style.borderColor = color;
    };
    this.setImageUrl = function(url) {
      if (self.isVisible()) {
        image.src = url;
      } else {
        pendingImageUrl = url;
      }
    };
    this.isVisible = function() {
      return handle.style.display === displayShown;
    };
    this.show = function() {
      if (pendingImageUrl) {
        image.src = pendingImageUrl;
        pendingImageUrl = undefined;
      }
      handle.style.display = displayShown;
    };
    this.hide = function() {
      handle.style.display = displayHidden;
    };
    this.markAsFocussed = function(isFocussed) {
      if (isFocussed) {
        handle.classList.add("active");
      } else {
        handle.classList.remove("active");
      }
    };
    this.destroy = function(callback) {
      parentElement.removeChild(handle);
      callback();
    };
    function init() {
      var document = parentElement.ownerDocument;
      handle = document.createElement("div");
      image = document.createElement("img");
      handle.appendChild(image);
      handle.style.display = avatarInitiallyVisible ? displayShown : displayHidden;
      handle.className = "handle";
      parentElement.appendChild(handle);
    }
    init();
  };
  gui.StepInfo = function() {
  };
  gui.StepInfo.VisualDirection = {LEFT_TO_RIGHT:0, RIGHT_TO_LEFT:1};
  gui.StepInfo.prototype.token;
  gui.StepInfo.prototype.container = function() {
  };
  gui.StepInfo.prototype.offset = function() {
  };
  gui.StepInfo.prototype.direction;
  gui.StepInfo.prototype.visualDirection;
  gui.VisualStepScanner = function() {
  };
  gui.VisualStepScanner.prototype.token;
  gui.VisualStepScanner.prototype.process = function(stepInfo, previousRect, nextRect) {
  };
  gui.GuiStepUtils = function GuiStepUtils() {
    var odfUtils = odf.OdfUtils, stepUtils = new odf.StepUtils, domUtils = core.DomUtils, NEXT = core.StepDirection.NEXT, LEFT_TO_RIGHT = gui.StepInfo.VisualDirection.LEFT_TO_RIGHT, RIGHT_TO_LEFT = gui.StepInfo.VisualDirection.RIGHT_TO_LEFT;
    function getContentRect(stepIterator) {
      var bounds = stepUtils.getContentBounds(stepIterator), range, rect = null;
      if (bounds) {
        if (bounds.container.nodeType === Node.TEXT_NODE) {
          range = bounds.container.ownerDocument.createRange();
          range.setStart(bounds.container, bounds.startOffset);
          range.setEnd(bounds.container, bounds.endOffset);
          rect = range.getClientRects().length > 0 ? range.getBoundingClientRect() : null;
          if (rect && bounds.container.data.substring(bounds.startOffset, bounds.endOffset) === " " && rect.width <= 1) {
            rect = null;
          }
          range.detach();
        } else {
          if (odfUtils.isCharacterElement(bounds.container) || odfUtils.isCharacterFrame(bounds.container)) {
            rect = domUtils.getBoundingClientRect(bounds.container);
          }
        }
      }
      return rect;
    }
    this.getContentRect = getContentRect;
    function moveToFilteredStep(stepIterator, direction, scanners) {
      var isForward = direction === NEXT, leftRect, rightRect, previousRect, nextRect, destinationToken, initialToken = stepIterator.snapshot(), wasTerminated = false, stepInfo;
      function process(terminated, scanner) {
        if (scanner.process(stepInfo, previousRect, nextRect)) {
          terminated = true;
          if (!destinationToken && scanner.token) {
            destinationToken = scanner.token;
          }
        }
        return terminated;
      }
      do {
        leftRect = getContentRect(stepIterator);
        stepInfo = {token:stepIterator.snapshot(), container:stepIterator.container, offset:stepIterator.offset, direction:direction, visualDirection:direction === NEXT ? LEFT_TO_RIGHT : RIGHT_TO_LEFT};
        if (stepIterator.nextStep()) {
          rightRect = getContentRect(stepIterator);
        } else {
          rightRect = null;
        }
        stepIterator.restore(stepInfo.token);
        if (isForward) {
          previousRect = leftRect;
          nextRect = rightRect;
        } else {
          previousRect = rightRect;
          nextRect = leftRect;
        }
        wasTerminated = scanners.reduce(process, false);
      } while (!wasTerminated && stepIterator.advanceStep(direction));
      if (!wasTerminated) {
        scanners.forEach(function(scanner) {
          if (!destinationToken && scanner.token) {
            destinationToken = scanner.token;
          }
        });
      }
      stepIterator.restore(destinationToken || initialToken);
      return Boolean(destinationToken);
    }
    this.moveToFilteredStep = moveToFilteredStep;
  };
  gui.Caret = function Caret(cursor, viewport, avatarInitiallyVisible, blinkOnRangeSelect) {
    var cursorns = "urn:webodf:names:cursor", MIN_OVERLAY_HEIGHT_PX = 8, BLINK_PERIOD_MS = 500, caretOverlay, caretElement, avatar, overlayElement, caretSizer, caretSizerRange, canvas = cursor.getDocument().getCanvas(), domUtils = core.DomUtils, guiStepUtils = new gui.GuiStepUtils, stepIterator, redrawTask, blinkTask, shouldResetBlink = false, shouldCheckCaretVisibility = false, shouldUpdateCaretSize = false, state = {isFocused:false, isShown:true, visibility:"hidden"}, lastState = {isFocused:!state.isFocused,
    isShown:!state.isShown, visibility:"hidden"};
    function blinkCaret() {
      caretElement.style.opacity = caretElement.style.opacity === "0" ? "1" : "0";
      blinkTask.trigger();
    }
    function getCaretSizeFromCursor() {
      caretSizerRange.selectNodeContents(caretSizer);
      return caretSizerRange.getBoundingClientRect();
    }
    function getSelectionRect() {
      var node = cursor.getNode(), caretRectangle, nextRectangle, selectionRectangle, rootRect = domUtils.getBoundingClientRect(canvas.getSizer()), useLeftEdge = false, width = 0;
      node.removeAttributeNS(cursorns, "caret-sizer-active");
      if (node.getClientRects().length > 0) {
        selectionRectangle = getCaretSizeFromCursor();
        width = selectionRectangle.left - domUtils.getBoundingClientRect(node).left;
        useLeftEdge = true;
      } else {
        stepIterator.setPosition(node, 0);
        selectionRectangle = guiStepUtils.getContentRect(stepIterator);
        if (!selectionRectangle && stepIterator.nextStep()) {
          nextRectangle = guiStepUtils.getContentRect(stepIterator);
          if (nextRectangle) {
            selectionRectangle = nextRectangle;
            useLeftEdge = true;
          }
        }
        if (!selectionRectangle) {
          node.setAttributeNS(cursorns, "caret-sizer-active", "true");
          selectionRectangle = getCaretSizeFromCursor();
          useLeftEdge = true;
        }
        if (!selectionRectangle) {
          runtime.log("WARN: No suitable client rectangle found for visual caret for " + cursor.getMemberId());
          while (node) {
            if (node.getClientRects().length > 0) {
              selectionRectangle = domUtils.getBoundingClientRect(node);
              useLeftEdge = true;
              break;
            }
            node = node.parentNode;
          }
        }
      }
      selectionRectangle = domUtils.translateRect(selectionRectangle, rootRect, canvas.getZoomLevel());
      caretRectangle = {top:selectionRectangle.top, height:selectionRectangle.height, right:useLeftEdge ? selectionRectangle.left : selectionRectangle.right, width:domUtils.adaptRangeDifferenceToZoomLevel(width, canvas.getZoomLevel())};
      return caretRectangle;
    }
    function updateOverlayHeightAndPosition() {
      var selectionRect = getSelectionRect(), cursorStyle;
      if (selectionRect.height < MIN_OVERLAY_HEIGHT_PX) {
        selectionRect = {top:selectionRect.top - (MIN_OVERLAY_HEIGHT_PX - selectionRect.height) / 2, height:MIN_OVERLAY_HEIGHT_PX, right:selectionRect.right};
      }
      caretOverlay.style.height = selectionRect.height + "px";
      caretOverlay.style.top = selectionRect.top + "px";
      caretOverlay.style.left = selectionRect.right - selectionRect.width + "px";
      caretOverlay.style.width = selectionRect.width ? selectionRect.width + "px" : "";
      if (overlayElement) {
        cursorStyle = runtime.getWindow().getComputedStyle(cursor.getNode(), null);
        if (cursorStyle.font) {
          overlayElement.style.font = cursorStyle.font;
        } else {
          overlayElement.style.fontStyle = cursorStyle.fontStyle;
          overlayElement.style.fontVariant = cursorStyle.fontVariant;
          overlayElement.style.fontWeight = cursorStyle.fontWeight;
          overlayElement.style.fontSize = cursorStyle.fontSize;
          overlayElement.style.lineHeight = cursorStyle.lineHeight;
          overlayElement.style.fontFamily = cursorStyle.fontFamily;
        }
      }
    }
    function hasStateChanged(property) {
      return lastState[property] !== state[property];
    }
    function saveState() {
      Object.keys(state).forEach(function(key) {
        lastState[key] = state[key];
      });
    }
    function updateCaret() {
      if (state.isShown === false || cursor.getSelectionType() !== ops.OdtCursor.RangeSelection || !blinkOnRangeSelect && !cursor.getSelectedRange().collapsed) {
        state.visibility = "hidden";
        caretElement.style.visibility = "hidden";
        blinkTask.cancel();
      } else {
        state.visibility = "visible";
        caretElement.style.visibility = "visible";
        if (state.isFocused === false) {
          caretElement.style.opacity = "1";
          blinkTask.cancel();
        } else {
          if (shouldResetBlink || hasStateChanged("visibility")) {
            caretElement.style.opacity = "1";
            blinkTask.cancel();
          }
          blinkTask.trigger();
        }
      }
      if (shouldUpdateCaretSize || shouldCheckCaretVisibility) {
        updateOverlayHeightAndPosition();
      }
      if (state.isShown && shouldCheckCaretVisibility) {
        viewport.scrollIntoView(caretElement.getBoundingClientRect());
      }
      if (hasStateChanged("isFocused")) {
        avatar.markAsFocussed(state.isFocused);
      }
      saveState();
      shouldResetBlink = false;
      shouldCheckCaretVisibility = false;
      shouldUpdateCaretSize = false;
    }
    this.handleUpdate = function() {
      shouldUpdateCaretSize = true;
      redrawTask.trigger();
    };
    this.refreshCursorBlinking = function() {
      shouldResetBlink = true;
      redrawTask.trigger();
    };
    this.setFocus = function() {
      state.isFocused = true;
      redrawTask.trigger();
    };
    this.removeFocus = function() {
      state.isFocused = false;
      redrawTask.trigger();
    };
    this.show = function() {
      state.isShown = true;
      redrawTask.trigger();
    };
    this.hide = function() {
      state.isShown = false;
      redrawTask.trigger();
    };
    this.setAvatarImageUrl = function(url) {
      avatar.setImageUrl(url);
    };
    this.setColor = function(newColor) {
      caretElement.style.borderColor = newColor;
      avatar.setColor(newColor);
    };
    this.getCursor = function() {
      return cursor;
    };
    this.getFocusElement = function() {
      return caretElement;
    };
    this.toggleHandleVisibility = function() {
      if (avatar.isVisible()) {
        avatar.hide();
      } else {
        avatar.show();
      }
    };
    this.showHandle = function() {
      avatar.show();
    };
    this.hideHandle = function() {
      avatar.hide();
    };
    this.setOverlayElement = function(element) {
      overlayElement = element;
      caretOverlay.appendChild(element);
      shouldUpdateCaretSize = true;
      redrawTask.trigger();
    };
    this.ensureVisible = function() {
      shouldCheckCaretVisibility = true;
      redrawTask.trigger();
    };
    this.getBoundingClientRect = function() {
      return domUtils.getBoundingClientRect(caretOverlay);
    };
    function destroy(callback) {
      caretOverlay.parentNode.removeChild(caretOverlay);
      caretSizer.parentNode.removeChild(caretSizer);
      callback();
    }
    this.destroy = function(callback) {
      var cleanup = [redrawTask.destroy, blinkTask.destroy, avatar.destroy, destroy];
      core.Async.destroyAll(cleanup, callback);
    };
    function init() {
      var odtDocument = cursor.getDocument(), positionFilters = [odtDocument.createRootFilter(cursor.getMemberId()), odtDocument.getPositionFilter()], dom = odtDocument.getDOMDocument(), editinfons = "urn:webodf:names:editinfo";
      caretSizerRange = dom.createRange();
      caretSizer = dom.createElement("span");
      caretSizer.className = "webodf-caretSizer";
      caretSizer.textContent = "|";
      cursor.getNode().appendChild(caretSizer);
      caretOverlay = dom.createElement("div");
      caretOverlay.setAttributeNS(editinfons, "editinfo:memberid", cursor.getMemberId());
      caretOverlay.className = "webodf-caretOverlay";
      caretElement = dom.createElement("div");
      caretElement.className = "caret";
      caretOverlay.appendChild(caretElement);
      avatar = new gui.Avatar(caretOverlay, avatarInitiallyVisible);
      canvas.getSizer().appendChild(caretOverlay);
      stepIterator = odtDocument.createStepIterator(cursor.getNode(), 0, positionFilters, odtDocument.getRootNode());
      redrawTask = core.Task.createRedrawTask(updateCaret);
      blinkTask = core.Task.createTimeoutTask(blinkCaret, BLINK_PERIOD_MS);
      redrawTask.triggerImmediate();
    }
    init();
  };
  odf.TextSerializer = function TextSerializer() {
    var self = this, odfUtils = odf.OdfUtils;
    function serializeNode(node) {
      var s = "", accept = self.filter ? self.filter.acceptNode(node) : NodeFilter.FILTER_ACCEPT, nodeType = node.nodeType, child;
      if ((accept === NodeFilter.FILTER_ACCEPT || accept === NodeFilter.FILTER_SKIP) && odfUtils.isTextContentContainingNode(node)) {
        child = node.firstChild;
        while (child) {
          s += serializeNode(child);
          child = child.nextSibling;
        }
      }
      if (accept === NodeFilter.FILTER_ACCEPT) {
        if (nodeType === Node.ELEMENT_NODE && odfUtils.isParagraph(node)) {
          s += "\n";
        } else {
          if (nodeType === Node.TEXT_NODE && node.textContent) {
            s += node.textContent;
          }
        }
      }
      return s;
    }
    this.filter = null;
    this.writeToString = function(node) {
      var plainText;
      if (!node) {
        return "";
      }
      plainText = serializeNode(node);
      if (plainText[plainText.length - 1] === "\n") {
        plainText = plainText.substr(0, plainText.length - 1);
      }
      return plainText;
    };
  };
  gui.MimeDataExporter = function MimeDataExporter() {
    var textSerializer;
    this.exportRangeToDataTransfer = function(dataTransfer, range) {
      var document = range.startContainer.ownerDocument, serializedFragment, fragmentContainer;
      fragmentContainer = document.createElement("span");
      fragmentContainer.appendChild(range.cloneContents());
      serializedFragment = textSerializer.writeToString(fragmentContainer);
      try {
        dataTransfer.setData("text/plain", serializedFragment);
      } catch (e) {
        dataTransfer.setData("Text", serializedFragment);
      }
    };
    function init() {
      textSerializer = new odf.TextSerializer;
      textSerializer.filter = new odf.OdfNodeFilter;
    }
    init();
  };
  gui.Clipboard = function Clipboard(mimeDataExporter) {
    this.setDataFromRange = function(e, range) {
      var result, clipboard = e.clipboardData, window = runtime.getWindow();
      if (!clipboard && window) {
        clipboard = window.clipboardData;
      }
      if (clipboard) {
        result = true;
        mimeDataExporter.exportRangeToDataTransfer(clipboard, range);
        e.preventDefault();
      } else {
        result = false;
      }
      return result;
    };
  };
  gui.SessionContext = function(session, inputMemberId) {
    var odtDocument = session.getOdtDocument(), odfUtils = odf.OdfUtils;
    this.isLocalCursorWithinOwnAnnotation = function() {
      var cursor = odtDocument.getCursor(inputMemberId), cursorNode, currentUserName, parentAnnotation;
      if (!cursor) {
        return false;
      }
      cursorNode = cursor && cursor.getNode();
      currentUserName = odtDocument.getMember(inputMemberId).getProperties().fullName;
      parentAnnotation = odfUtils.getParentAnnotation(cursorNode, odtDocument.getRootNode());
      if (parentAnnotation && odfUtils.getAnnotationCreator(parentAnnotation) === currentUserName) {
        return true;
      }
      return false;
    };
  };
  gui.StyleSummary = function StyleSummary(styles) {
    var propertyValues = {};
    function getPropertyValues(section, propertyName) {
      var cacheKey = section + "|" + propertyName, values;
      if (!propertyValues.hasOwnProperty(cacheKey)) {
        values = [];
        styles.forEach(function(style) {
          var styleSection = style.styleProperties[section], value = styleSection && styleSection[propertyName];
          if (values.indexOf(value) === -1) {
            values.push(value);
          }
        });
        propertyValues[cacheKey] = values;
      }
      return propertyValues[cacheKey];
    }
    this.getPropertyValues = getPropertyValues;
    function lazilyLoaded(section, propertyName, acceptedPropertyValues) {
      return function() {
        var existingPropertyValues = getPropertyValues(section, propertyName);
        return acceptedPropertyValues.length >= existingPropertyValues.length && existingPropertyValues.every(function(v) {
          return acceptedPropertyValues.indexOf(v) !== -1;
        });
      };
    }
    function getCommonValue(section, propertyName) {
      var values = getPropertyValues(section, propertyName);
      return values.length === 1 ? values[0] : undefined;
    }
    this.getCommonValue = getCommonValue;
    this.isBold = lazilyLoaded("style:text-properties", "fo:font-weight", ["bold"]);
    this.isItalic = lazilyLoaded("style:text-properties", "fo:font-style", ["italic"]);
    this.hasUnderline = lazilyLoaded("style:text-properties", "style:text-underline-style", ["solid"]);
    this.hasStrikeThrough = lazilyLoaded("style:text-properties", "style:text-line-through-style", ["solid"]);
    this.fontSize = function() {
      var stringFontSize = getCommonValue("style:text-properties", "fo:font-size");
      return stringFontSize && parseFloat(stringFontSize);
    };
    this.fontName = function() {
      return getCommonValue("style:text-properties", "style:font-name");
    };
    this.isAlignedLeft = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["left", "start"]);
    this.isAlignedCenter = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["center"]);
    this.isAlignedRight = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["right", "end"]);
    this.isAlignedJustified = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["justify"]);
    this.text = {isBold:this.isBold, isItalic:this.isItalic, hasUnderline:this.hasUnderline, hasStrikeThrough:this.hasStrikeThrough, fontSize:this.fontSize, fontName:this.fontName};
    this.paragraph = {isAlignedLeft:this.isAlignedLeft, isAlignedCenter:this.isAlignedCenter, isAlignedRight:this.isAlignedRight, isAlignedJustified:this.isAlignedJustified};
  };
  gui.DirectFormattingController = function DirectFormattingController(session, sessionConstraints, sessionContext, inputMemberId, objectNameGenerator, directTextStylingEnabled, directParagraphStylingEnabled) {
    var self = this, odtDocument = session.getOdtDocument(), utils = new core.Utils, odfUtils = odf.OdfUtils, eventNotifier = new core.EventNotifier([gui.DirectFormattingController.enabledChanged, gui.DirectFormattingController.textStylingChanged, gui.DirectFormattingController.paragraphStylingChanged]), textns = odf.Namespaces.textns, NEXT = core.StepDirection.NEXT, directCursorStyleProperties = null, lastSignalledSelectionInfo, selectionInfoCache;
    function getCachedStyleSummary() {
      return selectionInfoCache.value().styleSummary;
    }
    function getCachedEnabledFeatures() {
      return selectionInfoCache.value().enabledFeatures;
    }
    this.enabledFeatures = getCachedEnabledFeatures;
    function getNodes(range) {
      var container, nodes;
      if (range.collapsed) {
        container = range.startContainer;
        if (container.hasChildNodes() && range.startOffset < container.childNodes.length) {
          container = container.childNodes.item(range.startOffset);
        }
        nodes = [container];
      } else {
        nodes = odfUtils.getTextElements(range, true, false);
      }
      return nodes;
    }
    function getSelectionInfo() {
      var cursor = odtDocument.getCursor(inputMemberId), range = cursor && cursor.getSelectedRange(), nodes = [], selectionStyles = [], selectionContainsText = true, enabledFeatures = {directTextStyling:true, directParagraphStyling:true};
      if (range) {
        nodes = getNodes(range);
        if (nodes.length === 0) {
          nodes = [range.startContainer, range.endContainer];
          selectionContainsText = false;
        }
        selectionStyles = odtDocument.getFormatting().getAppliedStyles(nodes);
      }
      if (selectionStyles[0] !== undefined && directCursorStyleProperties) {
        selectionStyles[0].styleProperties = utils.mergeObjects(selectionStyles[0].styleProperties, directCursorStyleProperties);
      }
      if (sessionConstraints.getState(gui.CommonConstraints.EDIT.REVIEW_MODE) === true) {
        enabledFeatures.directTextStyling = enabledFeatures.directParagraphStyling = sessionContext.isLocalCursorWithinOwnAnnotation();
      }
      if (enabledFeatures.directTextStyling) {
        enabledFeatures.directTextStyling = selectionContainsText && cursor !== undefined && cursor.getSelectionType() === ops.OdtCursor.RangeSelection;
      }
      return {enabledFeatures:enabledFeatures, appliedStyles:selectionStyles, styleSummary:new gui.StyleSummary(selectionStyles)};
    }
    function createDiff(oldSummary, newSummary) {
      var diffMap = {};
      Object.keys(oldSummary).forEach(function(funcName) {
        var oldValue = oldSummary[funcName](), newValue = newSummary[funcName]();
        if (oldValue !== newValue) {
          diffMap[funcName] = newValue;
        }
      });
      return diffMap;
    }
    function emitSelectionChanges() {
      var textStyleDiff, paragraphStyleDiff, lastStyleSummary = lastSignalledSelectionInfo.styleSummary, newSelectionInfo = selectionInfoCache.value(), newSelectionStylesSummary = newSelectionInfo.styleSummary, lastEnabledFeatures = lastSignalledSelectionInfo.enabledFeatures, newEnabledFeatures = newSelectionInfo.enabledFeatures, enabledFeaturesChanged;
      textStyleDiff = createDiff(lastStyleSummary.text, newSelectionStylesSummary.text);
      paragraphStyleDiff = createDiff(lastStyleSummary.paragraph, newSelectionStylesSummary.paragraph);
      enabledFeaturesChanged = !(newEnabledFeatures.directTextStyling === lastEnabledFeatures.directTextStyling && newEnabledFeatures.directParagraphStyling === lastEnabledFeatures.directParagraphStyling);
      lastSignalledSelectionInfo = newSelectionInfo;
      if (enabledFeaturesChanged) {
        eventNotifier.emit(gui.DirectFormattingController.enabledChanged, newEnabledFeatures);
      }
      if (Object.keys(textStyleDiff).length > 0) {
        eventNotifier.emit(gui.DirectFormattingController.textStylingChanged, textStyleDiff);
      }
      if (Object.keys(paragraphStyleDiff).length > 0) {
        eventNotifier.emit(gui.DirectFormattingController.paragraphStylingChanged, paragraphStyleDiff);
      }
    }
    function forceSelectionInfoRefresh() {
      selectionInfoCache.reset();
      emitSelectionChanges();
    }
    function onCursorEvent(cursorOrId) {
      var cursorMemberId = typeof cursorOrId === "string" ? cursorOrId : cursorOrId.getMemberId();
      if (cursorMemberId === inputMemberId) {
        selectionInfoCache.reset();
      }
    }
    function onParagraphStyleModified() {
      selectionInfoCache.reset();
    }
    function onParagraphChanged(args) {
      var cursor = odtDocument.getCursor(inputMemberId), p = args.paragraphElement;
      if (cursor && odfUtils.getParagraphElement(cursor.getNode()) === p) {
        selectionInfoCache.reset();
      }
    }
    function toggle(predicate, toggleMethod) {
      toggleMethod(!predicate());
      return true;
    }
    function formatTextSelection(textProperties) {
      if (!getCachedEnabledFeatures().directTextStyling) {
        return;
      }
      var selection = odtDocument.getCursorSelection(inputMemberId), op, properties = {"style:text-properties":textProperties};
      if (selection.length !== 0) {
        op = new ops.OpApplyDirectStyling;
        op.init({memberid:inputMemberId, position:selection.position, length:selection.length, setProperties:properties});
        session.enqueue([op]);
      } else {
        directCursorStyleProperties = utils.mergeObjects(directCursorStyleProperties || {}, properties);
        selectionInfoCache.reset();
      }
    }
    this.formatTextSelection = formatTextSelection;
    function applyTextPropertyToSelection(propertyName, propertyValue) {
      var textProperties = {};
      textProperties[propertyName] = propertyValue;
      formatTextSelection(textProperties);
    }
    this.createCursorStyleOp = function(position, length, useCachedStyle) {
      var styleOp = null, appliedStyles, properties = directCursorStyleProperties;
      if (useCachedStyle) {
        appliedStyles = selectionInfoCache.value().appliedStyles[0];
        properties = appliedStyles && appliedStyles.styleProperties;
      }
      if (properties && properties["style:text-properties"]) {
        styleOp = new ops.OpApplyDirectStyling;
        styleOp.init({memberid:inputMemberId, position:position, length:length, setProperties:{"style:text-properties":properties["style:text-properties"]}});
        directCursorStyleProperties = null;
        selectionInfoCache.reset();
      }
      return styleOp;
    };
    function clearCursorStyle(op) {
      var spec = op.spec();
      if (directCursorStyleProperties && spec.memberid === inputMemberId) {
        if (spec.optype !== "SplitParagraph") {
          directCursorStyleProperties = null;
          selectionInfoCache.reset();
        }
      }
    }
    function setBold(checked) {
      var value = checked ? "bold" : "normal";
      applyTextPropertyToSelection("fo:font-weight", value);
    }
    this.setBold = setBold;
    function setItalic(checked) {
      var value = checked ? "italic" : "normal";
      applyTextPropertyToSelection("fo:font-style", value);
    }
    this.setItalic = setItalic;
    function setHasUnderline(checked) {
      var value = checked ? "solid" : "none";
      applyTextPropertyToSelection("style:text-underline-style", value);
    }
    this.setHasUnderline = setHasUnderline;
    function setHasStrikethrough(checked) {
      var value = checked ? "solid" : "none";
      applyTextPropertyToSelection("style:text-line-through-style", value);
    }
    this.setHasStrikethrough = setHasStrikethrough;
    function setFontSize(value) {
      applyTextPropertyToSelection("fo:font-size", value + "pt");
    }
    this.setFontSize = setFontSize;
    function setFontName(value) {
      applyTextPropertyToSelection("style:font-name", value);
    }
    this.setFontName = setFontName;
    this.getAppliedStyles = function() {
      return selectionInfoCache.value().appliedStyles;
    };
    this.toggleBold = toggle.bind(self, function() {
      return getCachedStyleSummary().isBold();
    }, setBold);
    this.toggleItalic = toggle.bind(self, function() {
      return getCachedStyleSummary().isItalic();
    }, setItalic);
    this.toggleUnderline = toggle.bind(self, function() {
      return getCachedStyleSummary().hasUnderline();
    }, setHasUnderline);
    this.toggleStrikethrough = toggle.bind(self, function() {
      return getCachedStyleSummary().hasStrikeThrough();
    }, setHasStrikethrough);
    this.isBold = function() {
      return getCachedStyleSummary().isBold();
    };
    this.isItalic = function() {
      return getCachedStyleSummary().isItalic();
    };
    this.hasUnderline = function() {
      return getCachedStyleSummary().hasUnderline();
    };
    this.hasStrikeThrough = function() {
      return getCachedStyleSummary().hasStrikeThrough();
    };
    this.fontSize = function() {
      return getCachedStyleSummary().fontSize();
    };
    this.fontName = function() {
      return getCachedStyleSummary().fontName();
    };
    this.isAlignedLeft = function() {
      return getCachedStyleSummary().isAlignedLeft();
    };
    this.isAlignedCenter = function() {
      return getCachedStyleSummary().isAlignedCenter();
    };
    this.isAlignedRight = function() {
      return getCachedStyleSummary().isAlignedRight();
    };
    this.isAlignedJustified = function() {
      return getCachedStyleSummary().isAlignedJustified();
    };
    function getOwnProperty(obj, key) {
      return obj.hasOwnProperty(key) ? obj[key] : undefined;
    }
    function applyParagraphDirectStyling(applyDirectStyling) {
      if (!getCachedEnabledFeatures().directParagraphStyling) {
        return;
      }
      var range = odtDocument.getCursor(inputMemberId).getSelectedRange(), paragraphs = odfUtils.getParagraphElements(range), formatting = odtDocument.getFormatting(), operations = [], derivedStyleNames = {}, defaultStyleName;
      paragraphs.forEach(function(paragraph) {
        var paragraphStartPoint = odtDocument.convertDomPointToCursorStep(paragraph, 0, NEXT), paragraphStyleName = paragraph.getAttributeNS(odf.Namespaces.textns, "style-name"), newParagraphStyleName, opAddStyle, opSetParagraphStyle, paragraphProperties;
        if (paragraphStyleName) {
          newParagraphStyleName = getOwnProperty(derivedStyleNames, paragraphStyleName);
        } else {
          newParagraphStyleName = defaultStyleName;
        }
        if (!newParagraphStyleName) {
          newParagraphStyleName = objectNameGenerator.generateStyleName();
          if (paragraphStyleName) {
            derivedStyleNames[paragraphStyleName] = newParagraphStyleName;
            paragraphProperties = formatting.createDerivedStyleObject(paragraphStyleName, "paragraph", {});
          } else {
            defaultStyleName = newParagraphStyleName;
            paragraphProperties = {};
          }
          paragraphProperties = applyDirectStyling(paragraphProperties);
          opAddStyle = new ops.OpAddStyle;
          opAddStyle.init({memberid:inputMemberId, styleName:newParagraphStyleName.toString(), styleFamily:"paragraph", isAutomaticStyle:true, setProperties:paragraphProperties});
          operations.push(opAddStyle);
        }
        opSetParagraphStyle = new ops.OpSetParagraphStyle;
        opSetParagraphStyle.init({memberid:inputMemberId, styleName:newParagraphStyleName.toString(), position:paragraphStartPoint});
        operations.push(opSetParagraphStyle);
      });
      session.enqueue(operations);
    }
    function applySimpleParagraphDirectStyling(styleOverrides) {
      applyParagraphDirectStyling(function(paragraphStyle) {
        return utils.mergeObjects(paragraphStyle, styleOverrides);
      });
    }
    function alignParagraph(alignment) {
      applySimpleParagraphDirectStyling({"style:paragraph-properties":{"fo:text-align":alignment}});
    }
    this.alignParagraphLeft = function() {
      alignParagraph("left");
      return true;
    };
    this.alignParagraphCenter = function() {
      alignParagraph("center");
      return true;
    };
    this.alignParagraphRight = function() {
      alignParagraph("right");
      return true;
    };
    this.alignParagraphJustified = function() {
      alignParagraph("justify");
      return true;
    };
    function modifyParagraphIndent(direction, paragraphStyle) {
      var tabStopDistance = odtDocument.getFormatting().getDefaultTabStopDistance(), paragraphProperties = paragraphStyle["style:paragraph-properties"], indentValue, indent, newIndent;
      if (paragraphProperties) {
        indentValue = paragraphProperties["fo:margin-left"];
        indent = odfUtils.parseLength(indentValue);
      }
      if (indent && indent.unit === tabStopDistance.unit) {
        newIndent = indent.value + direction * tabStopDistance.value + indent.unit;
      } else {
        newIndent = direction * tabStopDistance.value + tabStopDistance.unit;
      }
      return utils.mergeObjects(paragraphStyle, {"style:paragraph-properties":{"fo:margin-left":newIndent}});
    }
    this.indent = function() {
      applyParagraphDirectStyling(modifyParagraphIndent.bind(null, 1));
      return true;
    };
    this.outdent = function() {
      applyParagraphDirectStyling(modifyParagraphIndent.bind(null, -1));
      return true;
    };
    function isSelectionAtTheEndOfLastParagraph(range, paragraphNode) {
      var stepIterator, filters = [odtDocument.getPositionFilter(), odtDocument.createRootFilter(inputMemberId)];
      stepIterator = odtDocument.createStepIterator(range.endContainer, range.endOffset, filters, paragraphNode);
      return stepIterator.nextStep() === false;
    }
    function isTextStyleDifferentFromFirstParagraph(range, paragraphNode) {
      var textNodes = getNodes(range), selectedNodes = textNodes.length === 0 ? [range.startContainer] : textNodes, appliedTextStyles = odtDocument.getFormatting().getAppliedStyles(selectedNodes), textStyle = appliedTextStyles.length > 0 ? appliedTextStyles[0].styleProperties : undefined, paragraphStyle = odtDocument.getFormatting().getAppliedStylesForElement(paragraphNode).styleProperties;
      if (!textStyle || textStyle["style:family"] !== "text" || !textStyle["style:text-properties"]) {
        return false;
      }
      if (!paragraphStyle || !paragraphStyle["style:text-properties"]) {
        return true;
      }
      textStyle = textStyle["style:text-properties"];
      paragraphStyle = paragraphStyle["style:text-properties"];
      return !Object.keys(textStyle).every(function(key) {
        return textStyle[key] === paragraphStyle[key];
      });
    }
    this.createParagraphStyleOps = function(position) {
      if (!getCachedEnabledFeatures().directParagraphStyling) {
        return [];
      }
      var cursor = odtDocument.getCursor(inputMemberId), range = cursor.getSelectedRange(), operations = [], op, startNode, endNode, paragraphNode, appliedStyles, properties, parentStyleName, styleName;
      if (cursor.hasForwardSelection()) {
        startNode = cursor.getAnchorNode();
        endNode = cursor.getNode();
      } else {
        startNode = cursor.getNode();
        endNode = cursor.getAnchorNode();
      }
      paragraphNode = odfUtils.getParagraphElement(endNode);
      runtime.assert(Boolean(paragraphNode), "DirectFormattingController: Cursor outside paragraph");
      if (!isSelectionAtTheEndOfLastParagraph(range, paragraphNode)) {
        return operations;
      }
      if (endNode !== startNode) {
        paragraphNode = odfUtils.getParagraphElement(startNode);
      }
      if (!directCursorStyleProperties && !isTextStyleDifferentFromFirstParagraph(range, paragraphNode)) {
        return operations;
      }
      appliedStyles = selectionInfoCache.value().appliedStyles[0];
      properties = appliedStyles && appliedStyles.styleProperties;
      if (!properties) {
        return operations;
      }
      parentStyleName = paragraphNode.getAttributeNS(textns, "style-name");
      if (parentStyleName) {
        properties = {"style:text-properties":properties["style:text-properties"]};
        properties = odtDocument.getFormatting().createDerivedStyleObject(parentStyleName, "paragraph", properties);
      }
      styleName = objectNameGenerator.generateStyleName();
      op = new ops.OpAddStyle;
      op.init({memberid:inputMemberId, styleName:styleName, styleFamily:"paragraph", isAutomaticStyle:true, setProperties:properties});
      operations.push(op);
      op = new ops.OpSetParagraphStyle;
      op.init({memberid:inputMemberId, styleName:styleName, position:position});
      operations.push(op);
      return operations;
    };
    this.subscribe = function(eventid, cb) {
      eventNotifier.subscribe(eventid, cb);
    };
    this.unsubscribe = function(eventid, cb) {
      eventNotifier.unsubscribe(eventid, cb);
    };
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorEvent);
      odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorEvent);
      odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent);
      odtDocument.unsubscribe(ops.OdtDocument.signalParagraphStyleModified, onParagraphStyleModified);
      odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
      odtDocument.unsubscribe(ops.OdtDocument.signalOperationEnd, clearCursorStyle);
      odtDocument.unsubscribe(ops.OdtDocument.signalProcessingBatchEnd, emitSelectionChanges);
      sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, forceSelectionInfoRefresh);
      callback();
    };
    function emptyFunction() {
    }
    function emptyBoolFunction() {
      return false;
    }
    function emptyFalseReturningFunction() {
      return false;
    }
    function getCachedSelectionInfo() {
      return selectionInfoCache.value();
    }
    function init() {
      odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorEvent);
      odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorEvent);
      odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent);
      odtDocument.subscribe(ops.OdtDocument.signalParagraphStyleModified, onParagraphStyleModified);
      odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
      odtDocument.subscribe(ops.OdtDocument.signalOperationEnd, clearCursorStyle);
      odtDocument.subscribe(ops.OdtDocument.signalProcessingBatchEnd, emitSelectionChanges);
      sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, forceSelectionInfoRefresh);
      selectionInfoCache = new core.LazyProperty(getSelectionInfo);
      lastSignalledSelectionInfo = getCachedSelectionInfo();
      if (!directTextStylingEnabled) {
        self.formatTextSelection = emptyFunction;
        self.setBold = emptyFunction;
        self.setItalic = emptyFunction;
        self.setHasUnderline = emptyFunction;
        self.setHasStrikethrough = emptyFunction;
        self.setFontSize = emptyFunction;
        self.setFontName = emptyFunction;
        self.toggleBold = emptyFalseReturningFunction;
        self.toggleItalic = emptyFalseReturningFunction;
        self.toggleUnderline = emptyFalseReturningFunction;
        self.toggleStrikethrough = emptyFalseReturningFunction;
      }
      if (!directParagraphStylingEnabled) {
        self.alignParagraphCenter = emptyBoolFunction;
        self.alignParagraphJustified = emptyBoolFunction;
        self.alignParagraphLeft = emptyBoolFunction;
        self.alignParagraphRight = emptyBoolFunction;
        self.createParagraphStyleOps = function() {
          return [];
        };
        self.indent = emptyBoolFunction;
        self.outdent = emptyBoolFunction;
      }
    }
    init();
  };
  gui.DirectFormattingController.enabledChanged = "enabled/changed";
  gui.DirectFormattingController.textStylingChanged = "textStyling/changed";
  gui.DirectFormattingController.paragraphStylingChanged = "paragraphStyling/changed";
  gui.DirectFormattingController.SelectionInfo = function() {
    this.enabledFeatures;
    this.appliedStyles;
    this.styleSummary;
  };
  gui.KeyboardHandler = function KeyboardHandler() {
    var modifier = gui.KeyboardHandler.Modifier, defaultBinding = null, bindings = {};
    function getModifiers(e) {
      var modifiers = modifier.None;
      if (e.metaKey) {
        modifiers |= modifier.Meta;
      }
      if (e.ctrlKey) {
        modifiers |= modifier.Ctrl;
      }
      if (e.altKey) {
        modifiers |= modifier.Alt;
      }
      if (e.shiftKey) {
        modifiers |= modifier.Shift;
      }
      return modifiers;
    }
    function getKeyCombo(keyCode, modifiers) {
      if (!modifiers) {
        modifiers = modifier.None;
      }
      switch(keyCode) {
        case gui.KeyboardHandler.KeyCode.LeftMeta:
        ;
        case gui.KeyboardHandler.KeyCode.RightMeta:
        ;
        case gui.KeyboardHandler.KeyCode.MetaInMozilla:
          modifiers |= modifier.Meta;
          break;
        case gui.KeyboardHandler.KeyCode.Ctrl:
          modifiers |= modifier.Ctrl;
          break;
        case gui.KeyboardHandler.KeyCode.Alt:
          modifiers |= modifier.Alt;
          break;
        case gui.KeyboardHandler.KeyCode.Shift:
          modifiers |= modifier.Shift;
          break;
      }
      return keyCode + ":" + modifiers;
    }
    this.setDefault = function(callback) {
      defaultBinding = callback;
    };
    this.bind = function(keyCode, modifiers, callback, overwrite) {
      var keyCombo = getKeyCombo(keyCode, modifiers);
      runtime.assert(overwrite || bindings.hasOwnProperty(keyCombo) === false, "tried to overwrite the callback handler of key combo: " + keyCombo);
      bindings[keyCombo] = callback;
    };
    this.unbind = function(keyCode, modifiers) {
      var keyCombo = getKeyCombo(keyCode, modifiers);
      delete bindings[keyCombo];
    };
    this.reset = function() {
      defaultBinding = null;
      bindings = {};
    };
    this.handleEvent = function(e) {
      var keyCombo = getKeyCombo(e.keyCode, getModifiers(e)), callback = bindings[keyCombo], handled = false;
      if (callback) {
        handled = callback();
      } else {
        if (defaultBinding !== null) {
          handled = defaultBinding(e);
        }
      }
      if (handled) {
        if (e.preventDefault) {
          e.preventDefault();
        } else {
          e.returnValue = false;
        }
      }
    };
  };
  gui.KeyboardHandler.Modifier = {None:0, Meta:1, Ctrl:2, Alt:4, CtrlAlt:6, Shift:8, MetaShift:9, CtrlShift:10, AltShift:12};
  gui.KeyboardHandler.KeyCode = {Backspace:8, Tab:9, Clear:12, Enter:13, Shift:16, Ctrl:17, Alt:18, End:35, Home:36, Left:37, Up:38, Right:39, Down:40, Delete:46, A:65, B:66, C:67, D:68, E:69, F:70, G:71, H:72, I:73, J:74, K:75, L:76, M:77, N:78, O:79, P:80, Q:81, R:82, S:83, T:84, U:85, V:86, W:87, X:88, Y:89, Z:90, LeftMeta:91, RightMeta:93, MetaInMozilla:224};
  gui.HyperlinkClickHandler = function HyperlinkClickHandler(getContainer, keyDownHandler, keyUpHandler) {
    var inactiveLinksCssClass = "webodf-inactiveLinks", modifier = gui.KeyboardHandler.Modifier, keyCode = gui.KeyboardHandler.KeyCode, xpath = xmldom.XPath, odfUtils = odf.OdfUtils, window = runtime.getWindow(), activeModifier = modifier.None, activeKeyBindings = [];
    runtime.assert(window !== null, "Expected to be run in an environment which has a global window, like a browser.");
    function getHyperlinkElement(node) {
      while (node !== null) {
        if (odfUtils.isHyperlink(node)) {
          return node;
        }
        if (odfUtils.isParagraph(node)) {
          break;
        }
        node = node.parentNode;
      }
      return null;
    }
    this.handleClick = function(e) {
      var target = e.target || e.srcElement, pressedModifier, linkElement, url, rootNode, bookmarks;
      if (e.ctrlKey) {
        pressedModifier = modifier.Ctrl;
      } else {
        if (e.metaKey) {
          pressedModifier = modifier.Meta;
        }
      }
      if (activeModifier !== modifier.None && activeModifier !== pressedModifier) {
        return;
      }
      linkElement = getHyperlinkElement(target);
      if (!linkElement) {
        return;
      }
      url = odfUtils.getHyperlinkTarget(linkElement);
      if (url === "") {
        return;
      }
      if (url[0] === "#") {
        url = url.substring(1);
        rootNode = getContainer();
        bookmarks = xpath.getODFElementsWithXPath(rootNode, "//text:bookmark-start[@text:name='" + url + "']", odf.Namespaces.lookupNamespaceURI);
        if (bookmarks.length === 0) {
          bookmarks = xpath.getODFElementsWithXPath(rootNode, "//text:bookmark[@text:name='" + url + "']", odf.Namespaces.lookupNamespaceURI);
        }
        if (bookmarks.length > 0) {
          bookmarks[0].scrollIntoView(true);
        }
      } else {
        if (/^\s*(javascript|data):/.test(url)) {
          runtime.log("WARN:", "potentially malicious URL ignored");
        } else {
          window.open(url);
        }
      }
      if (e.preventDefault) {
        e.preventDefault();
      } else {
        e.returnValue = false;
      }
    };
    function showPointerCursor() {
      var container = getContainer();
      runtime.assert(Boolean(container.classList), "Document container has no classList element");
      container.classList.remove(inactiveLinksCssClass);
    }
    function showTextCursor() {
      var container = getContainer();
      runtime.assert(Boolean(container.classList), "Document container has no classList element");
      container.classList.add(inactiveLinksCssClass);
    }
    function cleanupEventBindings() {
      window.removeEventListener("focus", showTextCursor, false);
      activeKeyBindings.forEach(function(boundShortcut) {
        keyDownHandler.unbind(boundShortcut.keyCode, boundShortcut.modifier);
        keyUpHandler.unbind(boundShortcut.keyCode, boundShortcut.modifier);
      });
      activeKeyBindings.length = 0;
    }
    function bindEvents(modifierKey) {
      cleanupEventBindings();
      if (modifierKey !== modifier.None) {
        window.addEventListener("focus", showTextCursor, false);
        switch(modifierKey) {
          case modifier.Ctrl:
            activeKeyBindings.push({keyCode:keyCode.Ctrl, modifier:modifier.None});
            break;
          case modifier.Meta:
            activeKeyBindings.push({keyCode:keyCode.LeftMeta, modifier:modifier.None});
            activeKeyBindings.push({keyCode:keyCode.RightMeta, modifier:modifier.None});
            activeKeyBindings.push({keyCode:keyCode.MetaInMozilla, modifier:modifier.None});
            break;
        }
        activeKeyBindings.forEach(function(boundShortcut) {
          keyDownHandler.bind(boundShortcut.keyCode, boundShortcut.modifier, showPointerCursor);
          keyUpHandler.bind(boundShortcut.keyCode, boundShortcut.modifier, showTextCursor);
        });
      }
    }
    this.setModifier = function(value) {
      if (activeModifier === value) {
        return;
      }
      runtime.assert(value === modifier.None || value === modifier.Ctrl || value === modifier.Meta, "Unsupported KeyboardHandler.Modifier value: " + value);
      activeModifier = value;
      if (activeModifier !== modifier.None) {
        showTextCursor();
      } else {
        showPointerCursor();
      }
      bindEvents(activeModifier);
    };
    this.getModifier = function() {
      return activeModifier;
    };
    this.destroy = function(callback) {
      showTextCursor();
      cleanupEventBindings();
      callback();
    };
  };
  gui.EventManager = function EventManager(odtDocument) {
    var window = runtime.getWindow(), bindToDirectHandler = {"beforecut":true, "beforepaste":true, "longpress":true, "drag":true, "dragstop":true}, bindToWindow = {"mousedown":true, "mouseup":true, "focus":true}, compoundEvents = {}, eventDelegates = {}, eventTrap, canvasElement = odtDocument.getCanvas().getElement(), eventManager = this, longPressTimers = {}, LONGPRESS_DURATION = 400;
    function EventDelegate(eventName) {
      var self = this, recentEvents = [], subscribers = new core.EventNotifier([eventName]);
      function listenEvent(eventTarget, eventType, eventHandler) {
        var onVariant, bound = false;
        onVariant = "on" + eventType;
        if (eventTarget.attachEvent) {
          eventTarget.attachEvent(onVariant, eventHandler);
          bound = true;
        }
        if (!bound && eventTarget.addEventListener) {
          eventTarget.addEventListener(eventType, eventHandler, false);
          bound = true;
        }
        if ((!bound || bindToDirectHandler[eventType]) && eventTarget.hasOwnProperty(onVariant)) {
          eventTarget[onVariant] = eventHandler;
        }
      }
      function removeEvent(eventTarget, eventType, eventHandler) {
        var onVariant = "on" + eventType;
        if (eventTarget.detachEvent) {
          eventTarget.detachEvent(onVariant, eventHandler);
        }
        if (eventTarget.removeEventListener) {
          eventTarget.removeEventListener(eventType, eventHandler, false);
        }
        if (eventTarget[onVariant] === eventHandler) {
          eventTarget[onVariant] = null;
        }
      }
      function handleEvent(e) {
        if (recentEvents.indexOf(e) === -1) {
          recentEvents.push(e);
          if (self.filters.every(function(filter) {
            return filter(e);
          })) {
            try {
              subscribers.emit(eventName, e);
            } catch (err) {
              runtime.log("Error occurred while processing " + eventName + ":\n" + err.message + "\n" + err.stack);
            }
          }
          runtime.setTimeout(function() {
            recentEvents.splice(recentEvents.indexOf(e), 1);
          }, 0);
        }
      }
      this.filters = [];
      this.subscribe = function(cb) {
        subscribers.subscribe(eventName, cb);
      };
      this.unsubscribe = function(cb) {
        subscribers.unsubscribe(eventName, cb);
      };
      this.destroy = function() {
        removeEvent(window, eventName, handleEvent);
        removeEvent(eventTrap, eventName, handleEvent);
        removeEvent(canvasElement, eventName, handleEvent);
      };
      function init() {
        if (bindToWindow[eventName]) {
          listenEvent(window, eventName, handleEvent);
        }
        listenEvent(eventTrap, eventName, handleEvent);
        listenEvent(canvasElement, eventName, handleEvent);
      }
      init();
    }
    function CompoundEvent(eventName, dependencies, eventProxy) {
      var cachedState = {}, subscribers = new core.EventNotifier([eventName]);
      function subscribedProxy(event) {
        eventProxy(event, cachedState, function(compoundEventInstance) {
          compoundEventInstance.type = eventName;
          subscribers.emit(eventName, compoundEventInstance);
        });
      }
      this.subscribe = function(cb) {
        subscribers.subscribe(eventName, cb);
      };
      this.unsubscribe = function(cb) {
        subscribers.unsubscribe(eventName, cb);
      };
      this.destroy = function() {
        dependencies.forEach(function(eventName) {
          eventManager.unsubscribe(eventName, subscribedProxy);
        });
      };
      function init() {
        dependencies.forEach(function(eventName) {
          eventManager.subscribe(eventName, subscribedProxy);
        });
      }
      init();
    }
    function clearTimeout(timer) {
      runtime.clearTimeout(timer);
      delete longPressTimers[timer];
    }
    function setTimeout(fn, duration) {
      var timer = runtime.setTimeout(function() {
        fn();
        clearTimeout(timer);
      }, duration);
      longPressTimers[timer] = true;
      return timer;
    }
    function getTarget(e) {
      return e.target || e.srcElement || null;
    }
    function emitLongPressEvent(event, cachedState, callback) {
      var touchEvent = event, fingers = touchEvent.touches.length, touch = touchEvent.touches[0], timer = cachedState.timer;
      if (event.type === "touchmove" || event.type === "touchend") {
        if (timer) {
          clearTimeout(timer);
        }
      } else {
        if (event.type === "touchstart") {
          if (fingers !== 1) {
            runtime.clearTimeout(timer);
          } else {
            timer = setTimeout(function() {
              callback({clientX:touch.clientX, clientY:touch.clientY, pageX:touch.pageX, pageY:touch.pageY, target:getTarget(event), detail:1});
            }, LONGPRESS_DURATION);
          }
        }
      }
      cachedState.timer = timer;
    }
    function emitDragEvent(event, cachedState, callback) {
      var touchEvent = event, fingers = touchEvent.touches.length, touch = touchEvent.touches[0], target = getTarget(event), cachedTarget = cachedState.target;
      if (fingers !== 1 || event.type === "touchend") {
        cachedTarget = null;
      } else {
        if (event.type === "touchstart" && target.getAttribute("class") === "webodf-draggable") {
          cachedTarget = target;
        } else {
          if (event.type === "touchmove" && cachedTarget) {
            event.preventDefault();
            event.stopPropagation();
            callback({clientX:touch.clientX, clientY:touch.clientY, pageX:touch.pageX, pageY:touch.pageY, target:cachedTarget, detail:1});
          }
        }
      }
      cachedState.target = cachedTarget;
    }
    function emitDragStopEvent(event, cachedState, callback) {
      var touchEvent = event, target = getTarget(event), touch, dragging = cachedState.dragging;
      if (event.type === "drag") {
        dragging = true;
      } else {
        if (event.type === "touchend" && dragging) {
          dragging = false;
          touch = touchEvent.changedTouches[0];
          callback({clientX:touch.clientX, clientY:touch.clientY, pageX:touch.pageX, pageY:touch.pageY, target:target, detail:1});
        }
      }
      cachedState.dragging = dragging;
    }
    function declareTouchEnabled() {
      canvasElement.classList.add("webodf-touchEnabled");
      eventManager.unsubscribe("touchstart", declareTouchEnabled);
    }
    function WindowScrollState(window) {
      var x = window.scrollX, y = window.scrollY;
      this.restore = function() {
        if (window.scrollX !== x || window.scrollY !== y) {
          window.scrollTo(x, y);
        }
      };
    }
    function ElementScrollState(element) {
      var top = element.scrollTop, left = element.scrollLeft;
      this.restore = function() {
        if (element.scrollTop !== top || element.scrollLeft !== left) {
          element.scrollTop = top;
          element.scrollLeft = left;
        }
      };
    }
    function getDelegateForEvent(eventName, shouldCreate) {
      var delegate = eventDelegates[eventName] || compoundEvents[eventName] || null;
      if (!delegate && shouldCreate) {
        delegate = eventDelegates[eventName] = new EventDelegate(eventName);
      }
      return delegate;
    }
    this.addFilter = function(eventName, filter) {
      var delegate = getDelegateForEvent(eventName, true);
      delegate.filters.push(filter);
    };
    this.removeFilter = function(eventName, filter) {
      var delegate = getDelegateForEvent(eventName, true), index = delegate.filters.indexOf(filter);
      if (index !== -1) {
        delegate.filters.splice(index, 1);
      }
    };
    function subscribe(eventName, handler) {
      var delegate = getDelegateForEvent(eventName, true);
      delegate.subscribe(handler);
    }
    this.subscribe = subscribe;
    function unsubscribe(eventName, handler) {
      var delegate = getDelegateForEvent(eventName, false);
      if (delegate) {
        delegate.unsubscribe(handler);
      }
    }
    this.unsubscribe = unsubscribe;
    function hasFocus() {
      return odtDocument.getDOMDocument().activeElement === eventTrap;
    }
    this.hasFocus = hasFocus;
    function disableTrapSelection() {
      if (hasFocus()) {
        eventTrap.blur();
      }
      eventTrap.setAttribute("disabled", "true");
    }
    function enableTrapSelection() {
      eventTrap.removeAttribute("disabled");
    }
    function findScrollableParents(element) {
      var scrollParents = [];
      while (element) {
        if (element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight) {
          scrollParents.push(new ElementScrollState(element));
        }
        element = element.parentNode;
      }
      scrollParents.push(new WindowScrollState(window));
      return scrollParents;
    }
    function focus() {
      var scrollParents;
      if (!hasFocus()) {
        scrollParents = findScrollableParents(eventTrap);
        enableTrapSelection();
        eventTrap.focus();
        scrollParents.forEach(function(scrollParent) {
          scrollParent.restore();
        });
      }
    }
    this.focus = focus;
    this.getEventTrap = function() {
      return eventTrap;
    };
    this.setEditing = function(editable) {
      var hadFocus = hasFocus();
      if (hadFocus) {
        eventTrap.blur();
      }
      if (editable) {
        eventTrap.removeAttribute("readOnly");
      } else {
        eventTrap.setAttribute("readOnly", "true");
      }
      if (hadFocus) {
        focus();
      }
    };
    this.destroy = function(callback) {
      unsubscribe("touchstart", declareTouchEnabled);
      Object.keys(longPressTimers).forEach(function(timer) {
        clearTimeout(parseInt(timer, 10));
      });
      longPressTimers.length = 0;
      Object.keys(compoundEvents).forEach(function(compoundEventName) {
        compoundEvents[compoundEventName].destroy();
      });
      compoundEvents = {};
      unsubscribe("mousedown", disableTrapSelection);
      unsubscribe("mouseup", enableTrapSelection);
      unsubscribe("contextmenu", enableTrapSelection);
      Object.keys(eventDelegates).forEach(function(eventName) {
        eventDelegates[eventName].destroy();
      });
      eventDelegates = {};
      eventTrap.parentNode.removeChild(eventTrap);
      callback();
    };
    function init() {
      var sizerElement = odtDocument.getOdfCanvas().getSizer(), doc = sizerElement.ownerDocument;
      runtime.assert(Boolean(window), "EventManager requires a window object to operate correctly");
      eventTrap = doc.createElement("textarea");
      eventTrap.id = "eventTrap";
      eventTrap.setAttribute("tabindex", "-1");
      eventTrap.setAttribute("readOnly", "true");
      eventTrap.setAttribute("rows", "1");
      sizerElement.appendChild(eventTrap);
      subscribe("mousedown", disableTrapSelection);
      subscribe("mouseup", enableTrapSelection);
      subscribe("contextmenu", enableTrapSelection);
      compoundEvents.longpress = new CompoundEvent("longpress", ["touchstart", "touchmove", "touchend"], emitLongPressEvent);
      compoundEvents.drag = new CompoundEvent("drag", ["touchstart", "touchmove", "touchend"], emitDragEvent);
      compoundEvents.dragstop = new CompoundEvent("dragstop", ["drag", "touchend"], emitDragStopEvent);
      subscribe("touchstart", declareTouchEnabled);
    }
    init();
  };
  gui.IOSSafariSupport = function(eventManager) {
    var window = runtime.getWindow(), eventTrap = eventManager.getEventTrap();
    function suppressFocusScrollIfKeyboardOpen() {
      if (window.innerHeight !== window.outerHeight) {
        eventTrap.style.display = "none";
        runtime.requestAnimationFrame(function() {
          eventTrap.style.display = "block";
        });
      }
    }
    this.destroy = function(callback) {
      eventManager.unsubscribe("focus", suppressFocusScrollIfKeyboardOpen);
      eventTrap.removeAttribute("autocapitalize");
      eventTrap.style.WebkitTransform = "";
      callback();
    };
    function init() {
      eventManager.subscribe("focus", suppressFocusScrollIfKeyboardOpen);
      eventTrap.setAttribute("autocapitalize", "off");
      eventTrap.style.WebkitTransform = "translateX(-10000px)";
    }
    init();
  };
  gui.HyperlinkController = function HyperlinkController(session, sessionConstraints, sessionContext, inputMemberId) {
    var odfUtils = odf.OdfUtils, odtDocument = session.getOdtDocument(), eventNotifier = new core.EventNotifier([gui.HyperlinkController.enabledChanged]), isEnabled = false;
    function updateEnabledState() {
      var newIsEnabled = true;
      if (sessionConstraints.getState(gui.CommonConstraints.EDIT.REVIEW_MODE) === true) {
        newIsEnabled = sessionContext.isLocalCursorWithinOwnAnnotation();
      }
      if (newIsEnabled !== isEnabled) {
        isEnabled = newIsEnabled;
        eventNotifier.emit(gui.HyperlinkController.enabledChanged, isEnabled);
      }
    }
    function onCursorEvent(cursor) {
      if (cursor.getMemberId() === inputMemberId) {
        updateEnabledState();
      }
    }
    this.isEnabled = function() {
      return isEnabled;
    };
    this.subscribe = function(eventid, cb) {
      eventNotifier.subscribe(eventid, cb);
    };
    this.unsubscribe = function(eventid, cb) {
      eventNotifier.unsubscribe(eventid, cb);
    };
    function addHyperlink(hyperlink, insertionText) {
      if (!isEnabled) {
        return;
      }
      var selection = odtDocument.getCursorSelection(inputMemberId), op = new ops.OpApplyHyperlink, operations = [];
      if (selection.length === 0 || insertionText) {
        insertionText = insertionText || hyperlink;
        op = new ops.OpInsertText;
        op.init({memberid:inputMemberId, position:selection.position, text:insertionText});
        selection.length = insertionText.length;
        operations.push(op);
      }
      op = new ops.OpApplyHyperlink;
      op.init({memberid:inputMemberId, position:selection.position, length:selection.length, hyperlink:hyperlink});
      operations.push(op);
      session.enqueue(operations);
    }
    this.addHyperlink = addHyperlink;
    function removeHyperlinks() {
      if (!isEnabled) {
        return;
      }
      var iterator = odtDocument.createPositionIterator(odtDocument.getRootNode()), selectedRange = odtDocument.getCursor(inputMemberId).getSelectedRange(), links = odfUtils.getHyperlinkElements(selectedRange), removeEntireLink = selectedRange.collapsed && links.length === 1, domRange = odtDocument.getDOMDocument().createRange(), operations = [], cursorRange, firstLink, lastLink, offset, op;
      if (links.length === 0) {
        return;
      }
      links.forEach(function(link) {
        domRange.selectNodeContents(link);
        cursorRange = odtDocument.convertDomToCursorRange({anchorNode:domRange.startContainer, anchorOffset:domRange.startOffset, focusNode:domRange.endContainer, focusOffset:domRange.endOffset});
        op = new ops.OpRemoveHyperlink;
        op.init({memberid:inputMemberId, position:cursorRange.position, length:cursorRange.length});
        operations.push(op);
      });
      if (!removeEntireLink) {
        firstLink = links[0];
        if (selectedRange.comparePoint(firstLink, 0) === -1) {
          domRange.setStart(firstLink, 0);
          domRange.setEnd(selectedRange.startContainer, selectedRange.startOffset);
          cursorRange = odtDocument.convertDomToCursorRange({anchorNode:domRange.startContainer, anchorOffset:domRange.startOffset, focusNode:domRange.endContainer, focusOffset:domRange.endOffset});
          if (cursorRange.length > 0) {
            op = new ops.OpApplyHyperlink;
            op.init({memberid:inputMemberId, position:cursorRange.position, length:cursorRange.length, hyperlink:odfUtils.getHyperlinkTarget(firstLink)});
            operations.push(op);
          }
        }
        lastLink = links[links.length - 1];
        iterator.moveToEndOfNode(lastLink);
        offset = iterator.unfilteredDomOffset();
        if (selectedRange.comparePoint(lastLink, offset) === 1) {
          domRange.setStart(selectedRange.endContainer, selectedRange.endOffset);
          domRange.setEnd(lastLink, offset);
          cursorRange = odtDocument.convertDomToCursorRange({anchorNode:domRange.startContainer, anchorOffset:domRange.startOffset, focusNode:domRange.endContainer, focusOffset:domRange.endOffset});
          if (cursorRange.length > 0) {
            op = new ops.OpApplyHyperlink;
            op.init({memberid:inputMemberId, position:cursorRange.position, length:cursorRange.length, hyperlink:odfUtils.getHyperlinkTarget(lastLink)});
            operations.push(op);
          }
        }
      }
      session.enqueue(operations);
      domRange.detach();
    }
    this.removeHyperlinks = removeHyperlinks;
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      callback();
    };
    function init() {
      odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      updateEnabledState();
    }
    init();
  };
  gui.HyperlinkController.enabledChanged = "enabled/changed";
  gui.ImageController = function ImageController(session, sessionConstraints, sessionContext, inputMemberId, objectNameGenerator) {
    var fileExtensionByMimetype = {"image/gif":".gif", "image/jpeg":".jpg", "image/png":".png"}, textns = odf.Namespaces.textns, odtDocument = session.getOdtDocument(), odfUtils = odf.OdfUtils, formatting = odtDocument.getFormatting(), eventNotifier = new core.EventNotifier([gui.HyperlinkController.enabledChanged]), isEnabled = false;
    function updateEnabledState() {
      var newIsEnabled = true;
      if (sessionConstraints.getState(gui.CommonConstraints.EDIT.REVIEW_MODE) === true) {
        newIsEnabled = sessionContext.isLocalCursorWithinOwnAnnotation();
      }
      if (newIsEnabled !== isEnabled) {
        isEnabled = newIsEnabled;
        eventNotifier.emit(gui.ImageController.enabledChanged, isEnabled);
      }
    }
    function onCursorEvent(cursor) {
      if (cursor.getMemberId() === inputMemberId) {
        updateEnabledState();
      }
    }
    this.isEnabled = function() {
      return isEnabled;
    };
    this.subscribe = function(eventid, cb) {
      eventNotifier.subscribe(eventid, cb);
    };
    this.unsubscribe = function(eventid, cb) {
      eventNotifier.unsubscribe(eventid, cb);
    };
    function createAddGraphicsStyleOp(name) {
      var op = new ops.OpAddStyle;
      op.init({memberid:inputMemberId, styleName:name, styleFamily:"graphic", isAutomaticStyle:false, setProperties:{"style:graphic-properties":{"text:anchor-type":"paragraph", "svg:x":"0cm", "svg:y":"0cm", "style:wrap":"dynamic", "style:number-wrapped-paragraphs":"no-limit", "style:wrap-contour":"false", "style:vertical-pos":"top", "style:vertical-rel":"paragraph", "style:horizontal-pos":"center", "style:horizontal-rel":"paragraph"}}});
      return op;
    }
    function createAddFrameStyleOp(styleName, parentStyleName) {
      var op = new ops.OpAddStyle;
      op.init({memberid:inputMemberId, styleName:styleName, styleFamily:"graphic", isAutomaticStyle:true, setProperties:{"style:parent-style-name":parentStyleName, "style:graphic-properties":{"style:vertical-pos":"top", "style:vertical-rel":"baseline", "style:horizontal-pos":"center", "style:horizontal-rel":"paragraph", "fo:background-color":"transparent", "style:background-transparency":"100%", "style:shadow":"none", "style:mirror":"none", "fo:clip":"rect(0cm, 0cm, 0cm, 0cm)", "draw:luminance":"0%",
      "draw:contrast":"0%", "draw:red":"0%", "draw:green":"0%", "draw:blue":"0%", "draw:gamma":"100%", "draw:color-inversion":"false", "draw:image-opacity":"100%", "draw:color-mode":"standard"}}});
      return op;
    }
    function getFileExtension(mimetype) {
      mimetype = mimetype.toLowerCase();
      return fileExtensionByMimetype.hasOwnProperty(mimetype) ? fileExtensionByMimetype[mimetype] : null;
    }
    function insertImageInternal(mimetype, content, widthMeasure, heightMeasure) {
      var graphicsStyleName = "Graphics", stylesElement = odtDocument.getOdfCanvas().odfContainer().rootElement.styles, fileExtension = getFileExtension(mimetype), fileName, graphicsStyleElement, frameStyleName, op, operations = [];
      runtime.assert(fileExtension !== null, "Image type is not supported: " + mimetype);
      fileName = "Pictures/" + objectNameGenerator.generateImageName() + fileExtension;
      op = new ops.OpSetBlob;
      op.init({memberid:inputMemberId, filename:fileName, mimetype:mimetype, content:content});
      operations.push(op);
      graphicsStyleElement = formatting.getStyleElement(graphicsStyleName, "graphic", [stylesElement]);
      if (!graphicsStyleElement) {
        op = createAddGraphicsStyleOp(graphicsStyleName);
        operations.push(op);
      }
      frameStyleName = objectNameGenerator.generateStyleName();
      op = createAddFrameStyleOp(frameStyleName, graphicsStyleName);
      operations.push(op);
      op = new ops.OpInsertImage;
      op.init({memberid:inputMemberId, position:odtDocument.getCursorPosition(inputMemberId), filename:fileName, frameWidth:widthMeasure, frameHeight:heightMeasure, frameStyleName:frameStyleName, frameName:objectNameGenerator.generateFrameName()});
      operations.push(op);
      session.enqueue(operations);
    }
    function scaleToAvailableContentSize(originalSize, pageContentSize) {
      var widthRatio = 1, heightRatio = 1, ratio;
      if (originalSize.width > pageContentSize.width) {
        widthRatio = pageContentSize.width / originalSize.width;
      }
      if (originalSize.height > pageContentSize.height) {
        heightRatio = pageContentSize.height / originalSize.height;
      }
      ratio = Math.min(widthRatio, heightRatio);
      return {width:originalSize.width * ratio, height:originalSize.height * ratio};
    }
    this.insertImage = function(mimetype, content, widthInPx, heightInPx) {
      if (!isEnabled) {
        return;
      }
      var paragraphElement, styleName, pageContentSize, imageSize, cssUnits = new core.CSSUnits;
      runtime.assert(widthInPx > 0 && heightInPx > 0, "Both width and height of the image should be greater than 0px.");
      imageSize = {width:widthInPx, height:heightInPx};
      paragraphElement = odfUtils.getParagraphElement(odtDocument.getCursor(inputMemberId).getNode());
      styleName = paragraphElement.getAttributeNS(textns, "style-name");
      if (styleName) {
        pageContentSize = formatting.getContentSize(styleName, "paragraph");
        imageSize = scaleToAvailableContentSize(imageSize, pageContentSize);
      }
      insertImageInternal(mimetype, content, cssUnits.convert(imageSize.width, "px", "cm") + "cm", cssUnits.convert(imageSize.height, "px", "cm") + "cm");
    };
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      callback();
    };
    function init() {
      odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      updateEnabledState();
    }
    init();
  };
  gui.ImageController.enabledChanged = "enabled/changed";
  gui.ImageSelector = function ImageSelector(odfCanvas) {
    var svgns = odf.Namespaces.svgns, imageSelectorId = "imageSelector", selectorBorderWidth = 1, squareClassNames = ["topLeft", "topRight", "bottomRight", "bottomLeft", "topMiddle", "rightMiddle", "bottomMiddle", "leftMiddle"], document = odfCanvas.getElement().ownerDocument, hasSelection = false;
    function createSelectorElement() {
      var sizerElement = odfCanvas.getSizer(), selectorElement = document.createElement("div");
      selectorElement.id = "imageSelector";
      selectorElement.style.borderWidth = selectorBorderWidth + "px";
      sizerElement.appendChild(selectorElement);
      function createDiv(className) {
        var squareElement = document.createElement("div");
        squareElement.className = className;
        selectorElement.appendChild(squareElement);
      }
      squareClassNames.forEach(createDiv);
      return selectorElement;
    }
    function getPosition(element, referenceElement) {
      var rect = element.getBoundingClientRect(), refRect = referenceElement.getBoundingClientRect(), zoomLevel = odfCanvas.getZoomLevel();
      return {left:(rect.left - refRect.left) / zoomLevel - selectorBorderWidth, top:(rect.top - refRect.top) / zoomLevel - selectorBorderWidth};
    }
    this.select = function(frameElement) {
      var selectorElement = document.getElementById(imageSelectorId), position;
      if (!selectorElement) {
        selectorElement = createSelectorElement();
      }
      hasSelection = true;
      position = getPosition(frameElement, selectorElement.parentNode);
      selectorElement.style.display = "block";
      selectorElement.style.left = position.left + "px";
      selectorElement.style.top = position.top + "px";
      selectorElement.style.width = frameElement.getAttributeNS(svgns, "width");
      selectorElement.style.height = frameElement.getAttributeNS(svgns, "height");
    };
    this.clearSelection = function() {
      var selectorElement;
      if (hasSelection) {
        selectorElement = document.getElementById(imageSelectorId);
        if (selectorElement) {
          selectorElement.style.display = "none";
        }
      }
      hasSelection = false;
    };
    this.isSelectorElement = function(node) {
      var selectorElement = document.getElementById(imageSelectorId);
      if (!selectorElement) {
        return false;
      }
      return node === selectorElement || node.parentNode === selectorElement;
    };
  };
  (function() {
    function DetectSafariCompositionError(eventManager) {
      var lastCompositionValue, suppressedKeyPress = false;
      function suppressIncorrectKeyPress(e) {
        suppressedKeyPress = e.which && String.fromCharCode(e.which) === lastCompositionValue;
        lastCompositionValue = undefined;
        return suppressedKeyPress === false;
      }
      function clearSuppression() {
        suppressedKeyPress = false;
      }
      function trapComposedValue(e) {
        lastCompositionValue = e.data;
        suppressedKeyPress = false;
      }
      function init() {
        eventManager.subscribe("textInput", clearSuppression);
        eventManager.subscribe("compositionend", trapComposedValue);
        eventManager.addFilter("keypress", suppressIncorrectKeyPress);
      }
      this.destroy = function(callback) {
        eventManager.unsubscribe("textInput", clearSuppression);
        eventManager.unsubscribe("compositionend", trapComposedValue);
        eventManager.removeFilter("keypress", suppressIncorrectKeyPress);
        callback();
      };
      init();
    }
    gui.InputMethodEditor = function InputMethodEditor(inputMemberId, eventManager) {
      var cursorns = "urn:webodf:names:cursor", localCursor = null, eventTrap = eventManager.getEventTrap(), doc = eventTrap.ownerDocument, compositionElement, processUpdates, pendingEvent = false, pendingData = "", events = new core.EventNotifier([gui.InputMethodEditor.signalCompositionStart, gui.InputMethodEditor.signalCompositionEnd]), lastCompositionData, textSerializer, filters = [], cleanup, processingFocusEvent = false;
      this.subscribe = events.subscribe;
      this.unsubscribe = events.unsubscribe;
      function setCursorComposing(state) {
        if (localCursor) {
          if (state) {
            localCursor.getNode().setAttributeNS(cursorns, "composing", "true");
          } else {
            localCursor.getNode().removeAttributeNS(cursorns, "composing");
            compositionElement.textContent = "";
          }
        }
      }
      function flushEvent() {
        if (pendingEvent) {
          pendingEvent = false;
          setCursorComposing(false);
          events.emit(gui.InputMethodEditor.signalCompositionEnd, {data:pendingData});
          pendingData = "";
        }
      }
      function addCompositionData(data) {
        pendingEvent = true;
        pendingData += data;
        processUpdates.trigger();
      }
      function synchronizeWindowSelection() {
        if (processingFocusEvent) {
          return;
        }
        processingFocusEvent = true;
        flushEvent();
        if (localCursor && localCursor.getSelectedRange().collapsed) {
          eventTrap.value = "";
        } else {
          eventTrap.value = textSerializer.writeToString(localCursor.getSelectedRange().cloneContents());
        }
        eventTrap.setSelectionRange(0, eventTrap.value.length);
        processingFocusEvent = false;
      }
      function handleCursorUpdated() {
        if (eventManager.hasFocus()) {
          processUpdates.trigger();
        }
      }
      function compositionStart() {
        lastCompositionData = undefined;
        processUpdates.cancel();
        setCursorComposing(true);
        if (!pendingEvent) {
          events.emit(gui.InputMethodEditor.signalCompositionStart, {data:""});
        }
      }
      function compositionEnd(e) {
        lastCompositionData = e.data;
        addCompositionData(e.data);
      }
      function textInput(e) {
        if (e.data !== lastCompositionData) {
          addCompositionData(e.data);
        }
        lastCompositionData = undefined;
      }
      function synchronizeCompositionText() {
        compositionElement.textContent = eventTrap.value;
      }
      this.registerCursor = function(cursor) {
        if (cursor.getMemberId() === inputMemberId) {
          localCursor = cursor;
          localCursor.getNode().appendChild(compositionElement);
          cursor.subscribe(ops.OdtCursor.signalCursorUpdated, handleCursorUpdated);
          eventManager.subscribe("input", synchronizeCompositionText);
          eventManager.subscribe("compositionupdate", synchronizeCompositionText);
        }
      };
      this.removeCursor = function(memberid) {
        if (localCursor && memberid === inputMemberId) {
          localCursor.getNode().removeChild(compositionElement);
          localCursor.unsubscribe(ops.OdtCursor.signalCursorUpdated, handleCursorUpdated);
          eventManager.unsubscribe("input", synchronizeCompositionText);
          eventManager.unsubscribe("compositionupdate", synchronizeCompositionText);
          localCursor = null;
        }
      };
      this.destroy = function(callback) {
        eventManager.unsubscribe("compositionstart", compositionStart);
        eventManager.unsubscribe("compositionend", compositionEnd);
        eventManager.unsubscribe("textInput", textInput);
        eventManager.unsubscribe("keypress", flushEvent);
        eventManager.unsubscribe("focus", synchronizeWindowSelection);
        core.Async.destroyAll(cleanup, callback);
      };
      function init() {
        textSerializer = new odf.TextSerializer;
        textSerializer.filter = new odf.OdfNodeFilter;
        eventManager.subscribe("compositionstart", compositionStart);
        eventManager.subscribe("compositionend", compositionEnd);
        eventManager.subscribe("textInput", textInput);
        eventManager.subscribe("keypress", flushEvent);
        eventManager.subscribe("focus", synchronizeWindowSelection);
        filters.push(new DetectSafariCompositionError(eventManager));
        function getDestroy(filter) {
          return filter.destroy;
        }
        cleanup = filters.map(getDestroy);
        compositionElement = doc.createElement("span");
        compositionElement.setAttribute("id", "composer");
        processUpdates = core.Task.createTimeoutTask(synchronizeWindowSelection, 1);
        cleanup.push(processUpdates.destroy);
      }
      init();
    };
    gui.InputMethodEditor.signalCompositionStart = "input/compositionstart";
    gui.InputMethodEditor.signalCompositionEnd = "input/compositionend";
  })();
  gui.MetadataController = function MetadataController(session, inputMemberId) {
    var odtDocument = session.getOdtDocument(), eventNotifier = new core.EventNotifier([gui.MetadataController.signalMetadataChanged]), readonlyProperties = ["dc:creator", "dc:date", "meta:editing-cycles", "meta:editing-duration", "meta:document-statistic"];
    function onMetadataUpdated(changes) {
      eventNotifier.emit(gui.MetadataController.signalMetadataChanged, changes);
    }
    function isWriteableMetadata(property) {
      var isWriteable = readonlyProperties.indexOf(property) === -1;
      if (!isWriteable) {
        runtime.log("Setting " + property + " is restricted.");
      }
      return isWriteable;
    }
    this.setMetadata = function(setProperties, removedProperties) {
      var filteredSetProperties = {}, filteredRemovedProperties = "", op;
      if (setProperties) {
        Object.keys(setProperties).filter(isWriteableMetadata).forEach(function(property) {
          filteredSetProperties[property] = setProperties[property];
        });
      }
      if (removedProperties) {
        filteredRemovedProperties = removedProperties.filter(isWriteableMetadata).join(",");
      }
      if (filteredRemovedProperties.length > 0 || Object.keys(filteredSetProperties).length > 0) {
        op = new ops.OpUpdateMetadata;
        op.init({memberid:inputMemberId, setProperties:filteredSetProperties, removedProperties:filteredRemovedProperties.length > 0 ? {attributes:filteredRemovedProperties} : null});
        session.enqueue([op]);
      }
    };
    this.getMetadata = function(property) {
      var namespaceUri, parts;
      runtime.assert(typeof property === "string", "Property must be a string");
      parts = property.split(":");
      runtime.assert(parts.length === 2, "Property must be a namespace-prefixed string");
      namespaceUri = odf.Namespaces.lookupNamespaceURI(parts[0]);
      runtime.assert(Boolean(namespaceUri), "Prefix must be for an ODF namespace.");
      return odtDocument.getOdfCanvas().odfContainer().getMetadata(namespaceUri, parts[1]);
    };
    this.subscribe = function(eventid, cb) {
      eventNotifier.subscribe(eventid, cb);
    };
    this.unsubscribe = function(eventid, cb) {
      eventNotifier.unsubscribe(eventid, cb);
    };
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.OdtDocument.signalMetadataUpdated, onMetadataUpdated);
      callback();
    };
    function init() {
      odtDocument.subscribe(ops.OdtDocument.signalMetadataUpdated, onMetadataUpdated);
    }
    init();
  };
  gui.MetadataController.signalMetadataChanged = "metadata/changed";
  gui.PasteController = function PasteController(session, sessionConstraints, sessionContext, inputMemberId) {
    var odtDocument = session.getOdtDocument(), isEnabled = false, textns = odf.Namespaces.textns, NEXT = core.StepDirection.NEXT, odfUtils = odf.OdfUtils;
    function updateEnabledState() {
      if (sessionConstraints.getState(gui.CommonConstraints.EDIT.REVIEW_MODE) === true) {
        isEnabled = sessionContext.isLocalCursorWithinOwnAnnotation();
      } else {
        isEnabled = true;
      }
    }
    function onCursorEvent(cursor) {
      if (cursor.getMemberId() === inputMemberId) {
        updateEnabledState();
      }
    }
    this.isEnabled = function() {
      return isEnabled;
    };
    this.paste = function(data) {
      if (!isEnabled) {
        return;
      }
      var originalCursorPosition = odtDocument.getCursorPosition(inputMemberId), cursorNode = odtDocument.getCursor(inputMemberId).getNode(), originalParagraph = odfUtils.getParagraphElement(cursorNode), paragraphStyle = originalParagraph.getAttributeNS(textns, "style-name") || "", cursorPosition = originalCursorPosition, operations = [], currentParagraphStartPosition = odtDocument.convertDomPointToCursorStep(originalParagraph, 0, NEXT), paragraphs;
      paragraphs = data.replace(/\r/g, "").split("\n");
      paragraphs.forEach(function(text) {
        var insertTextOp = new ops.OpInsertText, splitParagraphOp = new ops.OpSplitParagraph;
        insertTextOp.init({memberid:inputMemberId, position:cursorPosition, text:text, moveCursor:true});
        operations.push(insertTextOp);
        cursorPosition += text.length;
        splitParagraphOp.init({memberid:inputMemberId, position:cursorPosition, paragraphStyleName:paragraphStyle, sourceParagraphPosition:currentParagraphStartPosition, moveCursor:true});
        operations.push(splitParagraphOp);
        cursorPosition += 1;
        currentParagraphStartPosition = cursorPosition;
      });
      operations.pop();
      session.enqueue(operations);
    };
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      callback();
    };
    function init() {
      odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      updateEnabledState();
    }
    init();
  };
  gui.ClosestXOffsetScanner = function(offset) {
    var self = this, closestDiff, LEFT_TO_RIGHT = gui.StepInfo.VisualDirection.LEFT_TO_RIGHT;
    this.token = undefined;
    function isFurtherFromOffset(edgeOffset) {
      if (edgeOffset !== null && closestDiff !== undefined) {
        return Math.abs(edgeOffset - offset) > closestDiff;
      }
      return false;
    }
    function updateDiffIfSmaller(edge) {
      if (edge !== null && isFurtherFromOffset(edge) === false) {
        closestDiff = Math.abs(edge - offset);
      }
    }
    this.process = function(stepInfo, previousRect, nextRect) {
      var edge1, edge2;
      if (stepInfo.visualDirection === LEFT_TO_RIGHT) {
        edge1 = previousRect && previousRect.right;
        edge2 = nextRect && nextRect.left;
      } else {
        edge1 = previousRect && previousRect.left;
        edge2 = nextRect && nextRect.right;
      }
      if (isFurtherFromOffset(edge1) || isFurtherFromOffset(edge2)) {
        return true;
      }
      if (previousRect || nextRect) {
        updateDiffIfSmaller(edge1);
        updateDiffIfSmaller(edge2);
        self.token = stepInfo.token;
      }
      return false;
    };
  };
  gui.LineBoundaryScanner = function() {
    var self = this, lineRect = null, MIN_OVERLAP_THRESHOLD = .4;
    function verticalOverlapPercent(rect1, rect2) {
      var rect1Height = rect1.bottom - rect1.top, rect2Height = rect2.bottom - rect2.top, minRectHeight = Math.min(rect1Height, rect2Height), intersectTop = Math.max(rect1.top, rect2.top), intersectBottom = Math.min(rect1.bottom, rect2.bottom), overlapHeight = intersectBottom - intersectTop;
      return minRectHeight > 0 ? overlapHeight / minRectHeight : 0;
    }
    function isLineBoundary(nextRect) {
      if (lineRect) {
        return verticalOverlapPercent(lineRect, nextRect) <= MIN_OVERLAP_THRESHOLD;
      }
      return false;
    }
    function combineRects(rect1, rect2) {
      return {left:Math.min(rect1.left, rect2.left), right:Math.max(rect1.right, rect2.right), top:Math.min(rect1.top, rect2.top), bottom:Math.min(rect1.bottom, rect2.bottom)};
    }
    function growRect(originalRect, newRect) {
      if (originalRect && newRect) {
        return combineRects(originalRect, newRect);
      }
      return originalRect || newRect;
    }
    this.token = undefined;
    this.process = function(stepInfo, previousRect, nextRect) {
      var isOverLineBoundary = nextRect && isLineBoundary(nextRect);
      if (previousRect && (!nextRect || isOverLineBoundary)) {
        self.token = stepInfo.token;
      }
      if (isOverLineBoundary) {
        return true;
      }
      lineRect = growRect(lineRect, previousRect);
      return false;
    };
  };
  gui.ParagraphBoundaryScanner = function() {
    var self = this, isInitialised = false, lastParagraph, odfUtils = odf.OdfUtils;
    this.token = undefined;
    this.process = function(stepInfo) {
      var currentParagraph = odfUtils.getParagraphElement(stepInfo.container());
      if (!isInitialised) {
        lastParagraph = currentParagraph;
        isInitialised = true;
      }
      if (lastParagraph !== currentParagraph) {
        return true;
      }
      self.token = stepInfo.token;
      return false;
    };
  };
  odf.WordBoundaryFilter = function WordBoundaryFilter(odtDocument, includeWhitespace) {
    var TEXT_NODE = Node.TEXT_NODE, ELEMENT_NODE = Node.ELEMENT_NODE, odfUtils = odf.OdfUtils, punctuation = /[!-#%-*,-\/:-;?-@\[-\]_{}\u00a1\u00ab\u00b7\u00bb\u00bf;\u00b7\u055a-\u055f\u0589-\u058a\u05be\u05c0\u05c3\u05c6\u05f3-\u05f4\u0609-\u060a\u060c-\u060d\u061b\u061e-\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0964-\u0965\u0970\u0df4\u0e4f\u0e5a-\u0e5b\u0f04-\u0f12\u0f3a-\u0f3d\u0f85\u0fd0-\u0fd4\u104a-\u104f\u10fb\u1361-\u1368\u166d-\u166e\u169b-\u169c\u16eb-\u16ed\u1735-\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u180a\u1944-\u1945\u19de-\u19df\u1a1e-\u1a1f\u1b5a-\u1b60\u1c3b-\u1c3f\u1c7e-\u1c7f\u2000-\u206e\u207d-\u207e\u208d-\u208e\u3008-\u3009\u2768-\u2775\u27c5-\u27c6\u27e6-\u27ef\u2983-\u2998\u29d8-\u29db\u29fc-\u29fd\u2cf9-\u2cfc\u2cfe-\u2cff\u2e00-\u2e7e\u3000-\u303f\u30a0\u30fb\ua60d-\ua60f\ua673\ua67e\ua874-\ua877\ua8ce-\ua8cf\ua92e-\ua92f\ua95f\uaa5c-\uaa5f\ufd3e-\ufd3f\ufe10-\ufe19\ufe30-\ufe52\ufe54-\ufe61\ufe63\ufe68\ufe6a-\ufe6b\uff01-\uff03\uff05-\uff0a\uff0c-\uff0f\uff1a-\uff1b\uff1f-\uff20\uff3b-\uff3d\uff3f\uff5b\uff5d\uff5f-\uff65]|\ud800[\udd00-\udd01\udf9f\udfd0]|\ud802[\udd1f\udd3f\ude50-\ude58]|\ud809[\udc00-\udc7e]/,
    spacing = /\s/, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT, TRAILING = odf.WordBoundaryFilter.IncludeWhitespace.TRAILING, LEADING = odf.WordBoundaryFilter.IncludeWhitespace.LEADING, NeighborType = {NO_NEIGHBOUR:0, SPACE_CHAR:1, PUNCTUATION_CHAR:2, WORD_CHAR:3, OTHER:4};
    function findHigherNeighborNode(node, direction, nodeFilter) {
      var neighboringNode = null, rootNode = odtDocument.getRootNode(), unfilteredCandidate;
      while (node !== rootNode && node !== null && neighboringNode === null) {
        unfilteredCandidate = direction < 0 ? node.previousSibling : node.nextSibling;
        if (nodeFilter(unfilteredCandidate) === NodeFilter.FILTER_ACCEPT) {
          neighboringNode = unfilteredCandidate;
        }
        node = node.parentNode;
      }
      return neighboringNode;
    }
    function typeOfNeighbor(node, getOffset) {
      var neighboringChar;
      if (node === null) {
        return NeighborType.NO_NEIGHBOUR;
      }
      if (odfUtils.isCharacterElement(node)) {
        return NeighborType.SPACE_CHAR;
      }
      if (node.nodeType === TEXT_NODE || odfUtils.isTextSpan(node) || odfUtils.isHyperlink(node)) {
        neighboringChar = node.textContent.charAt(getOffset());
        if (spacing.test(neighboringChar)) {
          return NeighborType.SPACE_CHAR;
        }
        if (punctuation.test(neighboringChar)) {
          return NeighborType.PUNCTUATION_CHAR;
        }
        return NeighborType.WORD_CHAR;
      }
      return NeighborType.OTHER;
    }
    this.acceptPosition = function(iterator) {
      var container = iterator.container(), leftNode = iterator.leftNode(), rightNode = iterator.rightNode(), getRightCharOffset = iterator.unfilteredDomOffset, getLeftCharOffset = function() {
        return iterator.unfilteredDomOffset() - 1;
      }, leftNeighborType, rightNeighborType;
      if (container.nodeType === ELEMENT_NODE) {
        if (rightNode === null) {
          rightNode = findHigherNeighborNode(container, 1, iterator.getNodeFilter());
        }
        if (leftNode === null) {
          leftNode = findHigherNeighborNode(container, -1, iterator.getNodeFilter());
        }
      }
      if (container !== rightNode) {
        getRightCharOffset = function() {
          return 0;
        };
      }
      if (container !== leftNode && leftNode !== null) {
        getLeftCharOffset = function() {
          return leftNode.textContent.length - 1;
        };
      }
      leftNeighborType = typeOfNeighbor(leftNode, getLeftCharOffset);
      rightNeighborType = typeOfNeighbor(rightNode, getRightCharOffset);
      if (leftNeighborType === NeighborType.WORD_CHAR && rightNeighborType === NeighborType.WORD_CHAR || leftNeighborType === NeighborType.PUNCTUATION_CHAR && rightNeighborType === NeighborType.PUNCTUATION_CHAR || includeWhitespace === TRAILING && leftNeighborType !== NeighborType.NO_NEIGHBOUR && rightNeighborType === NeighborType.SPACE_CHAR || includeWhitespace === LEADING && leftNeighborType === NeighborType.SPACE_CHAR && rightNeighborType !== NeighborType.NO_NEIGHBOUR) {
        return FILTER_REJECT;
      }
      return FILTER_ACCEPT;
    };
  };
  odf.WordBoundaryFilter.IncludeWhitespace = {None:0, TRAILING:1, LEADING:2};
  gui.SelectionController = function SelectionController(session, inputMemberId) {
    var odtDocument = session.getOdtDocument(), domUtils = core.DomUtils, odfUtils = odf.OdfUtils, baseFilter = odtDocument.getPositionFilter(), guiStepUtils = new gui.GuiStepUtils, rootFilter = odtDocument.createRootFilter(inputMemberId), caretXPositionLocator = null, lastXPosition, resetLastXPositionTask, TRAILING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.TRAILING, LEADING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.LEADING, PREVIOUS = core.StepDirection.PREVIOUS, NEXT = core.StepDirection.NEXT,
    UPDOWN_NAVIGATION_RESET_DELAY_MS = 2E3;
    function resetLastXPosition(op) {
      var opspec = op.spec();
      if (op.isEdit || opspec.memberid === inputMemberId) {
        lastXPosition = undefined;
        resetLastXPositionTask.cancel();
      }
    }
    function createKeyboardStepIterator() {
      var cursor = odtDocument.getCursor(inputMemberId), node = cursor.getNode();
      return odtDocument.createStepIterator(node, 0, [baseFilter, rootFilter], odtDocument.getRootElement(node));
    }
    function createWordBoundaryStepIterator(node, offset, includeWhitespace) {
      var wordBoundaryFilter = new odf.WordBoundaryFilter(odtDocument, includeWhitespace), nodeRoot = odtDocument.getRootElement(node) || odtDocument.getRootNode(), nodeRootFilter = odtDocument.createRootFilter(nodeRoot);
      return odtDocument.createStepIterator(node, offset, [baseFilter, nodeRootFilter, wordBoundaryFilter], nodeRoot);
    }
    function selectionToRange(selection) {
      var hasForwardSelection = domUtils.comparePoints(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset) >= 0, range = selection.focusNode.ownerDocument.createRange();
      if (hasForwardSelection) {
        range.setStart(selection.anchorNode, selection.anchorOffset);
        range.setEnd(selection.focusNode, selection.focusOffset);
      } else {
        range.setStart(selection.focusNode, selection.focusOffset);
        range.setEnd(selection.anchorNode, selection.anchorOffset);
      }
      return {range:range, hasForwardSelection:hasForwardSelection};
    }
    this.selectionToRange = selectionToRange;
    function rangeToSelection(range, hasForwardSelection) {
      if (hasForwardSelection) {
        return {anchorNode:range.startContainer, anchorOffset:range.startOffset, focusNode:range.endContainer, focusOffset:range.endOffset};
      }
      return {anchorNode:range.endContainer, anchorOffset:range.endOffset, focusNode:range.startContainer, focusOffset:range.startOffset};
    }
    this.rangeToSelection = rangeToSelection;
    function createOpMoveCursor(position, length, selectionType) {
      var op = new ops.OpMoveCursor;
      op.init({memberid:inputMemberId, position:position, length:length || 0, selectionType:selectionType});
      return op;
    }
    function moveCursorFocusPoint(focusNode, focusOffset, extend) {
      var cursor, newSelection, newCursorSelection;
      cursor = odtDocument.getCursor(inputMemberId);
      newSelection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection());
      newSelection.focusNode = focusNode;
      newSelection.focusOffset = focusOffset;
      if (!extend) {
        newSelection.anchorNode = newSelection.focusNode;
        newSelection.anchorOffset = newSelection.focusOffset;
      }
      newCursorSelection = odtDocument.convertDomToCursorRange(newSelection);
      session.enqueue([createOpMoveCursor(newCursorSelection.position, newCursorSelection.length)]);
    }
    function selectImage(frameNode) {
      var frameRoot = odtDocument.getRootElement(frameNode), frameRootFilter = odtDocument.createRootFilter(frameRoot), stepIterator = odtDocument.createStepIterator(frameNode, 0, [frameRootFilter, odtDocument.getPositionFilter()], frameRoot), anchorNode, anchorOffset, newSelection, op;
      if (!stepIterator.roundToPreviousStep()) {
        runtime.assert(false, "No walkable position before frame");
      }
      anchorNode = stepIterator.container();
      anchorOffset = stepIterator.offset();
      stepIterator.setPosition(frameNode, frameNode.childNodes.length);
      if (!stepIterator.roundToNextStep()) {
        runtime.assert(false, "No walkable position after frame");
      }
      newSelection = odtDocument.convertDomToCursorRange({anchorNode:anchorNode, anchorOffset:anchorOffset, focusNode:stepIterator.container(), focusOffset:stepIterator.offset()});
      op = createOpMoveCursor(newSelection.position, newSelection.length, ops.OdtCursor.RegionSelection);
      session.enqueue([op]);
    }
    this.selectImage = selectImage;
    function expandToWordBoundaries(range) {
      var stepIterator;
      stepIterator = createWordBoundaryStepIterator(range.startContainer, range.startOffset, TRAILING_SPACE);
      if (stepIterator.roundToPreviousStep()) {
        range.setStart(stepIterator.container(), stepIterator.offset());
      }
      stepIterator = createWordBoundaryStepIterator(range.endContainer, range.endOffset, LEADING_SPACE);
      if (stepIterator.roundToNextStep()) {
        range.setEnd(stepIterator.container(), stepIterator.offset());
      }
    }
    this.expandToWordBoundaries = expandToWordBoundaries;
    function expandToParagraphBoundaries(range) {
      var paragraphs = odfUtils.getParagraphElements(range), startParagraph = paragraphs[0], endParagraph = paragraphs[paragraphs.length - 1];
      if (startParagraph) {
        range.setStart(startParagraph, 0);
      }
      if (endParagraph) {
        if (odfUtils.isParagraph(range.endContainer) && range.endOffset === 0) {
          range.setEndBefore(endParagraph);
        } else {
          range.setEnd(endParagraph, endParagraph.childNodes.length);
        }
      }
    }
    this.expandToParagraphBoundaries = expandToParagraphBoundaries;
    function roundToClosestStep(root, filters, range, modifyStart) {
      var stepIterator, node, offset;
      if (modifyStart) {
        node = range.startContainer;
        offset = range.startOffset;
      } else {
        node = range.endContainer;
        offset = range.endOffset;
      }
      if (!domUtils.containsNode(root, node)) {
        if (domUtils.comparePoints(root, 0, node, offset) < 0) {
          offset = 0;
        } else {
          offset = root.childNodes.length;
        }
        node = root;
      }
      stepIterator = odtDocument.createStepIterator(node, offset, filters, odfUtils.getParagraphElement(node) || root);
      if (!stepIterator.roundToClosestStep()) {
        runtime.assert(false, "No step found in requested range");
      }
      if (modifyStart) {
        range.setStart(stepIterator.container(), stepIterator.offset());
      } else {
        range.setEnd(stepIterator.container(), stepIterator.offset());
      }
    }
    function selectRange(range, hasForwardSelection, clickCount) {
      var canvasElement = odtDocument.getOdfCanvas().getElement(), validSelection, startInsideCanvas, endInsideCanvas, existingSelection, newSelection, anchorRoot, filters = [baseFilter], op;
      startInsideCanvas = domUtils.containsNode(canvasElement, range.startContainer);
      endInsideCanvas = domUtils.containsNode(canvasElement, range.endContainer);
      if (!startInsideCanvas && !endInsideCanvas) {
        return;
      }
      if (startInsideCanvas && endInsideCanvas) {
        if (clickCount === 2) {
          expandToWordBoundaries(range);
        } else {
          if (clickCount >= 3) {
            expandToParagraphBoundaries(range);
          }
        }
      }
      if (hasForwardSelection) {
        anchorRoot = odtDocument.getRootElement(range.startContainer);
      } else {
        anchorRoot = odtDocument.getRootElement(range.endContainer);
      }
      if (!anchorRoot) {
        anchorRoot = odtDocument.getRootNode();
      }
      filters.push(odtDocument.createRootFilter(anchorRoot));
      roundToClosestStep(anchorRoot, filters, range, true);
      roundToClosestStep(anchorRoot, filters, range, false);
      validSelection = rangeToSelection(range, hasForwardSelection);
      newSelection = odtDocument.convertDomToCursorRange(validSelection);
      existingSelection = odtDocument.getCursorSelection(inputMemberId);
      if (newSelection.position !== existingSelection.position || newSelection.length !== existingSelection.length) {
        op = createOpMoveCursor(newSelection.position, newSelection.length, ops.OdtCursor.RangeSelection);
        session.enqueue([op]);
      }
    }
    this.selectRange = selectRange;
    function moveCursor(direction, extend) {
      var stepIterator = createKeyboardStepIterator();
      if (stepIterator.advanceStep(direction)) {
        moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend);
      }
    }
    function moveCursorToLeft() {
      moveCursor(PREVIOUS, false);
      return true;
    }
    this.moveCursorToLeft = moveCursorToLeft;
    function moveCursorToRight() {
      moveCursor(NEXT, false);
      return true;
    }
    this.moveCursorToRight = moveCursorToRight;
    function extendSelectionToLeft() {
      moveCursor(PREVIOUS, true);
      return true;
    }
    this.extendSelectionToLeft = extendSelectionToLeft;
    function extendSelectionToRight() {
      moveCursor(NEXT, true);
      return true;
    }
    this.extendSelectionToRight = extendSelectionToRight;
    this.setCaretXPositionLocator = function(locator) {
      caretXPositionLocator = locator;
    };
    function moveCursorByLine(direction, extend) {
      var stepIterator, currentX = lastXPosition, stepScanners = [new gui.LineBoundaryScanner, new gui.ParagraphBoundaryScanner];
      if (currentX === undefined && caretXPositionLocator) {
        currentX = caretXPositionLocator();
      }
      if (isNaN(currentX)) {
        return;
      }
      stepIterator = createKeyboardStepIterator();
      if (!guiStepUtils.moveToFilteredStep(stepIterator, direction, stepScanners)) {
        return;
      }
      if (!stepIterator.advanceStep(direction)) {
        return;
      }
      stepScanners = [new gui.ClosestXOffsetScanner(currentX), new gui.LineBoundaryScanner, new gui.ParagraphBoundaryScanner];
      if (guiStepUtils.moveToFilteredStep(stepIterator, direction, stepScanners)) {
        moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend);
        lastXPosition = currentX;
        resetLastXPositionTask.restart();
      }
    }
    function moveCursorUp() {
      moveCursorByLine(PREVIOUS, false);
      return true;
    }
    this.moveCursorUp = moveCursorUp;
    function moveCursorDown() {
      moveCursorByLine(NEXT, false);
      return true;
    }
    this.moveCursorDown = moveCursorDown;
    function extendSelectionUp() {
      moveCursorByLine(PREVIOUS, true);
      return true;
    }
    this.extendSelectionUp = extendSelectionUp;
    function extendSelectionDown() {
      moveCursorByLine(NEXT, true);
      return true;
    }
    this.extendSelectionDown = extendSelectionDown;
    function moveCursorToLineBoundary(direction, extend) {
      var stepIterator = createKeyboardStepIterator(), stepScanners = [new gui.LineBoundaryScanner, new gui.ParagraphBoundaryScanner];
      if (guiStepUtils.moveToFilteredStep(stepIterator, direction, stepScanners)) {
        moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend);
      }
    }
    function moveCursorByWord(direction, extend) {
      var cursor = odtDocument.getCursor(inputMemberId), newSelection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection()), stepIterator = createWordBoundaryStepIterator(newSelection.focusNode, newSelection.focusOffset, TRAILING_SPACE);
      if (stepIterator.advanceStep(direction)) {
        moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend);
      }
    }
    function moveCursorBeforeWord() {
      moveCursorByWord(PREVIOUS, false);
      return true;
    }
    this.moveCursorBeforeWord = moveCursorBeforeWord;
    function moveCursorPastWord() {
      moveCursorByWord(NEXT, false);
      return true;
    }
    this.moveCursorPastWord = moveCursorPastWord;
    function extendSelectionBeforeWord() {
      moveCursorByWord(PREVIOUS, true);
      return true;
    }
    this.extendSelectionBeforeWord = extendSelectionBeforeWord;
    function extendSelectionPastWord() {
      moveCursorByWord(NEXT, true);
      return true;
    }
    this.extendSelectionPastWord = extendSelectionPastWord;
    function moveCursorToLineStart() {
      moveCursorToLineBoundary(PREVIOUS, false);
      return true;
    }
    this.moveCursorToLineStart = moveCursorToLineStart;
    function moveCursorToLineEnd() {
      moveCursorToLineBoundary(NEXT, false);
      return true;
    }
    this.moveCursorToLineEnd = moveCursorToLineEnd;
    function extendSelectionToLineStart() {
      moveCursorToLineBoundary(PREVIOUS, true);
      return true;
    }
    this.extendSelectionToLineStart = extendSelectionToLineStart;
    function extendSelectionToLineEnd() {
      moveCursorToLineBoundary(NEXT, true);
      return true;
    }
    this.extendSelectionToLineEnd = extendSelectionToLineEnd;
    function adjustSelectionByNode(direction, extend, getContainmentNode) {
      var validStepFound = false, cursor = odtDocument.getCursor(inputMemberId), containmentNode, selection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection()), rootElement = odtDocument.getRootElement(selection.focusNode), stepIterator;
      runtime.assert(Boolean(rootElement), "SelectionController: Cursor outside root");
      stepIterator = odtDocument.createStepIterator(selection.focusNode, selection.focusOffset, [baseFilter, rootFilter], rootElement);
      stepIterator.roundToClosestStep();
      if (!stepIterator.advanceStep(direction)) {
        return;
      }
      containmentNode = getContainmentNode(stepIterator.container());
      if (!containmentNode) {
        return;
      }
      if (direction === PREVIOUS) {
        stepIterator.setPosition(containmentNode, 0);
        validStepFound = stepIterator.roundToNextStep();
      } else {
        stepIterator.setPosition(containmentNode, containmentNode.childNodes.length);
        validStepFound = stepIterator.roundToPreviousStep();
      }
      if (validStepFound) {
        moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend);
      }
    }
    this.extendSelectionToParagraphStart = function() {
      adjustSelectionByNode(PREVIOUS, true, odfUtils.getParagraphElement);
      return true;
    };
    this.extendSelectionToParagraphEnd = function() {
      adjustSelectionByNode(NEXT, true, odfUtils.getParagraphElement);
      return true;
    };
    this.moveCursorToParagraphStart = function() {
      adjustSelectionByNode(PREVIOUS, false, odfUtils.getParagraphElement);
      return true;
    };
    this.moveCursorToParagraphEnd = function() {
      adjustSelectionByNode(NEXT, false, odfUtils.getParagraphElement);
      return true;
    };
    this.moveCursorToDocumentStart = function() {
      adjustSelectionByNode(PREVIOUS, false, odtDocument.getRootElement);
      return true;
    };
    this.moveCursorToDocumentEnd = function() {
      adjustSelectionByNode(NEXT, false, odtDocument.getRootElement);
      return true;
    };
    this.extendSelectionToDocumentStart = function() {
      adjustSelectionByNode(PREVIOUS, true, odtDocument.getRootElement);
      return true;
    };
    this.extendSelectionToDocumentEnd = function() {
      adjustSelectionByNode(NEXT, true, odtDocument.getRootElement);
      return true;
    };
    function extendSelectionToEntireDocument() {
      var cursor = odtDocument.getCursor(inputMemberId), rootElement = odtDocument.getRootElement(cursor.getNode()), anchorNode, anchorOffset, stepIterator, newCursorSelection;
      runtime.assert(Boolean(rootElement), "SelectionController: Cursor outside root");
      stepIterator = odtDocument.createStepIterator(rootElement, 0, [baseFilter, rootFilter], rootElement);
      stepIterator.roundToClosestStep();
      anchorNode = stepIterator.container();
      anchorOffset = stepIterator.offset();
      stepIterator.setPosition(rootElement, rootElement.childNodes.length);
      stepIterator.roundToClosestStep();
      newCursorSelection = odtDocument.convertDomToCursorRange({anchorNode:anchorNode, anchorOffset:anchorOffset, focusNode:stepIterator.container(), focusOffset:stepIterator.offset()});
      session.enqueue([createOpMoveCursor(newCursorSelection.position, newCursorSelection.length)]);
      return true;
    }
    this.extendSelectionToEntireDocument = extendSelectionToEntireDocument;
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.OdtDocument.signalOperationStart, resetLastXPosition);
      core.Async.destroyAll([resetLastXPositionTask.destroy], callback);
    };
    function init() {
      resetLastXPositionTask = core.Task.createTimeoutTask(function() {
        lastXPosition = undefined;
      }, UPDOWN_NAVIGATION_RESET_DELAY_MS);
      odtDocument.subscribe(ops.OdtDocument.signalOperationStart, resetLastXPosition);
    }
    init();
  };
  gui.TextController = function TextController(session, sessionConstraints, sessionContext, inputMemberId, directStyleOp, paragraphStyleOps) {
    var odtDocument = session.getOdtDocument(), odfUtils = odf.OdfUtils, domUtils = core.DomUtils, BACKWARD = false, FORWARD = true, isEnabled = false, textns = odf.Namespaces.textns, NEXT = core.StepDirection.NEXT;
    function updateEnabledState() {
      if (sessionConstraints.getState(gui.CommonConstraints.EDIT.REVIEW_MODE) === true) {
        isEnabled = sessionContext.isLocalCursorWithinOwnAnnotation();
      } else {
        isEnabled = true;
      }
    }
    function onCursorEvent(cursor) {
      if (cursor.getMemberId() === inputMemberId) {
        updateEnabledState();
      }
    }
    this.isEnabled = function() {
      return isEnabled;
    };
    function domToCursorRange(range, subTree, withRootFilter) {
      var filters = [odtDocument.getPositionFilter()], startStep, endStep, stepIterator;
      if (withRootFilter) {
        filters.push(odtDocument.createRootFilter(range.startContainer));
      }
      stepIterator = odtDocument.createStepIterator(range.startContainer, range.startOffset, filters, subTree);
      if (!stepIterator.roundToClosestStep()) {
        runtime.assert(false, "No walkable step found in paragraph element at range start");
      }
      startStep = odtDocument.convertDomPointToCursorStep(stepIterator.container(), stepIterator.offset());
      if (range.collapsed) {
        endStep = startStep;
      } else {
        stepIterator.setPosition(range.endContainer, range.endOffset);
        if (!stepIterator.roundToClosestStep()) {
          runtime.assert(false, "No walkable step found in paragraph element at range end");
        }
        endStep = odtDocument.convertDomPointToCursorStep(stepIterator.container(), stepIterator.offset());
      }
      return {position:startStep, length:endStep - startStep};
    }
    function createRemoveSelectionOps(range) {
      var firstParagraph, lastParagraph, mergedParagraphStyleName, previousParagraphStart, paragraphs = odfUtils.getParagraphElements(range), paragraphRange = range.cloneRange(), operations = [];
      firstParagraph = paragraphs[0];
      if (paragraphs.length > 1) {
        if (odfUtils.hasNoODFContent(firstParagraph)) {
          lastParagraph = paragraphs[paragraphs.length - 1];
          mergedParagraphStyleName = lastParagraph.getAttributeNS(odf.Namespaces.textns, "style-name") || "";
        } else {
          mergedParagraphStyleName = firstParagraph.getAttributeNS(odf.Namespaces.textns, "style-name") || "";
        }
      }
      paragraphs.forEach(function(paragraph, index) {
        var paragraphStart, removeLimits, intersectionRange, removeOp, mergeOp;
        paragraphRange.setStart(paragraph, 0);
        paragraphRange.collapse(true);
        paragraphStart = domToCursorRange(paragraphRange, paragraph, false).position;
        if (index > 0) {
          mergeOp = new ops.OpMergeParagraph;
          mergeOp.init({memberid:inputMemberId, paragraphStyleName:mergedParagraphStyleName, destinationStartPosition:previousParagraphStart, sourceStartPosition:paragraphStart, moveCursor:index === 1});
          operations.unshift(mergeOp);
        }
        previousParagraphStart = paragraphStart;
        paragraphRange.selectNodeContents(paragraph);
        intersectionRange = domUtils.rangeIntersection(paragraphRange, range);
        if (intersectionRange) {
          removeLimits = domToCursorRange(intersectionRange, paragraph, true);
          if (removeLimits.length > 0) {
            removeOp = new ops.OpRemoveText;
            removeOp.init({memberid:inputMemberId, position:removeLimits.position, length:removeLimits.length});
            operations.unshift(removeOp);
          }
        }
      });
      return operations;
    }
    function toForwardSelection(selection) {
      if (selection.length < 0) {
        selection.position += selection.length;
        selection.length = -selection.length;
      }
      return selection;
    }
    this.enqueueParagraphSplittingOps = function() {
      if (!isEnabled) {
        return false;
      }
      var cursor = odtDocument.getCursor(inputMemberId), range = cursor.getSelectedRange(), selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), op, operations = [], styleOps, originalParagraph = odfUtils.getParagraphElement(cursor.getNode()), paragraphStyle = originalParagraph.getAttributeNS(textns, "style-name") || "";
      if (selection.length > 0) {
        operations = operations.concat(createRemoveSelectionOps(range));
      }
      op = new ops.OpSplitParagraph;
      op.init({memberid:inputMemberId, position:selection.position, paragraphStyleName:paragraphStyle, sourceParagraphPosition:odtDocument.convertDomPointToCursorStep(originalParagraph, 0, NEXT), moveCursor:true});
      operations.push(op);
      if (paragraphStyleOps) {
        styleOps = paragraphStyleOps(selection.position + 1);
        operations = operations.concat(styleOps);
      }
      session.enqueue(operations);
      return true;
    };
    function createStepIterator(cursorNode) {
      var cursorRoot = odtDocument.getRootElement(cursorNode), filters = [odtDocument.getPositionFilter(), odtDocument.createRootFilter(cursorRoot)];
      return odtDocument.createStepIterator(cursorNode, 0, filters, cursorRoot);
    }
    function removeTextInDirection(isForward) {
      if (!isEnabled) {
        return false;
      }
      var cursorNode, range = odtDocument.getCursor(inputMemberId).getSelectedRange().cloneRange(), selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), stepIterator;
      if (selection.length === 0) {
        selection = undefined;
        cursorNode = odtDocument.getCursor(inputMemberId).getNode();
        stepIterator = createStepIterator(cursorNode);
        if (stepIterator.roundToClosestStep() && (isForward ? stepIterator.nextStep() : stepIterator.previousStep())) {
          selection = toForwardSelection(odtDocument.convertDomToCursorRange({anchorNode:cursorNode, anchorOffset:0, focusNode:stepIterator.container(), focusOffset:stepIterator.offset()}));
          if (isForward) {
            range.setStart(cursorNode, 0);
            range.setEnd(stepIterator.container(), stepIterator.offset());
          } else {
            range.setStart(stepIterator.container(), stepIterator.offset());
            range.setEnd(cursorNode, 0);
          }
        }
      }
      if (selection) {
        session.enqueue(createRemoveSelectionOps(range));
      }
      return selection !== undefined;
    }
    this.removeTextByBackspaceKey = function() {
      return removeTextInDirection(BACKWARD);
    };
    this.removeTextByDeleteKey = function() {
      return removeTextInDirection(FORWARD);
    };
    this.removeCurrentSelection = function() {
      if (!isEnabled) {
        return false;
      }
      var range = odtDocument.getCursor(inputMemberId).getSelectedRange();
      session.enqueue(createRemoveSelectionOps(range));
      return true;
    };
    function insertText(text) {
      if (!isEnabled) {
        return;
      }
      var range = odtDocument.getCursor(inputMemberId).getSelectedRange(), selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), op, stylingOp, operations = [], useCachedStyle = false;
      if (selection.length > 0) {
        operations = operations.concat(createRemoveSelectionOps(range));
        useCachedStyle = true;
      }
      op = new ops.OpInsertText;
      op.init({memberid:inputMemberId, position:selection.position, text:text, moveCursor:true});
      operations.push(op);
      if (directStyleOp) {
        stylingOp = directStyleOp(selection.position, text.length, useCachedStyle);
        if (stylingOp) {
          operations.push(stylingOp);
        }
      }
      session.enqueue(operations);
    }
    this.insertText = insertText;
    this.destroy = function(callback) {
      odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      callback();
    };
    function init() {
      odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent);
      sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState);
      updateEnabledState();
    }
    init();
  };
  gui.UndoManager = function UndoManager() {
  };
  gui.UndoManager.prototype.subscribe = function(signal, callback) {
  };
  gui.UndoManager.prototype.unsubscribe = function(signal, callback) {
  };
  gui.UndoManager.prototype.setDocument = function(newDocument) {
  };
  gui.UndoManager.prototype.setInitialState = function() {
  };
  gui.UndoManager.prototype.initialize = function() {
  };
  gui.UndoManager.prototype.purgeInitialState = function() {
  };
  gui.UndoManager.prototype.setPlaybackFunction = function(playback_func) {
  };
  gui.UndoManager.prototype.hasUndoStates = function() {
  };
  gui.UndoManager.prototype.hasRedoStates = function() {
  };
  gui.UndoManager.prototype.moveForward = function(states) {
  };
  gui.UndoManager.prototype.moveBackward = function(states) {
  };
  gui.UndoManager.prototype.onOperationExecuted = function(op) {
  };
  gui.UndoManager.prototype.isDocumentModified = function() {
  };
  gui.UndoManager.prototype.setDocumentModified = function(modified) {
  };
  gui.UndoManager.signalUndoStackChanged = "undoStackChanged";
  gui.UndoManager.signalUndoStateCreated = "undoStateCreated";
  gui.UndoManager.signalUndoStateModified = "undoStateModified";
  gui.UndoManager.signalDocumentModifiedChanged = "documentModifiedChanged";
  gui.SessionControllerOptions = function() {
    this.directTextStylingEnabled = false;
    this.directParagraphStylingEnabled = false;
    this.annotationsEnabled = false;
  };
  (function() {
    var FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT;
    gui.SessionController = function SessionController(session, inputMemberId, shadowCursor, args) {
      var window = runtime.getWindow(), odtDocument = session.getOdtDocument(), sessionConstraints = new gui.SessionConstraints, sessionContext = new gui.SessionContext(session, inputMemberId), domUtils = core.DomUtils, odfUtils = odf.OdfUtils, mimeDataExporter = new gui.MimeDataExporter, clipboard = new gui.Clipboard(mimeDataExporter), keyDownHandler = new gui.KeyboardHandler, keyPressHandler = new gui.KeyboardHandler, keyUpHandler = new gui.KeyboardHandler, clickStartedWithinCanvas = false, objectNameGenerator =
      new odf.ObjectNameGenerator(odtDocument.getOdfCanvas().odfContainer(), inputMemberId), isMouseMoved = false, mouseDownRootFilter = null, handleMouseClickTimeoutId, undoManager = null, eventManager = new gui.EventManager(odtDocument), annotationsEnabled = args.annotationsEnabled, annotationController = new gui.AnnotationController(session, sessionConstraints, inputMemberId), directFormattingController = new gui.DirectFormattingController(session, sessionConstraints, sessionContext, inputMemberId,
      objectNameGenerator, args.directTextStylingEnabled, args.directParagraphStylingEnabled), createCursorStyleOp = directFormattingController.createCursorStyleOp, createParagraphStyleOps = directFormattingController.createParagraphStyleOps, textController = new gui.TextController(session, sessionConstraints, sessionContext, inputMemberId, createCursorStyleOp, createParagraphStyleOps), imageController = new gui.ImageController(session, sessionConstraints, sessionContext, inputMemberId, objectNameGenerator),
      imageSelector = new gui.ImageSelector(odtDocument.getOdfCanvas()), shadowCursorIterator = odtDocument.createPositionIterator(odtDocument.getRootNode()), drawShadowCursorTask, redrawRegionSelectionTask, pasteController = new gui.PasteController(session, sessionConstraints, sessionContext, inputMemberId), inputMethodEditor = new gui.InputMethodEditor(inputMemberId, eventManager), clickCount = 0, hyperlinkClickHandler = new gui.HyperlinkClickHandler(odtDocument.getOdfCanvas().getElement, keyDownHandler,
      keyUpHandler), hyperlinkController = new gui.HyperlinkController(session, sessionConstraints, sessionContext, inputMemberId), selectionController = new gui.SelectionController(session, inputMemberId), metadataController = new gui.MetadataController(session, inputMemberId), modifier = gui.KeyboardHandler.Modifier, keyCode = gui.KeyboardHandler.KeyCode, isMacOS = window.navigator.appVersion.toLowerCase().indexOf("mac") !== -1, isIOS = ["iPad", "iPod", "iPhone"].indexOf(window.navigator.platform) !==
      -1, iOSSafariSupport;
      runtime.assert(window !== null, "Expected to be run in an environment which has a global window, like a browser.");
      function getTarget(e) {
        return e.target || e.srcElement || null;
      }
      function cancelEvent(event) {
        if (event.preventDefault) {
          event.preventDefault();
        } else {
          event.returnValue = false;
        }
      }
      function caretPositionFromPoint(x, y) {
        var doc = odtDocument.getDOMDocument(), c, result = null;
        if (doc.caretRangeFromPoint) {
          c = doc.caretRangeFromPoint(x, y);
          result = {container:c.startContainer, offset:c.startOffset};
        } else {
          if (doc.caretPositionFromPoint) {
            c = doc.caretPositionFromPoint(x, y);
            if (c && c.offsetNode) {
              result = {container:c.offsetNode, offset:c.offset};
            }
          }
        }
        return result;
      }
      function redrawRegionSelection() {
        var cursor = odtDocument.getCursor(inputMemberId), imageElement;
        if (cursor && cursor.getSelectionType() === ops.OdtCursor.RegionSelection) {
          imageElement = odfUtils.getImageElements(cursor.getSelectedRange())[0];
          if (imageElement) {
            imageSelector.select(imageElement.parentNode);
            return;
          }
        }
        imageSelector.clearSelection();
      }
      function stringFromKeyPress(event) {
        if (event.which === null || event.which === undefined) {
          return String.fromCharCode(event.keyCode);
        }
        if (event.which !== 0 && event.charCode !== 0) {
          return String.fromCharCode(event.which);
        }
        return null;
      }
      function handleCut(e) {
        var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
        if (selectedRange.collapsed) {
          e.preventDefault();
          return;
        }
        if (clipboard.setDataFromRange(e, selectedRange)) {
          textController.removeCurrentSelection();
        } else {
          runtime.log("Cut operation failed");
        }
      }
      function handleBeforeCut() {
        var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
        return selectedRange.collapsed !== false;
      }
      function handleCopy(e) {
        var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
        if (selectedRange.collapsed) {
          e.preventDefault();
          return;
        }
        if (!clipboard.setDataFromRange(e, selectedRange)) {
          runtime.log("Copy operation failed");
        }
      }
      function handlePaste(e) {
        var plainText;
        if (window.clipboardData && window.clipboardData.getData) {
          plainText = window.clipboardData.getData("Text");
        } else {
          if (e.clipboardData && e.clipboardData.getData) {
            plainText = e.clipboardData.getData("text/plain");
          }
        }
        if (plainText) {
          textController.removeCurrentSelection();
          pasteController.paste(plainText);
        }
        cancelEvent(e);
      }
      function handleBeforePaste() {
        return false;
      }
      function updateUndoStack(op) {
        if (undoManager) {
          undoManager.onOperationExecuted(op);
        }
      }
      function forwardUndoStackChange(e) {
        odtDocument.emit(ops.OdtDocument.signalUndoStackChanged, e);
      }
      function undo() {
        var hadFocusBefore;
        if (undoManager) {
          hadFocusBefore = eventManager.hasFocus();
          undoManager.moveBackward(1);
          if (hadFocusBefore) {
            eventManager.focus();
          }
          return true;
        }
        return false;
      }
      this.undo = undo;
      function redo() {
        var hadFocusBefore;
        if (undoManager) {
          hadFocusBefore = eventManager.hasFocus();
          undoManager.moveForward(1);
          if (hadFocusBefore) {
            eventManager.focus();
          }
          return true;
        }
        return false;
      }
      this.redo = redo;
      function extendSelectionByDrag(event) {
        var position, cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange(), newSelectionRange, handleEnd = getTarget(event).getAttribute("end");
        if (selectedRange && handleEnd) {
          position = caretPositionFromPoint(event.clientX, event.clientY);
          if (position) {
            shadowCursorIterator.setUnfilteredPosition(position.container, position.offset);
            if (mouseDownRootFilter.acceptPosition(shadowCursorIterator) === FILTER_ACCEPT) {
              newSelectionRange = selectedRange.cloneRange();
              if (handleEnd === "left") {
                newSelectionRange.setStart(shadowCursorIterator.container(), shadowCursorIterator.unfilteredDomOffset());
              } else {
                newSelectionRange.setEnd(shadowCursorIterator.container(), shadowCursorIterator.unfilteredDomOffset());
              }
              shadowCursor.setSelectedRange(newSelectionRange, handleEnd === "right");
              odtDocument.emit(ops.Document.signalCursorMoved, shadowCursor);
            }
          }
        }
      }
      function updateCursorSelection() {
        selectionController.selectRange(shadowCursor.getSelectedRange(), shadowCursor.hasForwardSelection(), 1);
      }
      function updateShadowCursor() {
        var selection = window.getSelection(), selectionRange = selection.rangeCount > 0 && selectionController.selectionToRange(selection);
        if (clickStartedWithinCanvas && selectionRange) {
          isMouseMoved = true;
          imageSelector.clearSelection();
          shadowCursorIterator.setUnfilteredPosition(selection.focusNode, selection.focusOffset);
          if (mouseDownRootFilter.acceptPosition(shadowCursorIterator) === FILTER_ACCEPT) {
            if (clickCount === 2) {
              selectionController.expandToWordBoundaries(selectionRange.range);
            } else {
              if (clickCount >= 3) {
                selectionController.expandToParagraphBoundaries(selectionRange.range);
              }
            }
            shadowCursor.setSelectedRange(selectionRange.range, selectionRange.hasForwardSelection);
            odtDocument.emit(ops.Document.signalCursorMoved, shadowCursor);
          }
        }
      }
      function synchronizeWindowSelection(cursor) {
        var selection = window.getSelection(), range = cursor.getSelectedRange();
        if (selection.extend) {
          if (cursor.hasForwardSelection()) {
            selection.collapse(range.startContainer, range.startOffset);
            selection.extend(range.endContainer, range.endOffset);
          } else {
            selection.collapse(range.endContainer, range.endOffset);
            selection.extend(range.startContainer, range.startOffset);
          }
        } else {
          selection.removeAllRanges();
          selection.addRange(range.cloneRange());
        }
      }
      function computeClickCount(event) {
        return event.button === 0 ? event.detail : 0;
      }
      function handleMouseDown(e) {
        var target = getTarget(e), cursor = odtDocument.getCursor(inputMemberId), rootNode;
        clickStartedWithinCanvas = target !== null && domUtils.containsNode(odtDocument.getOdfCanvas().getElement(), target);
        if (clickStartedWithinCanvas) {
          isMouseMoved = false;
          rootNode = odtDocument.getRootElement(target) || odtDocument.getRootNode();
          mouseDownRootFilter = odtDocument.createRootFilter(rootNode);
          clickCount = computeClickCount(e);
          if (cursor && e.shiftKey) {
            window.getSelection().collapse(cursor.getAnchorNode(), 0);
          } else {
            synchronizeWindowSelection(cursor);
          }
          if (clickCount > 1) {
            updateShadowCursor();
          }
        }
      }
      function mutableSelection(selection) {
        if (selection) {
          return {anchorNode:selection.anchorNode, anchorOffset:selection.anchorOffset, focusNode:selection.focusNode, focusOffset:selection.focusOffset};
        }
        return null;
      }
      function getNextWalkablePosition(node) {
        var root = odtDocument.getRootElement(node), rootFilter = odtDocument.createRootFilter(root), stepIterator = odtDocument.createStepIterator(node, 0, [rootFilter, odtDocument.getPositionFilter()], root);
        stepIterator.setPosition(node, node.childNodes.length);
        if (!stepIterator.roundToNextStep()) {
          return null;
        }
        return {container:stepIterator.container(), offset:stepIterator.offset()};
      }
      function moveByMouseClickEvent(event) {
        var selection = mutableSelection(window.getSelection()), isCollapsed = window.getSelection().isCollapsed, position, selectionRange, rect, frameNode;
        if (!selection.anchorNode && !selection.focusNode) {
          position = caretPositionFromPoint(event.clientX, event.clientY);
          if (position) {
            selection.anchorNode = position.container;
            selection.anchorOffset = position.offset;
            selection.focusNode = selection.anchorNode;
            selection.focusOffset = selection.anchorOffset;
          }
        }
        if (odfUtils.isImage(selection.focusNode) && selection.focusOffset === 0 && odfUtils.isCharacterFrame(selection.focusNode.parentNode)) {
          frameNode = selection.focusNode.parentNode;
          rect = frameNode.getBoundingClientRect();
          if (event.clientX > rect.left) {
            position = getNextWalkablePosition(frameNode);
            if (position) {
              selection.focusNode = position.container;
              selection.focusOffset = position.offset;
              if (isCollapsed) {
                selection.anchorNode = selection.focusNode;
                selection.anchorOffset = selection.focusOffset;
              }
            }
          }
        } else {
          if (odfUtils.isImage(selection.focusNode.firstChild) && selection.focusOffset === 1 && odfUtils.isCharacterFrame(selection.focusNode)) {
            position = getNextWalkablePosition(selection.focusNode);
            if (position) {
              selection.anchorNode = selection.focusNode = position.container;
              selection.anchorOffset = selection.focusOffset = position.offset;
            }
          }
        }
        if (selection.anchorNode && selection.focusNode) {
          selectionRange = selectionController.selectionToRange(selection);
          selectionController.selectRange(selectionRange.range, selectionRange.hasForwardSelection, computeClickCount(event));
        }
        eventManager.focus();
      }
      function selectWordByLongPress(event) {
        var selection, position, selectionRange, container, offset;
        position = caretPositionFromPoint(event.clientX, event.clientY);
        if (position) {
          container = position.container;
          offset = position.offset;
          selection = {anchorNode:container, anchorOffset:offset, focusNode:container, focusOffset:offset};
          selectionRange = selectionController.selectionToRange(selection);
          selectionController.selectRange(selectionRange.range, selectionRange.hasForwardSelection, 2);
          eventManager.focus();
        }
      }
      function handleMouseClickEvent(event) {
        var target = getTarget(event), clickEvent, range, wasCollapsed, frameNode, pos;
        drawShadowCursorTask.processRequests();
        if (clickStartedWithinCanvas) {
          if (odfUtils.isImage(target) && odfUtils.isCharacterFrame(target.parentNode) && window.getSelection().isCollapsed) {
            selectionController.selectImage(target.parentNode);
            eventManager.focus();
          } else {
            if (imageSelector.isSelectorElement(target)) {
              eventManager.focus();
            } else {
              if (isMouseMoved) {
                range = shadowCursor.getSelectedRange();
                wasCollapsed = range.collapsed;
                if (odfUtils.isImage(range.endContainer) && range.endOffset === 0 && odfUtils.isCharacterFrame(range.endContainer.parentNode)) {
                  frameNode = range.endContainer.parentNode;
                  pos = getNextWalkablePosition(frameNode);
                  if (pos) {
                    range.setEnd(pos.container, pos.offset);
                    if (wasCollapsed) {
                      range.collapse(false);
                    }
                  }
                }
                selectionController.selectRange(range, shadowCursor.hasForwardSelection(), computeClickCount(event));
                eventManager.focus();
              } else {
                if (isIOS) {
                  moveByMouseClickEvent(event);
                } else {
                  clickEvent = domUtils.cloneEvent(event);
                  handleMouseClickTimeoutId = runtime.setTimeout(function() {
                    moveByMouseClickEvent(clickEvent);
                  }, 0);
                }
              }
            }
          }
          clickCount = 0;
          clickStartedWithinCanvas = false;
          isMouseMoved = false;
        }
      }
      function handleDragStart(e) {
        var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
        if (selectedRange.collapsed) {
          return;
        }
        mimeDataExporter.exportRangeToDataTransfer(e.dataTransfer, selectedRange);
      }
      function handleDragEnd() {
        if (clickStartedWithinCanvas) {
          eventManager.focus();
        }
        clickCount = 0;
        clickStartedWithinCanvas = false;
        isMouseMoved = false;
      }
      function handleContextMenu(e) {
        handleMouseClickEvent(e);
      }
      function handleMouseUp(event) {
        var target = getTarget(event), annotationNode = null;
        if (target.className === "annotationRemoveButton") {
          runtime.assert(annotationsEnabled, "Remove buttons are displayed on annotations while annotation editing is disabled in the controller.");
          annotationNode = target.parentNode.getElementsByTagNameNS(odf.Namespaces.officens, "annotation").item(0);
          annotationController.removeAnnotation(annotationNode);
          eventManager.focus();
        } else {
          if (target.getAttribute("class") !== "webodf-draggable") {
            handleMouseClickEvent(event);
          }
        }
      }
      function insertNonEmptyData(e) {
        var input = e.data;
        if (input) {
          if (input.indexOf("\n") === -1) {
            textController.insertText(input);
          } else {
            pasteController.paste(input);
          }
        }
      }
      function returnTrue(fn) {
        return function() {
          fn();
          return true;
        };
      }
      function rangeSelectionOnly(fn) {
        function f(e) {
          var selectionType = odtDocument.getCursor(inputMemberId).getSelectionType();
          if (selectionType === ops.OdtCursor.RangeSelection) {
            return fn(e);
          }
          return true;
        }
        return f;
      }
      function insertLocalCursor() {
        runtime.assert(session.getOdtDocument().getCursor(inputMemberId) === undefined, "Inserting local cursor a second time.");
        var op = new ops.OpAddCursor;
        op.init({memberid:inputMemberId});
        session.enqueue([op]);
        eventManager.focus();
      }
      this.insertLocalCursor = insertLocalCursor;
      function removeLocalCursor() {
        runtime.assert(session.getOdtDocument().getCursor(inputMemberId) !== undefined, "Removing local cursor without inserting before.");
        var op = new ops.OpRemoveCursor;
        op.init({memberid:inputMemberId});
        session.enqueue([op]);
      }
      this.removeLocalCursor = removeLocalCursor;
      this.startEditing = function() {
        inputMethodEditor.subscribe(gui.InputMethodEditor.signalCompositionStart, textController.removeCurrentSelection);
        inputMethodEditor.subscribe(gui.InputMethodEditor.signalCompositionEnd, insertNonEmptyData);
        eventManager.subscribe("beforecut", handleBeforeCut);
        eventManager.subscribe("cut", handleCut);
        eventManager.subscribe("beforepaste", handleBeforePaste);
        eventManager.subscribe("paste", handlePaste);
        if (undoManager) {
          undoManager.initialize();
        }
        eventManager.setEditing(true);
        hyperlinkClickHandler.setModifier(isMacOS ? modifier.Meta : modifier.Ctrl);
        keyDownHandler.bind(keyCode.Backspace, modifier.None, returnTrue(textController.removeTextByBackspaceKey), true);
        keyDownHandler.bind(keyCode.Delete, modifier.None, textController.removeTextByDeleteKey);
        keyDownHandler.bind(keyCode.Tab, modifier.None, rangeSelectionOnly(function() {
          textController.insertText("\t");
          return true;
        }));
        if (isMacOS) {
          keyDownHandler.bind(keyCode.Clear, modifier.None, textController.removeCurrentSelection);
          keyDownHandler.bind(keyCode.B, modifier.Meta, rangeSelectionOnly(directFormattingController.toggleBold));
          keyDownHandler.bind(keyCode.I, modifier.Meta, rangeSelectionOnly(directFormattingController.toggleItalic));
          keyDownHandler.bind(keyCode.U, modifier.Meta, rangeSelectionOnly(directFormattingController.toggleUnderline));
          keyDownHandler.bind(keyCode.L, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphLeft));
          keyDownHandler.bind(keyCode.E, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphCenter));
          keyDownHandler.bind(keyCode.R, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphRight));
          keyDownHandler.bind(keyCode.J, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphJustified));
          if (annotationsEnabled) {
            keyDownHandler.bind(keyCode.C, modifier.MetaShift, annotationController.addAnnotation);
          }
          keyDownHandler.bind(keyCode.Z, modifier.Meta, undo);
          keyDownHandler.bind(keyCode.Z, modifier.MetaShift, redo);
        } else {
          keyDownHandler.bind(keyCode.B, modifier.Ctrl, rangeSelectionOnly(directFormattingController.toggleBold));
          keyDownHandler.bind(keyCode.I, modifier.Ctrl, rangeSelectionOnly(directFormattingController.toggleItalic));
          keyDownHandler.bind(keyCode.U, modifier.Ctrl, rangeSelectionOnly(directFormattingController.toggleUnderline));
          keyDownHandler.bind(keyCode.L, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphLeft));
          keyDownHandler.bind(keyCode.E, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphCenter));
          keyDownHandler.bind(keyCode.R, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphRight));
          keyDownHandler.bind(keyCode.J, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphJustified));
          if (annotationsEnabled) {
            keyDownHandler.bind(keyCode.C, modifier.CtrlAlt, annotationController.addAnnotation);
          }
          keyDownHandler.bind(keyCode.Z, modifier.Ctrl, undo);
          keyDownHandler.bind(keyCode.Z, modifier.CtrlShift, redo);
        }
        function handler(e) {
          var text = stringFromKeyPress(e);
          if (text && !(e.altKey || e.ctrlKey || e.metaKey)) {
            textController.insertText(text);
            return true;
          }
          return false;
        }
        keyPressHandler.setDefault(rangeSelectionOnly(handler));
        keyPressHandler.bind(keyCode.Enter, modifier.None, rangeSelectionOnly(textController.enqueueParagraphSplittingOps));
      };
      this.endEditing = function() {
        inputMethodEditor.unsubscribe(gui.InputMethodEditor.signalCompositionStart, textController.removeCurrentSelection);
        inputMethodEditor.unsubscribe(gui.InputMethodEditor.signalCompositionEnd, insertNonEmptyData);
        eventManager.unsubscribe("cut", handleCut);
        eventManager.unsubscribe("beforecut", handleBeforeCut);
        eventManager.unsubscribe("paste", handlePaste);
        eventManager.unsubscribe("beforepaste", handleBeforePaste);
        eventManager.setEditing(false);
        hyperlinkClickHandler.setModifier(modifier.None);
        keyDownHandler.bind(keyCode.Backspace, modifier.None, function() {
          return true;
        }, true);
        keyDownHandler.unbind(keyCode.Delete, modifier.None);
        keyDownHandler.unbind(keyCode.Tab, modifier.None);
        if (isMacOS) {
          keyDownHandler.unbind(keyCode.Clear, modifier.None);
          keyDownHandler.unbind(keyCode.B, modifier.Meta);
          keyDownHandler.unbind(keyCode.I, modifier.Meta);
          keyDownHandler.unbind(keyCode.U, modifier.Meta);
          keyDownHandler.unbind(keyCode.L, modifier.MetaShift);
          keyDownHandler.unbind(keyCode.E, modifier.MetaShift);
          keyDownHandler.unbind(keyCode.R, modifier.MetaShift);
          keyDownHandler.unbind(keyCode.J, modifier.MetaShift);
          if (annotationsEnabled) {
            keyDownHandler.unbind(keyCode.C, modifier.MetaShift);
          }
          keyDownHandler.unbind(keyCode.Z, modifier.Meta);
          keyDownHandler.unbind(keyCode.Z, modifier.MetaShift);
        } else {
          keyDownHandler.unbind(keyCode.B, modifier.Ctrl);
          keyDownHandler.unbind(keyCode.I, modifier.Ctrl);
          keyDownHandler.unbind(keyCode.U, modifier.Ctrl);
          keyDownHandler.unbind(keyCode.L, modifier.CtrlShift);
          keyDownHandler.unbind(keyCode.E, modifier.CtrlShift);
          keyDownHandler.unbind(keyCode.R, modifier.CtrlShift);
          keyDownHandler.unbind(keyCode.J, modifier.CtrlShift);
          if (annotationsEnabled) {
            keyDownHandler.unbind(keyCode.C, modifier.CtrlAlt);
          }
          keyDownHandler.unbind(keyCode.Z, modifier.Ctrl);
          keyDownHandler.unbind(keyCode.Z, modifier.CtrlShift);
        }
        keyPressHandler.setDefault(null);
        keyPressHandler.unbind(keyCode.Enter, modifier.None);
      };
      this.getInputMemberId = function() {
        return inputMemberId;
      };
      this.getSession = function() {
        return session;
      };
      this.getSessionConstraints = function() {
        return sessionConstraints;
      };
      this.setUndoManager = function(manager) {
        if (undoManager) {
          undoManager.unsubscribe(gui.UndoManager.signalUndoStackChanged, forwardUndoStackChange);
        }
        undoManager = manager;
        if (undoManager) {
          undoManager.setDocument(odtDocument);
          undoManager.setPlaybackFunction(session.enqueue);
          undoManager.subscribe(gui.UndoManager.signalUndoStackChanged, forwardUndoStackChange);
        }
      };
      this.getUndoManager = function() {
        return undoManager;
      };
      this.getMetadataController = function() {
        return metadataController;
      };
      this.getAnnotationController = function() {
        return annotationController;
      };
      this.getDirectFormattingController = function() {
        return directFormattingController;
      };
      this.getHyperlinkClickHandler = function() {
        return hyperlinkClickHandler;
      };
      this.getHyperlinkController = function() {
        return hyperlinkController;
      };
      this.getImageController = function() {
        return imageController;
      };
      this.getSelectionController = function() {
        return selectionController;
      };
      this.getTextController = function() {
        return textController;
      };
      this.getEventManager = function() {
        return eventManager;
      };
      this.getKeyboardHandlers = function() {
        return {keydown:keyDownHandler, keypress:keyPressHandler};
      };
      function destroy(callback) {
        eventManager.unsubscribe("keydown", keyDownHandler.handleEvent);
        eventManager.unsubscribe("keypress", keyPressHandler.handleEvent);
        eventManager.unsubscribe("keyup", keyUpHandler.handleEvent);
        eventManager.unsubscribe("copy", handleCopy);
        eventManager.unsubscribe("mousedown", handleMouseDown);
        eventManager.unsubscribe("mousemove", drawShadowCursorTask.trigger);
        eventManager.unsubscribe("mouseup", handleMouseUp);
        eventManager.unsubscribe("contextmenu", handleContextMenu);
        eventManager.unsubscribe("dragstart", handleDragStart);
        eventManager.unsubscribe("dragend", handleDragEnd);
        eventManager.unsubscribe("click", hyperlinkClickHandler.handleClick);
        eventManager.unsubscribe("longpress", selectWordByLongPress);
        eventManager.unsubscribe("drag", extendSelectionByDrag);
        eventManager.unsubscribe("dragstop", updateCursorSelection);
        odtDocument.unsubscribe(ops.OdtDocument.signalOperationEnd, redrawRegionSelectionTask.trigger);
        odtDocument.unsubscribe(ops.Document.signalCursorAdded, inputMethodEditor.registerCursor);
        odtDocument.unsubscribe(ops.Document.signalCursorRemoved, inputMethodEditor.removeCursor);
        odtDocument.unsubscribe(ops.OdtDocument.signalOperationEnd, updateUndoStack);
        callback();
      }
      this.destroy = function(callback) {
        var destroyCallbacks = [drawShadowCursorTask.destroy, redrawRegionSelectionTask.destroy, directFormattingController.destroy, inputMethodEditor.destroy, eventManager.destroy, hyperlinkClickHandler.destroy, hyperlinkController.destroy, metadataController.destroy, selectionController.destroy, textController.destroy, destroy];
        if (iOSSafariSupport) {
          destroyCallbacks.unshift(iOSSafariSupport.destroy);
        }
        runtime.clearTimeout(handleMouseClickTimeoutId);
        core.Async.destroyAll(destroyCallbacks, callback);
      };
      function init() {
        drawShadowCursorTask = core.Task.createRedrawTask(updateShadowCursor);
        redrawRegionSelectionTask = core.Task.createRedrawTask(redrawRegionSelection);
        keyDownHandler.bind(keyCode.Left, modifier.None, rangeSelectionOnly(selectionController.moveCursorToLeft));
        keyDownHandler.bind(keyCode.Right, modifier.None, rangeSelectionOnly(selectionController.moveCursorToRight));
        keyDownHandler.bind(keyCode.Up, modifier.None, rangeSelectionOnly(selectionController.moveCursorUp));
        keyDownHandler.bind(keyCode.Down, modifier.None, rangeSelectionOnly(selectionController.moveCursorDown));
        keyDownHandler.bind(keyCode.Left, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToLeft));
        keyDownHandler.bind(keyCode.Right, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToRight));
        keyDownHandler.bind(keyCode.Up, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionUp));
        keyDownHandler.bind(keyCode.Down, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionDown));
        keyDownHandler.bind(keyCode.Home, modifier.None, rangeSelectionOnly(selectionController.moveCursorToLineStart));
        keyDownHandler.bind(keyCode.End, modifier.None, rangeSelectionOnly(selectionController.moveCursorToLineEnd));
        keyDownHandler.bind(keyCode.Home, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorToDocumentStart));
        keyDownHandler.bind(keyCode.End, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorToDocumentEnd));
        keyDownHandler.bind(keyCode.Home, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToLineStart));
        keyDownHandler.bind(keyCode.End, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToLineEnd));
        keyDownHandler.bind(keyCode.Up, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphStart));
        keyDownHandler.bind(keyCode.Down, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphEnd));
        keyDownHandler.bind(keyCode.Home, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentStart));
        keyDownHandler.bind(keyCode.End, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentEnd));
        if (isMacOS) {
          keyDownHandler.bind(keyCode.Left, modifier.Alt, rangeSelectionOnly(selectionController.moveCursorBeforeWord));
          keyDownHandler.bind(keyCode.Right, modifier.Alt, rangeSelectionOnly(selectionController.moveCursorPastWord));
          keyDownHandler.bind(keyCode.Left, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToLineStart));
          keyDownHandler.bind(keyCode.Right, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToLineEnd));
          keyDownHandler.bind(keyCode.Home, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToDocumentStart));
          keyDownHandler.bind(keyCode.End, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToDocumentEnd));
          keyDownHandler.bind(keyCode.Left, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionBeforeWord));
          keyDownHandler.bind(keyCode.Right, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionPastWord));
          keyDownHandler.bind(keyCode.Left, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToLineStart));
          keyDownHandler.bind(keyCode.Right, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToLineEnd));
          keyDownHandler.bind(keyCode.Up, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphStart));
          keyDownHandler.bind(keyCode.Down, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphEnd));
          keyDownHandler.bind(keyCode.Up, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentStart));
          keyDownHandler.bind(keyCode.Down, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentEnd));
          keyDownHandler.bind(keyCode.A, modifier.Meta, rangeSelectionOnly(selectionController.extendSelectionToEntireDocument));
        } else {
          keyDownHandler.bind(keyCode.Left, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorBeforeWord));
          keyDownHandler.bind(keyCode.Right, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorPastWord));
          keyDownHandler.bind(keyCode.Left, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionBeforeWord));
          keyDownHandler.bind(keyCode.Right, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionPastWord));
          keyDownHandler.bind(keyCode.A, modifier.Ctrl, rangeSelectionOnly(selectionController.extendSelectionToEntireDocument));
        }
        if (isIOS) {
          iOSSafariSupport = new gui.IOSSafariSupport(eventManager);
        }
        eventManager.subscribe("keydown", keyDownHandler.handleEvent);
        eventManager.subscribe("keypress", keyPressHandler.handleEvent);
        eventManager.subscribe("keyup", keyUpHandler.handleEvent);
        eventManager.subscribe("copy", handleCopy);
        eventManager.subscribe("mousedown", handleMouseDown);
        eventManager.subscribe("mousemove", drawShadowCursorTask.trigger);
        eventManager.subscribe("mouseup", handleMouseUp);
        eventManager.subscribe("contextmenu", handleContextMenu);
        eventManager.subscribe("dragstart", handleDragStart);
        eventManager.subscribe("dragend", handleDragEnd);
        eventManager.subscribe("click", hyperlinkClickHandler.handleClick);
        eventManager.subscribe("longpress", selectWordByLongPress);
        eventManager.subscribe("drag", extendSelectionByDrag);
        eventManager.subscribe("dragstop", updateCursorSelection);
        odtDocument.subscribe(ops.OdtDocument.signalOperationEnd, redrawRegionSelectionTask.trigger);
        odtDocument.subscribe(ops.Document.signalCursorAdded, inputMethodEditor.registerCursor);
        odtDocument.subscribe(ops.Document.signalCursorRemoved, inputMethodEditor.removeCursor);
        odtDocument.subscribe(ops.OdtDocument.signalOperationEnd, updateUndoStack);
      }
      init();
    };
  })();
  gui.CaretManager = function CaretManager(sessionController, viewport) {
    var carets = {}, window = runtime.getWindow(), odtDocument = sessionController.getSession().getOdtDocument(), eventManager = sessionController.getEventManager();
    function getCaret(memberId) {
      return carets.hasOwnProperty(memberId) ? carets[memberId] : null;
    }
    function getLocalCaretXOffsetPx() {
      var localCaret = getCaret(sessionController.getInputMemberId()), lastRect;
      if (localCaret) {
        lastRect = localCaret.getBoundingClientRect();
      }
      return lastRect ? lastRect.right : undefined;
    }
    function getCarets() {
      return Object.keys(carets).map(function(memberid) {
        return carets[memberid];
      });
    }
    function removeCaret(memberId) {
      var caret = carets[memberId];
      if (caret) {
        delete carets[memberId];
        if (memberId === sessionController.getInputMemberId()) {
          odtDocument.unsubscribe(ops.OdtDocument.signalProcessingBatchEnd, caret.ensureVisible);
          odtDocument.unsubscribe(ops.Document.signalCursorMoved, caret.refreshCursorBlinking);
          eventManager.unsubscribe("compositionupdate", caret.handleUpdate);
          eventManager.unsubscribe("compositionend", caret.handleUpdate);
          eventManager.unsubscribe("focus", caret.setFocus);
          eventManager.unsubscribe("blur", caret.removeFocus);
          window.removeEventListener("focus", caret.show, false);
          window.removeEventListener("blur", caret.hide, false);
        } else {
          odtDocument.unsubscribe(ops.OdtDocument.signalProcessingBatchEnd, caret.handleUpdate);
        }
        caret.destroy(function() {
        });
      }
    }
    this.registerCursor = function(cursor, caretAvatarInitiallyVisible, blinkOnRangeSelect) {
      var memberid = cursor.getMemberId(), caret = new gui.Caret(cursor, viewport, caretAvatarInitiallyVisible, blinkOnRangeSelect);
      carets[memberid] = caret;
      if (memberid === sessionController.getInputMemberId()) {
        runtime.log("Starting to track input on new cursor of " + memberid);
        odtDocument.subscribe(ops.OdtDocument.signalProcessingBatchEnd, caret.ensureVisible);
        odtDocument.subscribe(ops.Document.signalCursorMoved, caret.refreshCursorBlinking);
        eventManager.subscribe("compositionupdate", caret.handleUpdate);
        eventManager.subscribe("compositionend", caret.handleUpdate);
        eventManager.subscribe("focus", caret.setFocus);
        eventManager.subscribe("blur", caret.removeFocus);
        window.addEventListener("focus", caret.show, false);
        window.addEventListener("blur", caret.hide, false);
        caret.setOverlayElement(eventManager.getEventTrap());
      } else {
        odtDocument.subscribe(ops.OdtDocument.signalProcessingBatchEnd, caret.handleUpdate);
      }
      return caret;
    };
    this.getCaret = getCaret;
    this.getCarets = getCarets;
    this.destroy = function(callback) {
      var caretCleanup = getCarets().map(function(caret) {
        return caret.destroy;
      });
      sessionController.getSelectionController().setCaretXPositionLocator(null);
      odtDocument.unsubscribe(ops.Document.signalCursorRemoved, removeCaret);
      carets = {};
      core.Async.destroyAll(caretCleanup, callback);
    };
    function init() {
      sessionController.getSelectionController().setCaretXPositionLocator(getLocalCaretXOffsetPx);
      odtDocument.subscribe(ops.Document.signalCursorRemoved, removeCaret);
    }
    init();
  };
  gui.EditInfoHandle = function EditInfoHandle(parentElement) {
    var edits = [], handle, document = parentElement.ownerDocument, htmlns = document.documentElement.namespaceURI, editinfons = "urn:webodf:names:editinfo";
    function renderEdits() {
      var i, infoDiv, colorSpan, authorSpan, timeSpan;
      core.DomUtils.removeAllChildNodes(handle);
      for (i = 0;i < edits.length;i += 1) {
        infoDiv = document.createElementNS(htmlns, "div");
        infoDiv.className = "editInfo";
        colorSpan = document.createElementNS(htmlns, "span");
        colorSpan.className = "editInfoColor";
        colorSpan.setAttributeNS(editinfons, "editinfo:memberid", edits[i].memberid);
        authorSpan = document.createElementNS(htmlns, "span");
        authorSpan.className = "editInfoAuthor";
        authorSpan.setAttributeNS(editinfons, "editinfo:memberid", edits[i].memberid);
        timeSpan = document.createElementNS(htmlns, "span");
        timeSpan.className = "editInfoTime";
        timeSpan.setAttributeNS(editinfons, "editinfo:memberid", edits[i].memberid);
        timeSpan.appendChild(document.createTextNode(edits[i].time.toString()));
        infoDiv.appendChild(colorSpan);
        infoDiv.appendChild(authorSpan);
        infoDiv.appendChild(timeSpan);
        handle.appendChild(infoDiv);
      }
    }
    this.setEdits = function(editArray) {
      edits = editArray;
      renderEdits();
    };
    this.show = function() {
      handle.style.display = "block";
    };
    this.hide = function() {
      handle.style.display = "none";
    };
    this.destroy = function(callback) {
      parentElement.removeChild(handle);
      callback();
    };
    function init() {
      handle = document.createElementNS(htmlns, "div");
      handle.setAttribute("class", "editInfoHandle");
      handle.style.display = "none";
      parentElement.appendChild(handle);
    }
    init();
  };
  ops.EditInfo = function EditInfo(container, odtDocument) {
    var editInfoNode, editHistory = {};
    function sortEdits() {
      var arr = [], memberid;
      for (memberid in editHistory) {
        if (editHistory.hasOwnProperty(memberid)) {
          arr.push({"memberid":memberid, "time":editHistory[memberid].time});
        }
      }
      arr.sort(function(a, b) {
        return a.time - b.time;
      });
      return arr;
    }
    this.getNode = function() {
      return editInfoNode;
    };
    this.getOdtDocument = function() {
      return odtDocument;
    };
    this.getEdits = function() {
      return editHistory;
    };
    this.getSortedEdits = function() {
      return sortEdits();
    };
    this.addEdit = function(memberid, timestamp) {
      editHistory[memberid] = {time:timestamp};
    };
    this.clearEdits = function() {
      editHistory = {};
    };
    this.destroy = function(callback) {
      if (container.parentNode) {
        container.removeChild(editInfoNode);
      }
      callback();
    };
    function init() {
      var editInfons = "urn:webodf:names:editinfo", dom = odtDocument.getDOMDocument();
      editInfoNode = dom.createElementNS(editInfons, "editinfo");
      container.insertBefore(editInfoNode, container.firstChild);
    }
    init();
  };
  gui.EditInfoMarker = function EditInfoMarker(editInfo, initialVisibility) {
    var self = this, editInfoNode, handle, marker, editinfons = "urn:webodf:names:editinfo", decayTimer0, decayTimer1, decayTimer2, decayTimeStep = 1E4;
    function applyDecay(opacity, delay) {
      return runtime.setTimeout(function() {
        marker.style.opacity = opacity;
      }, delay);
    }
    function deleteDecay(timerId) {
      runtime.clearTimeout(timerId);
    }
    function setLastAuthor(memberid) {
      marker.setAttributeNS(editinfons, "editinfo:memberid", memberid);
    }
    this.addEdit = function(memberid, timestamp) {
      var age = Date.now() - timestamp;
      editInfo.addEdit(memberid, timestamp);
      handle.setEdits(editInfo.getSortedEdits());
      setLastAuthor(memberid);
      deleteDecay(decayTimer1);
      deleteDecay(decayTimer2);
      if (age < decayTimeStep) {
        decayTimer0 = applyDecay(1, 0);
        decayTimer1 = applyDecay(.5, decayTimeStep - age);
        decayTimer2 = applyDecay(.2, decayTimeStep * 2 - age);
      } else {
        if (age >= decayTimeStep && age < decayTimeStep * 2) {
          decayTimer0 = applyDecay(.5, 0);
          decayTimer2 = applyDecay(.2, decayTimeStep * 2 - age);
        } else {
          decayTimer0 = applyDecay(.2, 0);
        }
      }
    };
    this.getEdits = function() {
      return editInfo.getEdits();
    };
    this.clearEdits = function() {
      editInfo.clearEdits();
      handle.setEdits([]);
      if (marker.hasAttributeNS(editinfons, "editinfo:memberid")) {
        marker.removeAttributeNS(editinfons, "editinfo:memberid");
      }
    };
    this.getEditInfo = function() {
      return editInfo;
    };
    this.show = function() {
      marker.style.display = "block";
    };
    this.hide = function() {
      self.hideHandle();
      marker.style.display = "none";
    };
    this.showHandle = function() {
      handle.show();
    };
    this.hideHandle = function() {
      handle.hide();
    };
    this.destroy = function(callback) {
      deleteDecay(decayTimer0);
      deleteDecay(decayTimer1);
      deleteDecay(decayTimer2);
      editInfoNode.removeChild(marker);
      handle.destroy(function(err) {
        if (err) {
          callback(err);
        } else {
          editInfo.destroy(callback);
        }
      });
    };
    function init() {
      var dom = editInfo.getOdtDocument().getDOMDocument(), htmlns = dom.documentElement.namespaceURI;
      marker = dom.createElementNS(htmlns, "div");
      marker.setAttribute("class", "editInfoMarker");
      marker.onmouseover = function() {
        self.showHandle();
      };
      marker.onmouseout = function() {
        self.hideHandle();
      };
      editInfoNode = editInfo.getNode();
      editInfoNode.appendChild(marker);
      handle = new gui.EditInfoHandle(editInfoNode);
      if (!initialVisibility) {
        self.hide();
      }
    }
    init();
  };
  gui.HyperlinkTooltipView = function HyperlinkTooltipView(odfCanvas, getActiveModifier) {
    var domUtils = core.DomUtils, odfUtils = odf.OdfUtils, window = runtime.getWindow(), linkSpan, textSpan, tooltipElement, offsetXPx = 15, offsetYPx = 10;
    runtime.assert(window !== null, "Expected to be run in an environment which has a global window, like a browser.");
    function getHyperlinkElement(node) {
      while (node) {
        if (odfUtils.isHyperlink(node)) {
          return node;
        }
        if (odfUtils.isParagraph(node) || odfUtils.isInlineRoot(node)) {
          break;
        }
        node = node.parentNode;
      }
      return null;
    }
    function getHint() {
      var modifierKey = getActiveModifier(), hint;
      switch(modifierKey) {
        case gui.KeyboardHandler.Modifier.Ctrl:
          hint = runtime.tr("Ctrl-click to follow link");
          break;
        case gui.KeyboardHandler.Modifier.Meta:
          hint = runtime.tr("\u2318-click to follow link");
          break;
        default:
          hint = "";
          break;
      }
      return hint;
    }
    this.showTooltip = function(e) {
      var target = e.target || e.srcElement, sizerElement = odfCanvas.getSizer(), zoomLevel = odfCanvas.getZoomLevel(), referenceRect, linkElement, left, top, max;
      linkElement = getHyperlinkElement(target);
      if (!linkElement) {
        return;
      }
      if (!domUtils.containsNode(sizerElement, tooltipElement)) {
        sizerElement.appendChild(tooltipElement);
      }
      textSpan.textContent = getHint();
      linkSpan.textContent = odfUtils.getHyperlinkTarget(linkElement);
      tooltipElement.style.display = "block";
      max = window.innerWidth - tooltipElement.offsetWidth - offsetXPx;
      left = e.clientX > max ? max : e.clientX + offsetXPx;
      max = window.innerHeight - tooltipElement.offsetHeight - offsetYPx;
      top = e.clientY > max ? max : e.clientY + offsetYPx;
      referenceRect = sizerElement.getBoundingClientRect();
      left = (left - referenceRect.left) / zoomLevel;
      top = (top - referenceRect.top) / zoomLevel;
      tooltipElement.style.left = left + "px";
      tooltipElement.style.top = top + "px";
    };
    this.hideTooltip = function() {
      tooltipElement.style.display = "none";
    };
    this.destroy = function(callback) {
      if (tooltipElement.parentNode) {
        tooltipElement.parentNode.removeChild(tooltipElement);
      }
      callback();
    };
    function init() {
      var document = odfCanvas.getElement().ownerDocument;
      linkSpan = document.createElement("span");
      textSpan = document.createElement("span");
      linkSpan.className = "webodf-hyperlinkTooltipLink";
      textSpan.className = "webodf-hyperlinkTooltipText";
      tooltipElement = document.createElement("div");
      tooltipElement.className = "webodf-hyperlinkTooltip";
      tooltipElement.appendChild(linkSpan);
      tooltipElement.appendChild(textSpan);
      odfCanvas.getElement().appendChild(tooltipElement);
    }
    init();
  };
  gui.OdfFieldView = function(odfCanvas) {
    var style, document = odfCanvas.getElement().ownerDocument;
    function newStyleSheet() {
      var head = document.getElementsByTagName("head").item(0), sheet = document.createElement("style"), text = "";
      sheet.type = "text/css";
      sheet.media = "screen, print, handheld, projection";
      odf.Namespaces.forEachPrefix(function(prefix, ns) {
        text += "@namespace " + prefix + " url(" + ns + ");\n";
      });
      sheet.appendChild(document.createTextNode(text));
      head.appendChild(sheet);
      return sheet;
    }
    function clearCSSStyleSheet(style) {
      var stylesheet = style.sheet, cssRules = stylesheet.cssRules;
      while (cssRules.length) {
        stylesheet.deleteRule(cssRules.length - 1);
      }
    }
    function createRule(selectors, css) {
      return selectors.join(",\n") + "\n" + css + "\n";
    }
    function generateFieldCSS() {
      var cssSelectors = odf.OdfSchema.getFields().map(function(prefixedName) {
        return prefixedName.replace(":", "|");
      }), highlightFields = createRule(cssSelectors, "{ background-color: #D0D0D0; }"), emptyCssSelectors = cssSelectors.map(function(selector) {
        return selector + ":empty::after";
      }), highlightEmptyFields = createRule(emptyCssSelectors, "{ content:' '; white-space: pre; }");
      return highlightFields + "\n" + highlightEmptyFields;
    }
    this.showFieldHighlight = function() {
      style.appendChild(document.createTextNode(generateFieldCSS()));
    };
    this.hideFieldHighlight = function() {
      clearCSSStyleSheet(style);
    };
    this.destroy = function(callback) {
      if (style.parentNode) {
        style.parentNode.removeChild(style);
      }
      callback();
    };
    function init() {
      style = newStyleSheet();
    }
    init();
  };
  gui.ShadowCursor = function ShadowCursor(document) {
    var selectedRange = document.getDOMDocument().createRange(), forwardSelection = true;
    this.removeFromDocument = function() {
    };
    this.getMemberId = function() {
      return gui.ShadowCursor.ShadowCursorMemberId;
    };
    this.getSelectedRange = function() {
      return selectedRange;
    };
    this.setSelectedRange = function(range, isForwardSelection) {
      selectedRange = range;
      forwardSelection = isForwardSelection !== false;
    };
    this.hasForwardSelection = function() {
      return forwardSelection;
    };
    this.getDocument = function() {
      return document;
    };
    this.getSelectionType = function() {
      return ops.OdtCursor.RangeSelection;
    };
    function init() {
      selectedRange.setStart(document.getRootNode(), 0);
    }
    init();
  };
  gui.ShadowCursor.ShadowCursorMemberId = "";
  gui.SelectionView = function SelectionView(cursor) {
  };
  gui.SelectionView.prototype.rerender = function() {
  };
  gui.SelectionView.prototype.show = function() {
  };
  gui.SelectionView.prototype.hide = function() {
  };
  gui.SelectionView.prototype.destroy = function(callback) {
  };
  gui.SelectionViewManager = function SelectionViewManager(SelectionView) {
    var selectionViews = {};
    function getSelectionView(memberId) {
      return selectionViews.hasOwnProperty(memberId) ? selectionViews[memberId] : null;
    }
    this.getSelectionView = getSelectionView;
    function getSelectionViews() {
      return Object.keys(selectionViews).map(function(memberid) {
        return selectionViews[memberid];
      });
    }
    this.getSelectionViews = getSelectionViews;
    function removeSelectionView(memberId) {
      if (selectionViews.hasOwnProperty(memberId)) {
        selectionViews[memberId].destroy(function() {
        });
        delete selectionViews[memberId];
      }
    }
    this.removeSelectionView = removeSelectionView;
    function hideSelectionView(memberId) {
      if (selectionViews.hasOwnProperty(memberId)) {
        selectionViews[memberId].hide();
      }
    }
    this.hideSelectionView = hideSelectionView;
    function showSelectionView(memberId) {
      if (selectionViews.hasOwnProperty(memberId)) {
        selectionViews[memberId].show();
      }
    }
    this.showSelectionView = showSelectionView;
    this.rerenderSelectionViews = function() {
      Object.keys(selectionViews).forEach(function(memberId) {
        selectionViews[memberId].rerender();
      });
    };
    this.registerCursor = function(cursor, virtualSelectionsInitiallyVisible) {
      var memberId = cursor.getMemberId(), selectionView = new SelectionView(cursor);
      if (virtualSelectionsInitiallyVisible) {
        selectionView.show();
      } else {
        selectionView.hide();
      }
      selectionViews[memberId] = selectionView;
      return selectionView;
    };
    this.destroy = function(callback) {
      var selectionViewArray = getSelectionViews();
      function destroySelectionView(i, err) {
        if (err) {
          callback(err);
        } else {
          if (i < selectionViewArray.length) {
            selectionViewArray[i].destroy(function(err) {
              destroySelectionView(i + 1, err);
            });
          } else {
            callback();
          }
        }
      }
      destroySelectionView(0, undefined);
    };
  };
  gui.SessionViewOptions = function() {
    this.editInfoMarkersInitiallyVisible = true;
    this.caretAvatarsInitiallyVisible = true;
    this.caretBlinksOnRangeSelect = true;
  };
  (function() {
    function configOption(userValue, defaultValue) {
      return userValue !== undefined ? Boolean(userValue) : defaultValue;
    }
    gui.SessionView = function SessionView(viewOptions, localMemberId, session, sessionConstraints, caretManager, selectionViewManager) {
      var avatarInfoStyles, annotationConstraintStyles, editInfons = "urn:webodf:names:editinfo", editInfoMap = {}, odtDocument, odfCanvas, highlightRefreshTask, showEditInfoMarkers = configOption(viewOptions.editInfoMarkersInitiallyVisible, true), showCaretAvatars = configOption(viewOptions.caretAvatarsInitiallyVisible, true), blinkOnRangeSelect = configOption(viewOptions.caretBlinksOnRangeSelect, true);
      function onAnnotationAdded(info) {
        if (info.memberId === localMemberId) {
          odfCanvas.getViewport().scrollIntoView(info.annotation.getBoundingClientRect());
        }
      }
      function newStyleSheet() {
        var head = document.getElementsByTagName("head").item(0), sheet = document.createElement("style");
        sheet.type = "text/css";
        sheet.media = "screen, print, handheld, projection";
        head.appendChild(sheet);
        return sheet;
      }
      function createAvatarInfoNodeMatch(nodeName, memberId, pseudoClass) {
        return nodeName + '[editinfo|memberid="' + memberId + '"]' + pseudoClass;
      }
      function getAvatarInfoStyle(nodeName, memberId, pseudoClass) {
        var node = avatarInfoStyles.firstChild, nodeMatch = createAvatarInfoNodeMatch(nodeName, memberId, pseudoClass) + "{";
        while (node) {
          if (node.nodeType === Node.TEXT_NODE && node.data.indexOf(nodeMatch) === 0) {
            return node;
          }
          node = node.nextSibling;
        }
        return null;
      }
      function setAvatarInfoStyle(memberId, name, color) {
        function setStyle(nodeName, rule, pseudoClass) {
          var styleRule = createAvatarInfoNodeMatch(nodeName, memberId, pseudoClass) + rule, styleNode = getAvatarInfoStyle(nodeName, memberId, pseudoClass);
          if (styleNode) {
            styleNode.data = styleRule;
          } else {
            avatarInfoStyles.appendChild(document.createTextNode(styleRule));
          }
        }
        setStyle("div.editInfoMarker", "{ background-color: " + color + "; }", "");
        setStyle("span.editInfoColor", "{ background-color: " + color + "; }", "");
        setStyle("span.editInfoAuthor", '{ content: "' + name + '"; }', ":before");
        setStyle("dc|creator", "{ background-color: " + color + "; }", "");
        setStyle(".webodf-selectionOverlay", "{ fill: " + color + "; stroke: " + color + ";}", "");
        if (memberId === localMemberId) {
          setStyle(".webodf-touchEnabled .webodf-selectionOverlay", "{ display: block; }", " > .webodf-draggable");
          memberId = gui.ShadowCursor.ShadowCursorMemberId;
          setStyle(".webodf-selectionOverlay", "{ fill: " + color + "; stroke: " + color + ";}", "");
          setStyle(".webodf-touchEnabled .webodf-selectionOverlay", "{ display: block; }", " > .webodf-draggable");
        }
      }
      function highlightEdit(element, memberId, timestamp) {
        var editInfo, editInfoMarker, id = "", editInfoNode = element.getElementsByTagNameNS(editInfons, "editinfo").item(0);
        if (editInfoNode) {
          id = editInfoNode.getAttributeNS(editInfons, "id");
          editInfoMarker = editInfoMap[id];
        } else {
          id = Math.random().toString();
          editInfo = new ops.EditInfo(element, session.getOdtDocument());
          editInfoMarker = new gui.EditInfoMarker(editInfo, showEditInfoMarkers);
          editInfoNode = element.getElementsByTagNameNS(editInfons, "editinfo").item(0);
          editInfoNode.setAttributeNS(editInfons, "id", id);
          editInfoMap[id] = editInfoMarker;
        }
        editInfoMarker.addEdit(memberId, new Date(timestamp));
      }
      function setEditInfoMarkerVisibility(visible) {
        var editInfoMarker, keyname;
        for (keyname in editInfoMap) {
          if (editInfoMap.hasOwnProperty(keyname)) {
            editInfoMarker = editInfoMap[keyname];
            if (visible) {
              editInfoMarker.show();
            } else {
              editInfoMarker.hide();
            }
          }
        }
      }
      function setCaretAvatarVisibility(visible) {
        caretManager.getCarets().forEach(function(caret) {
          if (visible) {
            caret.showHandle();
          } else {
            caret.hideHandle();
          }
        });
      }
      this.showEditInfoMarkers = function() {
        if (showEditInfoMarkers) {
          return;
        }
        showEditInfoMarkers = true;
        setEditInfoMarkerVisibility(showEditInfoMarkers);
      };
      this.hideEditInfoMarkers = function() {
        if (!showEditInfoMarkers) {
          return;
        }
        showEditInfoMarkers = false;
        setEditInfoMarkerVisibility(showEditInfoMarkers);
      };
      this.showCaretAvatars = function() {
        if (showCaretAvatars) {
          return;
        }
        showCaretAvatars = true;
        setCaretAvatarVisibility(showCaretAvatars);
      };
      this.hideCaretAvatars = function() {
        if (!showCaretAvatars) {
          return;
        }
        showCaretAvatars = false;
        setCaretAvatarVisibility(showCaretAvatars);
      };
      this.getSession = function() {
        return session;
      };
      this.getCaret = function(memberid) {
        return caretManager.getCaret(memberid);
      };
      function renderMemberData(member) {
        var memberId = member.getMemberId(), properties = member.getProperties();
        setAvatarInfoStyle(memberId, properties.fullName, properties.color);
      }
      function onCursorAdded(cursor) {
        var memberId = cursor.getMemberId(), properties = session.getOdtDocument().getMember(memberId).getProperties(), caret;
        caretManager.registerCursor(cursor, showCaretAvatars, blinkOnRangeSelect);
        selectionViewManager.registerCursor(cursor, true);
        caret = caretManager.getCaret(memberId);
        if (caret) {
          caret.setAvatarImageUrl(properties.imageUrl);
          caret.setColor(properties.color);
        }
        runtime.log("+++ View here +++ eagerly created an Caret for '" + memberId + "'! +++");
      }
      function onCursorMoved(cursor) {
        var memberId = cursor.getMemberId(), localSelectionView = selectionViewManager.getSelectionView(localMemberId), shadowSelectionView = selectionViewManager.getSelectionView(gui.ShadowCursor.ShadowCursorMemberId), localCaret = caretManager.getCaret(localMemberId);
        if (memberId === localMemberId) {
          shadowSelectionView.hide();
          if (localSelectionView) {
            localSelectionView.show();
          }
          if (localCaret) {
            localCaret.show();
          }
        } else {
          if (memberId === gui.ShadowCursor.ShadowCursorMemberId) {
            shadowSelectionView.show();
            if (localSelectionView) {
              localSelectionView.hide();
            }
            if (localCaret) {
              localCaret.hide();
            }
          }
        }
      }
      function onCursorRemoved(memberid) {
        selectionViewManager.removeSelectionView(memberid);
      }
      function onParagraphChanged(info) {
        highlightEdit(info.paragraphElement, info.memberId, info.timeStamp);
        highlightRefreshTask.trigger();
      }
      function refreshHighlights() {
        var annotationViewManager = odfCanvas.getAnnotationViewManager();
        if (annotationViewManager) {
          annotationViewManager.rehighlightAnnotations();
          odtDocument.fixCursorPositions();
        }
      }
      function processConstraints() {
        var localMemberName, cssString, localMember;
        if (annotationConstraintStyles.hasChildNodes()) {
          core.DomUtils.removeAllChildNodes(annotationConstraintStyles);
        }
        if (sessionConstraints.getState(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN) === true) {
          localMember = session.getOdtDocument().getMember(localMemberId);
          if (localMember) {
            localMemberName = localMember.getProperties().fullName;
            cssString = ".annotationWrapper:not([creator = '" + localMemberName + "']) .annotationRemoveButton { display: none; }";
            annotationConstraintStyles.appendChild(document.createTextNode(cssString));
          }
        }
      }
      function destroy(callback) {
        var editInfoArray = Object.keys(editInfoMap).map(function(keyname) {
          return editInfoMap[keyname];
        });
        odtDocument.unsubscribe(ops.Document.signalMemberAdded, renderMemberData);
        odtDocument.unsubscribe(ops.Document.signalMemberUpdated, renderMemberData);
        odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorAdded);
        odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
        odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
        odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorMoved);
        odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, selectionViewManager.rerenderSelectionViews);
        odtDocument.unsubscribe(ops.OdtDocument.signalTableAdded, selectionViewManager.rerenderSelectionViews);
        odtDocument.unsubscribe(ops.OdtDocument.signalParagraphStyleModified, selectionViewManager.rerenderSelectionViews);
        sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN, processConstraints);
        odtDocument.unsubscribe(ops.Document.signalMemberAdded, processConstraints);
        odtDocument.unsubscribe(ops.Document.signalMemberUpdated, processConstraints);
        avatarInfoStyles.parentNode.removeChild(avatarInfoStyles);
        annotationConstraintStyles.parentNode.removeChild(annotationConstraintStyles);
        (function destroyEditInfo(i, err) {
          if (err) {
            callback(err);
          } else {
            if (i < editInfoArray.length) {
              editInfoArray[i].destroy(function(err) {
                destroyEditInfo(i + 1, err);
              });
            } else {
              callback();
            }
          }
        })(0, undefined);
      }
      this.destroy = function(callback) {
        var cleanup = [highlightRefreshTask.destroy, destroy];
        odtDocument.unsubscribe(ops.OdtDocument.signalAnnotationAdded, onAnnotationAdded);
        core.Async.destroyAll(cleanup, callback);
      };
      function init() {
        odtDocument = session.getOdtDocument();
        odfCanvas = odtDocument.getOdfCanvas();
        odtDocument.subscribe(ops.OdtDocument.signalAnnotationAdded, onAnnotationAdded);
        odtDocument.subscribe(ops.Document.signalMemberAdded, renderMemberData);
        odtDocument.subscribe(ops.Document.signalMemberUpdated, renderMemberData);
        odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorAdded);
        odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
        odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
        odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorMoved);
        odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, selectionViewManager.rerenderSelectionViews);
        odtDocument.subscribe(ops.OdtDocument.signalTableAdded, selectionViewManager.rerenderSelectionViews);
        odtDocument.subscribe(ops.OdtDocument.signalParagraphStyleModified, selectionViewManager.rerenderSelectionViews);
        sessionConstraints.subscribe(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN, processConstraints);
        odtDocument.subscribe(ops.Document.signalMemberAdded, processConstraints);
        odtDocument.subscribe(ops.Document.signalMemberUpdated, processConstraints);
        avatarInfoStyles = newStyleSheet();
        avatarInfoStyles.appendChild(document.createTextNode("@namespace editinfo url(urn:webodf:names:editinfo);"));
        avatarInfoStyles.appendChild(document.createTextNode("@namespace dc url(http://purl.org/dc/elements/1.1/);"));
        annotationConstraintStyles = newStyleSheet();
        processConstraints();
        highlightRefreshTask = core.Task.createRedrawTask(refreshHighlights);
      }
      init();
    };
  })();
  gui.SvgSelectionView = function SvgSelectionView(cursor) {
    var document = cursor.getDocument(), documentRoot, sizer, doc = document.getDOMDocument(), svgns = "http://www.w3.org/2000/svg", overlay = doc.createElementNS(svgns, "svg"), polygon = doc.createElementNS(svgns, "polygon"), handle1 = doc.createElementNS(svgns, "circle"), handle2 = doc.createElementNS(svgns, "circle"), odfUtils = odf.OdfUtils, domUtils = core.DomUtils, zoomHelper = document.getCanvas().getZoomHelper(), isVisible = true, positionIterator = cursor.getDocument().createPositionIterator(document.getRootNode()),
    FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT, FILTER_REJECT = NodeFilter.FILTER_REJECT, HANDLE_RADIUS = 8, renderTask;
    function addOverlay() {
      var newDocumentRoot = document.getRootNode();
      if (documentRoot !== newDocumentRoot) {
        documentRoot = newDocumentRoot;
        sizer = document.getCanvas().getSizer();
        sizer.appendChild(overlay);
        overlay.setAttribute("class", "webodf-selectionOverlay");
        handle1.setAttribute("class", "webodf-draggable");
        handle2.setAttribute("class", "webodf-draggable");
        handle1.setAttribute("end", "left");
        handle2.setAttribute("end", "right");
        handle1.setAttribute("r", HANDLE_RADIUS);
        handle2.setAttribute("r", HANDLE_RADIUS);
        overlay.appendChild(polygon);
        overlay.appendChild(handle1);
        overlay.appendChild(handle2);
      }
    }
    function isRangeVisible(range) {
      var bcr = range.getBoundingClientRect();
      return Boolean(bcr && bcr.height !== 0);
    }
    function lastVisibleRect(range, nodes) {
      var nextNodeIndex = nodes.length - 1, node = nodes[nextNodeIndex], startOffset, endOffset;
      if (range.endContainer === node) {
        startOffset = range.endOffset;
      } else {
        if (node.nodeType === Node.TEXT_NODE) {
          startOffset = node.length;
        } else {
          startOffset = node.childNodes.length;
        }
      }
      endOffset = startOffset;
      range.setStart(node, startOffset);
      range.setEnd(node, endOffset);
      while (!isRangeVisible(range)) {
        if (node.nodeType === Node.ELEMENT_NODE && startOffset > 0) {
          startOffset = 0;
        } else {
          if (node.nodeType === Node.TEXT_NODE && startOffset > 0) {
            startOffset -= 1;
          } else {
            if (nodes[nextNodeIndex]) {
              node = nodes[nextNodeIndex];
              nextNodeIndex -= 1;
              startOffset = endOffset = node.length || node.childNodes.length;
            } else {
              return false;
            }
          }
        }
        range.setStart(node, startOffset);
        range.setEnd(node, endOffset);
      }
      return true;
    }
    function firstVisibleRect(range, nodes) {
      var nextNodeIndex = 0, node = nodes[nextNodeIndex], startOffset = range.startContainer === node ? range.startOffset : 0, endOffset = startOffset;
      range.setStart(node, startOffset);
      range.setEnd(node, endOffset);
      while (!isRangeVisible(range)) {
        if (node.nodeType === Node.ELEMENT_NODE && endOffset < node.childNodes.length) {
          endOffset = node.childNodes.length;
        } else {
          if (node.nodeType === Node.TEXT_NODE && endOffset < node.length) {
            endOffset += 1;
          } else {
            if (nodes[nextNodeIndex]) {
              node = nodes[nextNodeIndex];
              nextNodeIndex += 1;
              startOffset = endOffset = 0;
            } else {
              return false;
            }
          }
        }
        range.setStart(node, startOffset);
        range.setEnd(node, endOffset);
      }
      return true;
    }
    function getExtremeRanges(range) {
      var nodes = odfUtils.getTextElements(range, true, false), firstRange = range.cloneRange(), lastRange = range.cloneRange(), fillerRange = range.cloneRange();
      if (!nodes.length) {
        return null;
      }
      if (!firstVisibleRect(firstRange, nodes)) {
        return null;
      }
      if (!lastVisibleRect(lastRange, nodes)) {
        return null;
      }
      fillerRange.setStart(firstRange.startContainer, firstRange.startOffset);
      fillerRange.setEnd(lastRange.endContainer, lastRange.endOffset);
      return {firstRange:firstRange, lastRange:lastRange, fillerRange:fillerRange};
    }
    function getBoundingRect(rect1, rect2) {
      var resultRect = {};
      resultRect.top = Math.min(rect1.top, rect2.top);
      resultRect.left = Math.min(rect1.left, rect2.left);
      resultRect.right = Math.max(rect1.right, rect2.right);
      resultRect.bottom = Math.max(rect1.bottom, rect2.bottom);
      resultRect.width = resultRect.right - resultRect.left;
      resultRect.height = resultRect.bottom - resultRect.top;
      return resultRect;
    }
    function checkAndGrowOrCreateRect(originalRect, newRect) {
      if (newRect && newRect.width > 0 && newRect.height > 0) {
        if (!originalRect) {
          originalRect = newRect;
        } else {
          originalRect = getBoundingRect(originalRect, newRect);
        }
      }
      return originalRect;
    }
    function getFillerRect(fillerRange) {
      var containerNode = fillerRange.commonAncestorContainer, firstNode = fillerRange.startContainer, lastNode = fillerRange.endContainer, firstOffset = fillerRange.startOffset, lastOffset = fillerRange.endOffset, currentNode, lastMeasuredNode, firstSibling, lastSibling, grownRect = null, currentRect, range = doc.createRange(), rootFilter, odfNodeFilter = new odf.OdfNodeFilter, treeWalker;
      function acceptNode(node) {
        positionIterator.setUnfilteredPosition(node, 0);
        if (odfNodeFilter.acceptNode(node) === FILTER_ACCEPT && rootFilter.acceptPosition(positionIterator) === FILTER_ACCEPT) {
          return FILTER_ACCEPT;
        }
        return FILTER_REJECT;
      }
      function getRectFromNodeAfterFiltering(node) {
        var rect = null;
        if (acceptNode(node) === FILTER_ACCEPT) {
          rect = domUtils.getBoundingClientRect(node);
        }
        return rect;
      }
      if (firstNode === containerNode || lastNode === containerNode) {
        range = fillerRange.cloneRange();
        grownRect = range.getBoundingClientRect();
        range.detach();
        return grownRect;
      }
      firstSibling = firstNode;
      while (firstSibling.parentNode !== containerNode) {
        firstSibling = firstSibling.parentNode;
      }
      lastSibling = lastNode;
      while (lastSibling.parentNode !== containerNode) {
        lastSibling = lastSibling.parentNode;
      }
      rootFilter = document.createRootFilter(firstNode);
      currentNode = firstSibling.nextSibling;
      while (currentNode && currentNode !== lastSibling) {
        currentRect = getRectFromNodeAfterFiltering(currentNode);
        grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
        currentNode = currentNode.nextSibling;
      }
      if (odfUtils.isParagraph(firstSibling)) {
        grownRect = checkAndGrowOrCreateRect(grownRect, domUtils.getBoundingClientRect(firstSibling));
      } else {
        if (firstSibling.nodeType === Node.TEXT_NODE) {
          currentNode = firstSibling;
          range.setStart(currentNode, firstOffset);
          range.setEnd(currentNode, currentNode === lastSibling ? lastOffset : currentNode.length);
          currentRect = range.getBoundingClientRect();
          grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
        } else {
          treeWalker = doc.createTreeWalker(firstSibling, NodeFilter.SHOW_TEXT, acceptNode, false);
          currentNode = treeWalker.currentNode = firstNode;
          while (currentNode && currentNode !== lastNode) {
            range.setStart(currentNode, firstOffset);
            range.setEnd(currentNode, currentNode.length);
            currentRect = range.getBoundingClientRect();
            grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
            lastMeasuredNode = currentNode;
            firstOffset = 0;
            currentNode = treeWalker.nextNode();
          }
        }
      }
      if (!lastMeasuredNode) {
        lastMeasuredNode = firstNode;
      }
      if (odfUtils.isParagraph(lastSibling)) {
        grownRect = checkAndGrowOrCreateRect(grownRect, domUtils.getBoundingClientRect(lastSibling));
      } else {
        if (lastSibling.nodeType === Node.TEXT_NODE) {
          currentNode = lastSibling;
          range.setStart(currentNode, currentNode === firstSibling ? firstOffset : 0);
          range.setEnd(currentNode, lastOffset);
          currentRect = range.getBoundingClientRect();
          grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
        } else {
          treeWalker = doc.createTreeWalker(lastSibling, NodeFilter.SHOW_TEXT, acceptNode, false);
          currentNode = treeWalker.currentNode = lastNode;
          while (currentNode && currentNode !== lastMeasuredNode) {
            range.setStart(currentNode, 0);
            range.setEnd(currentNode, lastOffset);
            currentRect = range.getBoundingClientRect();
            grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
            currentNode = treeWalker.previousNode();
            if (currentNode) {
              lastOffset = currentNode.length;
            }
          }
        }
      }
      return grownRect;
    }
    function getCollapsedRectOfTextRange(range, useRightEdge) {
      var clientRect = range.getBoundingClientRect(), collapsedRect = {};
      collapsedRect.width = 0;
      collapsedRect.top = clientRect.top;
      collapsedRect.bottom = clientRect.bottom;
      collapsedRect.height = clientRect.height;
      collapsedRect.left = collapsedRect.right = useRightEdge ? clientRect.right : clientRect.left;
      return collapsedRect;
    }
    function setPoints(points) {
      var pointsString = "", i;
      for (i = 0;i < points.length;i += 1) {
        pointsString += points[i].x + "," + points[i].y + " ";
      }
      polygon.setAttribute("points", pointsString);
    }
    function repositionOverlays(selectedRange) {
      var rootRect = domUtils.getBoundingClientRect(sizer), zoomLevel = zoomHelper.getZoomLevel(), extremes = getExtremeRanges(selectedRange), firstRange, lastRange, fillerRange, firstRect, fillerRect, lastRect, left, right, top, bottom;
      if (extremes) {
        firstRange = extremes.firstRange;
        lastRange = extremes.lastRange;
        fillerRange = extremes.fillerRange;
        firstRect = domUtils.translateRect(getCollapsedRectOfTextRange(firstRange, false), rootRect, zoomLevel);
        lastRect = domUtils.translateRect(getCollapsedRectOfTextRange(lastRange, true), rootRect, zoomLevel);
        fillerRect = getFillerRect(fillerRange);
        if (!fillerRect) {
          fillerRect = getBoundingRect(firstRect, lastRect);
        } else {
          fillerRect = domUtils.translateRect(fillerRect, rootRect, zoomLevel);
        }
        left = fillerRect.left;
        right = firstRect.left + Math.max(0, fillerRect.width - (firstRect.left - fillerRect.left));
        top = Math.min(firstRect.top, lastRect.top);
        bottom = lastRect.top + lastRect.height;
        setPoints([{x:firstRect.left, y:top + firstRect.height}, {x:firstRect.left, y:top}, {x:right, y:top}, {x:right, y:bottom - lastRect.height}, {x:lastRect.right, y:bottom - lastRect.height}, {x:lastRect.right, y:bottom}, {x:left, y:bottom}, {x:left, y:top + firstRect.height}, {x:firstRect.left, y:top + firstRect.height}]);
        handle1.setAttribute("cx", firstRect.left);
        handle1.setAttribute("cy", top + firstRect.height / 2);
        handle2.setAttribute("cx", lastRect.right);
        handle2.setAttribute("cy", bottom - lastRect.height / 2);
        firstRange.detach();
        lastRange.detach();
        fillerRange.detach();
      }
      return Boolean(extremes);
    }
    function rerender() {
      var range = cursor.getSelectedRange(), shouldShow;
      shouldShow = isVisible && cursor.getSelectionType() === ops.OdtCursor.RangeSelection && !range.collapsed;
      if (shouldShow) {
        addOverlay();
        shouldShow = repositionOverlays(range);
      }
      if (shouldShow) {
        overlay.style.display = "block";
      } else {
        overlay.style.display = "none";
      }
    }
    this.rerender = function() {
      if (isVisible) {
        renderTask.trigger();
      }
    };
    this.show = function() {
      isVisible = true;
      renderTask.trigger();
    };
    this.hide = function() {
      isVisible = false;
      renderTask.trigger();
    };
    function handleCursorMove(movedCursor) {
      if (isVisible && movedCursor === cursor) {
        renderTask.trigger();
      }
    }
    function scaleHandles(zoomLevel) {
      var radius = HANDLE_RADIUS / zoomLevel;
      handle1.setAttribute("r", radius);
      handle2.setAttribute("r", radius);
    }
    function destroy(callback) {
      sizer.removeChild(overlay);
      sizer.classList.remove("webodf-virtualSelections");
      cursor.getDocument().unsubscribe(ops.Document.signalCursorMoved, handleCursorMove);
      zoomHelper.unsubscribe(gui.ZoomHelper.signalZoomChanged, scaleHandles);
      callback();
    }
    this.destroy = function(callback) {
      core.Async.destroyAll([renderTask.destroy, destroy], callback);
    };
    function init() {
      var editinfons = "urn:webodf:names:editinfo", memberid = cursor.getMemberId();
      renderTask = core.Task.createRedrawTask(rerender);
      addOverlay();
      overlay.setAttributeNS(editinfons, "editinfo:memberid", memberid);
      sizer.classList.add("webodf-virtualSelections");
      cursor.getDocument().subscribe(ops.Document.signalCursorMoved, handleCursorMove);
      zoomHelper.subscribe(gui.ZoomHelper.signalZoomChanged, scaleHandles);
      scaleHandles(zoomHelper.getZoomLevel());
    }
    init();
  };
  gui.UndoStateRules = function UndoStateRules() {
    function ReverseIterator(array, predicate) {
      var index = array.length;
      this.previous = function() {
        for (index = index - 1;index >= 0;index -= 1) {
          if (predicate(array[index])) {
            return array[index];
          }
        }
        return null;
      };
    }
    function getOpType(op) {
      return op.spec().optype;
    }
    function getOpPosition(op) {
      var key = "position", spec = op.spec(), value;
      if (spec.hasOwnProperty(key)) {
        value = spec[key];
      }
      return value;
    }
    function isEditOperation(op) {
      return op.isEdit;
    }
    this.isEditOperation = isEditOperation;
    function canAggregateOperation(op) {
      switch(getOpType(op)) {
        case "RemoveText":
        ;
        case "InsertText":
          return true;
        default:
          return false;
      }
    }
    function isSameDirectionOfTravel(thisOp, lastEditOp, secondLastEditOp) {
      var thisPosition = getOpPosition(thisOp), lastPosition = getOpPosition(lastEditOp), secondLastPosition = getOpPosition(secondLastEditOp), diffLastToSecondLast = lastPosition - secondLastPosition, diffThisToLast = thisPosition - lastPosition;
      return diffThisToLast === diffLastToSecondLast;
    }
    function isAdjacentOperation(thisOp, lastEditOp) {
      var positionDifference = getOpPosition(thisOp) - getOpPosition(lastEditOp);
      return positionDifference === 0 || Math.abs(positionDifference) === 1;
    }
    function continuesOperations(thisOp, lastEditOp, secondLastEditOp) {
      if (!secondLastEditOp) {
        return isAdjacentOperation(thisOp, lastEditOp);
      }
      return isSameDirectionOfTravel(thisOp, lastEditOp, secondLastEditOp);
    }
    function continuesMostRecentEditOperation(thisOp, recentOperations) {
      var thisOpType = getOpType(thisOp), editOpsFinder = new ReverseIterator(recentOperations, isEditOperation), lastEditOp = editOpsFinder.previous();
      runtime.assert(Boolean(lastEditOp), "No edit operations found in state");
      if (thisOpType === getOpType(lastEditOp)) {
        return continuesOperations(thisOp, lastEditOp, editOpsFinder.previous());
      }
      return false;
    }
    function continuesMostRecentEditGroup(thisOp, recentOperations) {
      var thisOpType = getOpType(thisOp), editOpsFinder = new ReverseIterator(recentOperations, isEditOperation), candidateOp = editOpsFinder.previous(), lastEditOp, secondLastEditOp = null, inspectedGroupsCount, groupId;
      runtime.assert(Boolean(candidateOp), "No edit operations found in state");
      groupId = candidateOp.group;
      runtime.assert(groupId !== undefined, "Operation has no group");
      inspectedGroupsCount = 1;
      while (candidateOp && candidateOp.group === groupId) {
        if (thisOpType === getOpType(candidateOp)) {
          lastEditOp = candidateOp;
          break;
        }
        candidateOp = editOpsFinder.previous();
      }
      if (lastEditOp) {
        candidateOp = editOpsFinder.previous();
        while (candidateOp) {
          if (candidateOp.group !== groupId) {
            if (inspectedGroupsCount === 2) {
              break;
            }
            groupId = candidateOp.group;
            inspectedGroupsCount += 1;
          }
          if (thisOpType === getOpType(candidateOp)) {
            secondLastEditOp = candidateOp;
            break;
          }
          candidateOp = editOpsFinder.previous();
        }
        return continuesOperations(thisOp, lastEditOp, secondLastEditOp);
      }
      return false;
    }
    function isPartOfOperationSet(operation, recentOperations) {
      var areOperationsGrouped = operation.group !== undefined, lastOperation;
      if (!isEditOperation(operation)) {
        return true;
      }
      if (recentOperations.length === 0) {
        return true;
      }
      lastOperation = recentOperations[recentOperations.length - 1];
      if (areOperationsGrouped && operation.group === lastOperation.group) {
        return true;
      }
      if (canAggregateOperation(operation) && recentOperations.some(isEditOperation)) {
        if (areOperationsGrouped) {
          return continuesMostRecentEditGroup(operation, recentOperations);
        }
        return continuesMostRecentEditOperation(operation, recentOperations);
      }
      return false;
    }
    this.isPartOfOperationSet = isPartOfOperationSet;
  };
  (function() {
    var stateIdBase = 0;
    function StateId(mainId, subId) {
      this.mainId = mainId !== undefined ? mainId : -1;
      this.subId = subId !== undefined ? subId : -1;
    }
    function StateTransition(undoRules, initialOps, editOpsPossible) {
      var nextStateId, operations, editOpsCount;
      this.addOperation = function(op) {
        if (undoRules.isEditOperation(op)) {
          editOpsCount += 1;
        }
        operations.push(op);
      };
      this.isNextStateId = function(stateId) {
        return stateId.mainId === nextStateId && stateId.subId === editOpsCount;
      };
      this.getNextStateId = function() {
        return new StateId(nextStateId, editOpsCount);
      };
      this.getOperations = function() {
        return operations;
      };
      function addEditOpsCount(count, op) {
        return count + (undoRules.isEditOperation(op) ? 1 : 0);
      }
      function init() {
        stateIdBase += 1;
        nextStateId = stateIdBase;
        operations = initialOps || [];
        editOpsCount = initialOps && editOpsPossible ? initialOps.reduce(addEditOpsCount, 0) : 0;
      }
      init();
    }
    gui.TrivialUndoManager = function TrivialUndoManager(defaultRules) {
      var self = this, cursorns = "urn:webodf:names:cursor", domUtils = core.DomUtils, initialDoc, initialStateTransition, playFunc, document, unmodifiedStateId, currentUndoStateTransition, undoStateTransitions = [], redoStateTransitions = [], eventNotifier = new core.EventNotifier([gui.UndoManager.signalUndoStackChanged, gui.UndoManager.signalUndoStateCreated, gui.UndoManager.signalUndoStateModified, gui.UndoManager.signalDocumentModifiedChanged, gui.TrivialUndoManager.signalDocumentRootReplaced]),
      undoRules = defaultRules || new gui.UndoStateRules, isExecutingOps = false;
      function isModified() {
        return currentUndoStateTransition.isNextStateId(unmodifiedStateId) !== true;
      }
      function executeOperations(stateTransition) {
        var operations = stateTransition.getOperations();
        if (operations.length > 0) {
          isExecutingOps = true;
          playFunc(operations);
          isExecutingOps = false;
        }
      }
      function emitStackChange() {
        eventNotifier.emit(gui.UndoManager.signalUndoStackChanged, {undoAvailable:self.hasUndoStates(), redoAvailable:self.hasRedoStates()});
      }
      function emitDocumentModifiedChange(oldModified) {
        var newModified = isModified();
        if (oldModified !== newModified) {
          eventNotifier.emit(gui.UndoManager.signalDocumentModifiedChanged, newModified);
        }
      }
      function mostRecentUndoStateTransition() {
        return undoStateTransitions[undoStateTransitions.length - 1];
      }
      function completeCurrentUndoState() {
        if (currentUndoStateTransition !== initialStateTransition && currentUndoStateTransition !== mostRecentUndoStateTransition()) {
          undoStateTransitions.push(currentUndoStateTransition);
        }
      }
      function removeNode(node) {
        var sibling = node.previousSibling || node.nextSibling;
        node.parentNode.removeChild(node);
        domUtils.normalizeTextNodes(sibling);
      }
      function removeCursors(root) {
        domUtils.getElementsByTagNameNS(root, cursorns, "cursor").forEach(removeNode);
        domUtils.getElementsByTagNameNS(root, cursorns, "anchor").forEach(removeNode);
      }
      function values(obj) {
        return Object.keys(obj).map(function(key) {
          return obj[key];
        });
      }
      function extractCursorStates(undoStateTransitions) {
        var addCursor = {}, moveCursor = {}, requiredAddOps = {}, remainingAddOps, ops, stateTransition = undoStateTransitions.pop();
        document.getMemberIds().forEach(function(memberid) {
          requiredAddOps[memberid] = true;
        });
        remainingAddOps = Object.keys(requiredAddOps).length;
        function processOp(op) {
          var spec = op.spec();
          if (!requiredAddOps[spec.memberid]) {
            return;
          }
          switch(spec.optype) {
            case "AddCursor":
              if (!addCursor[spec.memberid]) {
                addCursor[spec.memberid] = op;
                delete requiredAddOps[spec.memberid];
                remainingAddOps -= 1;
              }
              break;
            case "MoveCursor":
              if (!moveCursor[spec.memberid]) {
                moveCursor[spec.memberid] = op;
              }
              break;
          }
        }
        while (stateTransition && remainingAddOps > 0) {
          ops = stateTransition.getOperations();
          ops.reverse();
          ops.forEach(processOp);
          stateTransition = undoStateTransitions.pop();
        }
        return new StateTransition(undoRules, values(addCursor).concat(values(moveCursor)));
      }
      this.subscribe = function(signal, callback) {
        eventNotifier.subscribe(signal, callback);
      };
      this.unsubscribe = function(signal, callback) {
        eventNotifier.unsubscribe(signal, callback);
      };
      this.isDocumentModified = isModified;
      this.setDocumentModified = function(modified) {
        if (isModified() === modified) {
          return;
        }
        if (modified) {
          unmodifiedStateId = new StateId;
        } else {
          unmodifiedStateId = currentUndoStateTransition.getNextStateId();
        }
        eventNotifier.emit(gui.UndoManager.signalDocumentModifiedChanged, modified);
      };
      this.hasUndoStates = function() {
        return undoStateTransitions.length > 0;
      };
      this.hasRedoStates = function() {
        return redoStateTransitions.length > 0;
      };
      this.setDocument = function(newDocument) {
        document = newDocument;
      };
      this.purgeInitialState = function() {
        var oldModified = isModified();
        undoStateTransitions.length = 0;
        redoStateTransitions.length = 0;
        currentUndoStateTransition = initialStateTransition = new StateTransition(undoRules);
        unmodifiedStateId = currentUndoStateTransition.getNextStateId();
        initialDoc = null;
        emitStackChange();
        emitDocumentModifiedChange(oldModified);
      };
      function setInitialState() {
        var oldModified = isModified();
        initialDoc = document.cloneDocumentElement();
        removeCursors(initialDoc);
        completeCurrentUndoState();
        currentUndoStateTransition = initialStateTransition = extractCursorStates([initialStateTransition].concat(undoStateTransitions));
        undoStateTransitions.length = 0;
        redoStateTransitions.length = 0;
        if (!oldModified) {
          unmodifiedStateId = currentUndoStateTransition.getNextStateId();
        }
        emitStackChange();
        emitDocumentModifiedChange(oldModified);
      }
      this.setInitialState = setInitialState;
      this.initialize = function() {
        if (!initialDoc) {
          setInitialState();
        }
      };
      this.setPlaybackFunction = function(playback_func) {
        playFunc = playback_func;
      };
      this.onOperationExecuted = function(op) {
        if (isExecutingOps) {
          return;
        }
        var oldModified = isModified();
        if (undoRules.isEditOperation(op) && (currentUndoStateTransition === initialStateTransition || redoStateTransitions.length > 0) || !undoRules.isPartOfOperationSet(op, currentUndoStateTransition.getOperations())) {
          redoStateTransitions.length = 0;
          completeCurrentUndoState();
          currentUndoStateTransition = new StateTransition(undoRules, [op], true);
          undoStateTransitions.push(currentUndoStateTransition);
          eventNotifier.emit(gui.UndoManager.signalUndoStateCreated, {operations:currentUndoStateTransition.getOperations()});
          emitStackChange();
        } else {
          currentUndoStateTransition.addOperation(op);
          eventNotifier.emit(gui.UndoManager.signalUndoStateModified, {operations:currentUndoStateTransition.getOperations()});
        }
        emitDocumentModifiedChange(oldModified);
      };
      this.moveForward = function(states) {
        var moved = 0, oldModified = isModified(), redoOperations;
        while (states && redoStateTransitions.length) {
          redoOperations = redoStateTransitions.pop();
          undoStateTransitions.push(redoOperations);
          executeOperations(redoOperations);
          states -= 1;
          moved += 1;
        }
        if (moved) {
          currentUndoStateTransition = mostRecentUndoStateTransition();
          emitStackChange();
          emitDocumentModifiedChange(oldModified);
        }
        return moved;
      };
      this.moveBackward = function(states) {
        var moved = 0, oldModified = isModified();
        while (states && undoStateTransitions.length) {
          redoStateTransitions.push(undoStateTransitions.pop());
          states -= 1;
          moved += 1;
        }
        if (moved) {
          document.getMemberIds().forEach(function(memberid) {
            if (document.hasCursor(memberid)) {
              document.removeCursor(memberid);
            }
          });
          document.setDocumentElement(initialDoc.cloneNode(true));
          eventNotifier.emit(gui.TrivialUndoManager.signalDocumentRootReplaced, {});
          executeOperations(initialStateTransition);
          undoStateTransitions.forEach(executeOperations);
          currentUndoStateTransition = mostRecentUndoStateTransition() || initialStateTransition;
          emitStackChange();
          emitDocumentModifiedChange(oldModified);
        }
        return moved;
      };
      function init() {
        currentUndoStateTransition = initialStateTransition = new StateTransition(undoRules);
        unmodifiedStateId = currentUndoStateTransition.getNextStateId();
      }
      init();
    };
    gui.TrivialUndoManager.signalDocumentRootReplaced = "documentRootReplaced";
  })();
  odf.GraphicProperties = function(element, styleParseUtils, parent) {
    var self = this, stylens = odf.Namespaces.stylens, svgns = odf.Namespaces.svgns, getter;
    getter = {verticalPos:function() {
      var v = element.getAttributeNS(stylens, "vertical-pos");
      return v === "" ? undefined : v;
    }, verticalRel:function() {
      var v = element.getAttributeNS(stylens, "vertical-rel");
      return v === "" ? undefined : v;
    }, horizontalPos:function() {
      var v = element.getAttributeNS(stylens, "horizontal-pos");
      return v === "" ? undefined : v;
    }, horizontalRel:function() {
      var v = element.getAttributeNS(stylens, "horizontal-rel");
      return v === "" ? undefined : v;
    }, strokeWidth:function() {
      var a = element.getAttributeNS(svgns, "stroke-width");
      return styleParseUtils.parseLength(a);
    }};
    this.verticalPos = function() {
      return self.data.value("verticalPos");
    };
    this.verticalRel = function() {
      return self.data.value("verticalRel");
    };
    this.horizontalPos = function() {
      return self.data.value("horizontalPos");
    };
    this.horizontalRel = function() {
      return self.data.value("horizontalRel");
    };
    this.strokeWidth = function() {
      return self.data.value("strokeWidth");
    };
    this.data;
    function init() {
      var p = parent === undefined ? undefined : parent.data;
      self.data = new odf.LazyStyleProperties(p, getter);
    }
    init();
  };
  odf.ComputedGraphicProperties = function() {
    var g;
    this.setGraphicProperties = function(graphicProperties) {
      g = graphicProperties;
    };
    this.verticalPos = function() {
      return g && g.verticalPos() || "from-top";
    };
    this.verticalRel = function() {
      return g && g.verticalRel() || "page";
    };
    this.horizontalPos = function() {
      return g && g.horizontalPos() || "from-left";
    };
    this.horizontalRel = function() {
      return g && g.horizontalRel() || "page";
    };
  };
  odf.PageLayoutProperties = function(element, styleParseUtils, parent) {
    var self = this, fons = odf.Namespaces.fons, getter;
    getter = {pageHeight:function() {
      var a, value;
      if (element) {
        a = element.getAttributeNS(fons, "page-height");
        value = styleParseUtils.parseLength(a);
      }
      return value;
    }, pageWidth:function() {
      var a, value;
      if (element) {
        a = element.getAttributeNS(fons, "page-width");
        value = styleParseUtils.parseLength(a);
      }
      return value;
    }};
    this.pageHeight = function() {
      return self.data.value("pageHeight") || 1123;
    };
    this.pageWidth = function() {
      return self.data.value("pageWidth") || 794;
    };
    this.data;
    function init() {
      var p = parent === undefined ? undefined : parent.data;
      self.data = new odf.LazyStyleProperties(p, getter);
    }
    init();
  };
  odf.PageLayout = function(element, styleParseUtils, parent) {
    var self = this;
    this.pageLayout;
    function init() {
      var e = null;
      if (element) {
        e = styleParseUtils.getPropertiesElement("page-layout-properties", element);
      }
      self.pageLayout = new odf.PageLayoutProperties(e, styleParseUtils, parent && parent.pageLayout);
    }
    init();
  };
  odf.PageLayoutCache = function() {
  };
  odf.PageLayoutCache.prototype.getPageLayout = function(name) {
  };
  odf.PageLayoutCache.prototype.getDefaultPageLayout = function() {
  };
  odf.ParagraphProperties = function(element, styleParseUtils, parent) {
    var self = this, fons = odf.Namespaces.fons, getter;
    getter = {marginTop:function() {
      var a = element.getAttributeNS(fons, "margin-top"), value = styleParseUtils.parsePositiveLengthOrPercent(a, "marginTop", parent && parent.data);
      return value;
    }};
    this.marginTop = function() {
      return self.data.value("marginTop");
    };
    this.data;
    function init() {
      var p = parent === undefined ? undefined : parent.data;
      self.data = new odf.LazyStyleProperties(p, getter);
    }
    init();
  };
  odf.ComputedParagraphProperties = function() {
    var data = {}, styleChain = [];
    function value(name) {
      var v, i;
      if (data.hasOwnProperty(name)) {
        v = data[name];
      } else {
        for (i = 0;v === undefined && i < styleChain.length;i += 1) {
          v = styleChain[i][name]();
        }
        data[name] = v;
      }
      return v;
    }
    this.setStyleChain = function setStyleChain(newStyleChain) {
      styleChain = newStyleChain;
      data = {};
    };
    this.marginTop = function() {
      return value("marginTop") || 0;
    };
  };
  odf.TextProperties = function(element, styleParseUtils, parent) {
    var self = this, fons = odf.Namespaces.fons, getter;
    getter = {fontSize:function() {
      var a = element.getAttributeNS(fons, "font-size"), value = styleParseUtils.parsePositiveLengthOrPercent(a, "fontSize", parent && parent.data);
      return value;
    }};
    this.fontSize = function() {
      return self.data.value("fontSize");
    };
    this.data;
    function init() {
      var p = parent === undefined ? undefined : parent.data;
      self.data = new odf.LazyStyleProperties(p, getter);
    }
    init();
  };
  odf.ComputedTextProperties = function() {
    var data = {}, styleChain = [];
    function value(name) {
      var v, i;
      if (data.hasOwnProperty(name)) {
        v = data[name];
      } else {
        for (i = 0;v === undefined && i < styleChain.length;i += 1) {
          v = styleChain[i][name]();
        }
        data[name] = v;
      }
      return v;
    }
    this.setStyleChain = function setStyleChain(newStyleChain) {
      styleChain = newStyleChain;
      data = {};
    };
    this.fontSize = function() {
      return value("fontSize") || 12;
    };
  };
  odf.MasterPage = function(element, pageLayoutCache) {
    var self = this;
    this.pageLayout;
    function init() {
      var pageLayoutName;
      if (element) {
        pageLayoutName = element.getAttributeNS(odf.Namespaces.stylens, "page-layout-name");
        self.pageLayout = pageLayoutCache.getPageLayout(pageLayoutName);
      } else {
        self.pageLayout = pageLayoutCache.getDefaultPageLayout();
      }
    }
    init();
  };
  odf.MasterPageCache = function() {
  };
  odf.MasterPageCache.prototype.getMasterPage = function(name) {
  };
  odf.StylePileEntry = function(element, styleParseUtils, masterPageCache, parent) {
    this.text;
    this.paragraph;
    this.graphic;
    this.masterPage = function() {
      var masterPageName = element.getAttributeNS(odf.Namespaces.stylens, "master-page-name"), masterPage = null;
      if (masterPageName) {
        masterPage = masterPageCache.getMasterPage(masterPageName);
      }
      return masterPage;
    };
    function init(self) {
      var stylens = odf.Namespaces.stylens, family = element.getAttributeNS(stylens, "family"), e = null;
      if (family === "graphic" || family === "chart") {
        self.graphic = parent === undefined ? undefined : parent.graphic;
        e = styleParseUtils.getPropertiesElement("graphic-properties", element, e);
        if (e !== null) {
          self.graphic = new odf.GraphicProperties(e, styleParseUtils, self.graphic);
        }
      }
      if (family === "paragraph" || family === "table-cell" || family === "graphic" || family === "presentation" || family === "chart") {
        self.paragraph = parent === undefined ? undefined : parent.paragraph;
        e = styleParseUtils.getPropertiesElement("paragraph-properties", element, e);
        if (e !== null) {
          self.paragraph = new odf.ParagraphProperties(e, styleParseUtils, self.paragraph);
        }
      }
      if (family === "text" || family === "paragraph" || family === "table-cell" || family === "graphic" || family === "presentation" || family === "chart") {
        self.text = parent === undefined ? undefined : parent.text;
        e = styleParseUtils.getPropertiesElement("text-properties", element, e);
        if (e !== null) {
          self.text = new odf.TextProperties(e, styleParseUtils, self.text);
        }
      }
    }
    init(this);
  };
  odf.StylePile = function(styleParseUtils, masterPageCache) {
    var stylens = odf.Namespaces.stylens, commonStyles = {}, automaticStyles = {}, defaultStyle, parsedCommonStyles = {}, parsedAutomaticStyles = {}, getCommonStyle;
    function parseStyle(element, visitedStyles) {
      var parent, parentName, style;
      if (element.hasAttributeNS(stylens, "parent-style-name")) {
        parentName = element.getAttributeNS(stylens, "parent-style-name");
        if (visitedStyles.indexOf(parentName) === -1) {
          parent = getCommonStyle(parentName, visitedStyles);
        }
      }
      style = new odf.StylePileEntry(element, styleParseUtils, masterPageCache, parent);
      return style;
    }
    getCommonStyle = function(styleName, visitedStyles) {
      var style = parsedCommonStyles[styleName], element;
      if (!style) {
        element = commonStyles[styleName];
        if (element) {
          visitedStyles.push(styleName);
          style = parseStyle(element, visitedStyles);
          parsedCommonStyles[styleName] = style;
        }
      }
      return style;
    };
    function getStyle(styleName) {
      var style = parsedAutomaticStyles[styleName] || parsedCommonStyles[styleName], element, visitedStyles = [];
      if (!style) {
        element = automaticStyles[styleName];
        if (!element) {
          element = commonStyles[styleName];
          if (element) {
            visitedStyles.push(styleName);
          }
        }
        if (element) {
          style = parseStyle(element, visitedStyles);
        }
      }
      return style;
    }
    this.getStyle = getStyle;
    this.addCommonStyle = function(style) {
      var name;
      if (style.hasAttributeNS(stylens, "name")) {
        name = style.getAttributeNS(stylens, "name");
        if (!commonStyles.hasOwnProperty(name)) {
          commonStyles[name] = style;
        }
      }
    };
    this.addAutomaticStyle = function(style) {
      var name;
      if (style.hasAttributeNS(stylens, "name")) {
        name = style.getAttributeNS(stylens, "name");
        if (!automaticStyles.hasOwnProperty(name)) {
          automaticStyles[name] = style;
        }
      }
    };
    this.setDefaultStyle = function(style) {
      if (defaultStyle === undefined) {
        defaultStyle = parseStyle(style, []);
      }
    };
    this.getDefaultStyle = function() {
      return defaultStyle;
    };
  };
  odf.ComputedGraphicStyle = function() {
    this.text = new odf.ComputedTextProperties;
    this.paragraph = new odf.ComputedParagraphProperties;
    this.graphic = new odf.ComputedGraphicProperties;
  };
  odf.ComputedParagraphStyle = function() {
    this.text = new odf.ComputedTextProperties;
    this.paragraph = new odf.ComputedParagraphProperties;
  };
  odf.ComputedTextStyle = function() {
    this.text = new odf.ComputedTextProperties;
  };
  odf.StyleCache = function(odfroot) {
    var self = this, stylePiles, textStyleCache, paragraphStyleCache, graphicStyleCache, textStylePile, paragraphStylePile, graphicStylePile, textns = odf.Namespaces.textns, stylens = odf.Namespaces.stylens, styleInfo = new odf.StyleInfo, styleParseUtils = new odf.StyleParseUtils, masterPages, parsedMasterPages, defaultMasterPage, defaultPageLayout, pageLayouts, parsedPageLayouts;
    function appendClassNames(family, ns, element, chain) {
      var names = element.getAttributeNS(ns, "class-names"), stylename, i;
      if (names) {
        names = names.split(" ");
        for (i = 0;i < names.length;i += 1) {
          stylename = names[i];
          if (stylename) {
            chain.push(family);
            chain.push(stylename);
          }
        }
      }
    }
    function getGraphicStyleChain(element, chain) {
      var stylename = styleInfo.getStyleName("graphic", element);
      if (stylename !== undefined) {
        chain.push("graphic");
        chain.push(stylename);
      }
      return chain;
    }
    function getParagraphStyleChain(element, chain) {
      var stylename = styleInfo.getStyleName("paragraph", element);
      if (stylename !== undefined) {
        chain.push("paragraph");
        chain.push(stylename);
      }
      if (element.namespaceURI === textns && (element.localName === "h" || element.localName === "p")) {
        appendClassNames("paragraph", textns, element, chain);
      }
      return chain;
    }
    function createPropertiesChain(styleChain, propertiesName, defaultFamily) {
      var chain = [], i, lastProperties, family, styleName, pile, style, properties;
      for (i = 0;i < styleChain.length;i += 2) {
        family = styleChain[i];
        styleName = styleChain[i + 1];
        pile = stylePiles[family];
        style = pile.getStyle(styleName);
        if (style !== undefined) {
          properties = style[propertiesName];
          if (properties !== undefined && properties !== lastProperties) {
            chain.push(properties);
            lastProperties = properties;
          }
        }
      }
      pile = stylePiles[defaultFamily];
      style = pile.getDefaultStyle();
      if (style) {
        properties = style[propertiesName];
        if (properties !== undefined && properties !== lastProperties) {
          chain.push(properties);
        }
      }
      return chain;
    }
    this.getComputedGraphicStyle = function(element) {
      var styleChain = getGraphicStyleChain(element, []), key = styleChain.join("/"), computedStyle = graphicStyleCache[key];
      runtime.assert(styleChain.length % 2 === 0, "Invalid style chain.");
      if (computedStyle === undefined) {
        computedStyle = new odf.ComputedGraphicStyle;
        computedStyle.graphic.setGraphicProperties(createPropertiesChain(styleChain, "graphic", "graphic")[0]);
        computedStyle.text.setStyleChain(createPropertiesChain(styleChain, "text", "graphic"));
        computedStyle.paragraph.setStyleChain(createPropertiesChain(styleChain, "paragraph", "graphic"));
        graphicStyleCache[key] = computedStyle;
      }
      return computedStyle;
    };
    this.getComputedParagraphStyle = function(element) {
      var styleChain = getParagraphStyleChain(element, []), key = styleChain.join("/"), computedStyle = paragraphStyleCache[key];
      runtime.assert(styleChain.length % 2 === 0, "Invalid style chain.");
      if (computedStyle === undefined) {
        computedStyle = new odf.ComputedParagraphStyle;
        computedStyle.text.setStyleChain(createPropertiesChain(styleChain, "text", "paragraph"));
        computedStyle.paragraph.setStyleChain(createPropertiesChain(styleChain, "paragraph", "paragraph"));
        paragraphStyleCache[key] = computedStyle;
      }
      return computedStyle;
    };
    function getTextStyleChain(element, chain) {
      var stylename = styleInfo.getStyleName("text", element), parent = element.parentNode;
      if (stylename !== undefined) {
        chain.push("text");
        chain.push(stylename);
      }
      if (element.localName === "span" && element.namespaceURI === textns) {
        appendClassNames("text", textns, element, chain);
      }
      if (!parent || parent === odfroot) {
        return chain;
      }
      if (parent.namespaceURI === textns && (parent.localName === "p" || parent.localName === "h")) {
        getParagraphStyleChain(parent, chain);
      } else {
        getTextStyleChain(parent, chain);
      }
      return chain;
    }
    this.getComputedTextStyle = function(element) {
      var styleChain = getTextStyleChain(element, []), key = styleChain.join("/"), computedStyle = textStyleCache[key];
      runtime.assert(styleChain.length % 2 === 0, "Invalid style chain.");
      if (computedStyle === undefined) {
        computedStyle = new odf.ComputedTextStyle;
        computedStyle.text.setStyleChain(createPropertiesChain(styleChain, "text", "text"));
        textStyleCache[key] = computedStyle;
      }
      return computedStyle;
    };
    function getPileFromElement(element) {
      var family = element.getAttributeNS(stylens, "family");
      return stylePiles[family];
    }
    function addMasterPage(element) {
      var name = element.getAttributeNS(stylens, "name");
      if (name.length > 0 && !masterPages.hasOwnProperty(name)) {
        masterPages[name] = element;
      }
    }
    function getPageLayout(name) {
      var pageLayout = parsedPageLayouts[name], e;
      if (!pageLayout) {
        e = pageLayouts[name];
        if (e) {
          pageLayout = new odf.PageLayout(e, styleParseUtils, defaultPageLayout);
          parsedPageLayouts[name] = pageLayout;
        } else {
          pageLayout = defaultPageLayout;
        }
      }
      return pageLayout;
    }
    this.getPageLayout = getPageLayout;
    this.getDefaultPageLayout = function() {
      return defaultPageLayout;
    };
    function getMasterPage(name) {
      var masterPage = parsedMasterPages[name], element;
      if (masterPage === undefined) {
        element = masterPages[name];
        if (element) {
          masterPage = new odf.MasterPage(element, self);
          parsedMasterPages[name] = masterPage;
        } else {
          masterPage = null;
        }
      }
      return masterPage;
    }
    this.getMasterPage = getMasterPage;
    this.getDefaultMasterPage = function() {
      return defaultMasterPage;
    };
    function update() {
      var e, pile, defaultPageLayoutElement = null, defaultMasterPageElement = null;
      textStyleCache = {};
      paragraphStyleCache = {};
      graphicStyleCache = {};
      masterPages = {};
      parsedMasterPages = {};
      parsedPageLayouts = {};
      pageLayouts = {};
      textStylePile = new odf.StylePile(styleParseUtils, self);
      paragraphStylePile = new odf.StylePile(styleParseUtils, self);
      graphicStylePile = new odf.StylePile(styleParseUtils, self);
      stylePiles = {text:textStylePile, paragraph:paragraphStylePile, graphic:graphicStylePile};
      e = odfroot.styles.firstElementChild;
      while (e) {
        if (e.namespaceURI === stylens) {
          pile = getPileFromElement(e);
          if (pile) {
            if (e.localName === "style") {
              pile.addCommonStyle(e);
            } else {
              if (e.localName === "default-style") {
                pile.setDefaultStyle(e);
              }
            }
          } else {
            if (e.localName === "default-page-layout") {
              defaultPageLayoutElement = e;
            }
          }
        }
        e = e.nextElementSibling;
      }
      defaultPageLayout = new odf.PageLayout(defaultPageLayoutElement, styleParseUtils);
      e = odfroot.automaticStyles.firstElementChild;
      while (e) {
        if (e.namespaceURI === stylens) {
          pile = getPileFromElement(e);
          if (pile && e.localName === "style") {
            pile.addAutomaticStyle(e);
          } else {
            if (e.localName === "page-layout") {
              pageLayouts[e.getAttributeNS(stylens, "name")] = e;
            }
          }
        }
        e = e.nextElementSibling;
      }
      e = odfroot.masterStyles.firstElementChild;
      while (e) {
        if (e.namespaceURI === stylens && e.localName === "master-page") {
          defaultMasterPageElement = defaultMasterPageElement || e;
          addMasterPage(e);
        }
        e = e.nextElementSibling;
      }
      defaultMasterPage = new odf.MasterPage(defaultMasterPageElement, self);
    }
    this.update = update;
  };
  ops.OperationTransformMatrix = function OperationTransformMatrix() {
    function invertMoveCursorSpecRange(moveCursorSpec) {
      moveCursorSpec.position = moveCursorSpec.position + moveCursorSpec.length;
      moveCursorSpec.length *= -1;
    }
    function invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec) {
      var isBackwards = moveCursorSpec.length < 0;
      if (isBackwards) {
        invertMoveCursorSpecRange(moveCursorSpec);
      }
      return isBackwards;
    }
    function getStyleReferencingAttributes(setProperties, styleName) {
      var attributes = [];
      function check(attributeName) {
        if (setProperties[attributeName] === styleName) {
          attributes.push(attributeName);
        }
      }
      if (setProperties) {
        ["style:parent-style-name", "style:next-style-name"].forEach(check);
      }
      return attributes;
    }
    function dropStyleReferencingAttributes(setProperties, deletedStyleName) {
      function del(attributeName) {
        if (setProperties[attributeName] === deletedStyleName) {
          delete setProperties[attributeName];
        }
      }
      if (setProperties) {
        ["style:parent-style-name", "style:next-style-name"].forEach(del);
      }
    }
    function cloneOpspec(opspec) {
      var result = {};
      Object.keys(opspec).forEach(function(key) {
        if (typeof opspec[key] === "object") {
          result[key] = cloneOpspec(opspec[key]);
        } else {
          result[key] = opspec[key];
        }
      });
      return result;
    }
    function dropOverruledAndUnneededAttributes(minorSetProperties, minorRemovedProperties, majorSetProperties, majorRemovedProperties) {
      var i, name, majorChanged = false, minorChanged = false, removedPropertyNames, majorRemovedPropertyNames = [];
      if (majorRemovedProperties && majorRemovedProperties.attributes) {
        majorRemovedPropertyNames = majorRemovedProperties.attributes.split(",");
      }
      if (minorSetProperties && (majorSetProperties || majorRemovedPropertyNames.length > 0)) {
        Object.keys(minorSetProperties).forEach(function(key) {
          var value = minorSetProperties[key], overrulingPropertyValue;
          if (typeof value !== "object") {
            if (majorSetProperties) {
              overrulingPropertyValue = majorSetProperties[key];
            }
            if (overrulingPropertyValue !== undefined) {
              delete minorSetProperties[key];
              minorChanged = true;
              if (overrulingPropertyValue === value) {
                delete majorSetProperties[key];
                majorChanged = true;
              }
            } else {
              if (majorRemovedPropertyNames.indexOf(key) !== -1) {
                delete minorSetProperties[key];
                minorChanged = true;
              }
            }
          }
        });
      }
      if (minorRemovedProperties && minorRemovedProperties.attributes && (majorSetProperties || majorRemovedPropertyNames.length > 0)) {
        removedPropertyNames = minorRemovedProperties.attributes.split(",");
        for (i = 0;i < removedPropertyNames.length;i += 1) {
          name = removedPropertyNames[i];
          if (majorSetProperties && majorSetProperties[name] !== undefined || majorRemovedPropertyNames && majorRemovedPropertyNames.indexOf(name) !== -1) {
            removedPropertyNames.splice(i, 1);
            i -= 1;
            minorChanged = true;
          }
        }
        if (removedPropertyNames.length > 0) {
          minorRemovedProperties.attributes = removedPropertyNames.join(",");
        } else {
          delete minorRemovedProperties.attributes;
        }
      }
      return {majorChanged:majorChanged, minorChanged:minorChanged};
    }
    function hasProperties(properties) {
      var key;
      for (key in properties) {
        if (properties.hasOwnProperty(key)) {
          return true;
        }
      }
      return false;
    }
    function hasRemovedProperties(properties) {
      var key;
      for (key in properties) {
        if (properties.hasOwnProperty(key)) {
          if (key !== "attributes" || properties.attributes.length > 0) {
            return true;
          }
        }
      }
      return false;
    }
    function dropOverruledAndUnneededProperties(minorSet, minorRem, majorSet, majorRem, propertiesName) {
      var minorSP = minorSet ? minorSet[propertiesName] : null, minorRP = minorRem ? minorRem[propertiesName] : null, majorSP = majorSet ? majorSet[propertiesName] : null, majorRP = majorRem ? majorRem[propertiesName] : null, result;
      result = dropOverruledAndUnneededAttributes(minorSP, minorRP, majorSP, majorRP);
      if (minorSP && !hasProperties(minorSP)) {
        delete minorSet[propertiesName];
      }
      if (minorRP && !hasRemovedProperties(minorRP)) {
        delete minorRem[propertiesName];
      }
      if (majorSP && !hasProperties(majorSP)) {
        delete majorSet[propertiesName];
      }
      if (majorRP && !hasRemovedProperties(majorRP)) {
        delete majorRem[propertiesName];
      }
      return result;
    }
    function transformAddAnnotationAddAnnotation(addAnnotationSpecA, addAnnotationSpecB, hasAPriority) {
      var firstAnnotationSpec, secondAnnotationSpec;
      if (addAnnotationSpecA.position < addAnnotationSpecB.position) {
        firstAnnotationSpec = addAnnotationSpecA;
        secondAnnotationSpec = addAnnotationSpecB;
      } else {
        if (addAnnotationSpecB.position < addAnnotationSpecA.position) {
          firstAnnotationSpec = addAnnotationSpecB;
          secondAnnotationSpec = addAnnotationSpecA;
        } else {
          firstAnnotationSpec = hasAPriority ? addAnnotationSpecA : addAnnotationSpecB;
          secondAnnotationSpec = hasAPriority ? addAnnotationSpecB : addAnnotationSpecA;
        }
      }
      if (secondAnnotationSpec.position < firstAnnotationSpec.position + firstAnnotationSpec.length) {
        firstAnnotationSpec.length += 2;
      }
      secondAnnotationSpec.position += 2;
      return {opSpecsA:[addAnnotationSpecA], opSpecsB:[addAnnotationSpecB]};
    }
    function transformAddAnnotationApplyDirectStyling(addAnnotationSpec, applyDirectStylingSpec) {
      if (addAnnotationSpec.position <= applyDirectStylingSpec.position) {
        applyDirectStylingSpec.position += 2;
      } else {
        if (addAnnotationSpec.position <= applyDirectStylingSpec.position + applyDirectStylingSpec.length) {
          applyDirectStylingSpec.length += 2;
        }
      }
      return {opSpecsA:[addAnnotationSpec], opSpecsB:[applyDirectStylingSpec]};
    }
    function transformAddAnnotationInsertText(addAnnotationSpec, insertTextSpec) {
      if (insertTextSpec.position <= addAnnotationSpec.position) {
        addAnnotationSpec.position += insertTextSpec.text.length;
      } else {
        if (addAnnotationSpec.length !== undefined) {
          if (insertTextSpec.position <= addAnnotationSpec.position + addAnnotationSpec.length) {
            addAnnotationSpec.length += insertTextSpec.text.length;
          }
        }
        insertTextSpec.position += 2;
      }
      return {opSpecsA:[addAnnotationSpec], opSpecsB:[insertTextSpec]};
    }
    function transformAddAnnotationMergeParagraph(addAnnotationSpec, mergeParagraphSpec) {
      if (mergeParagraphSpec.sourceStartPosition <= addAnnotationSpec.position) {
        addAnnotationSpec.position -= 1;
      } else {
        if (addAnnotationSpec.length !== undefined) {
          if (mergeParagraphSpec.sourceStartPosition <= addAnnotationSpec.position + addAnnotationSpec.length) {
            addAnnotationSpec.length -= 1;
          }
        }
        mergeParagraphSpec.sourceStartPosition += 2;
        if (addAnnotationSpec.position < mergeParagraphSpec.destinationStartPosition) {
          mergeParagraphSpec.destinationStartPosition += 2;
        }
      }
      return {opSpecsA:[addAnnotationSpec], opSpecsB:[mergeParagraphSpec]};
    }
    function transformAddAnnotationMoveCursor(addAnnotationSpec, moveCursorSpec) {
      var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec);
      if (addAnnotationSpec.position < moveCursorSpec.position) {
        moveCursorSpec.position += 2;
      } else {
        if (addAnnotationSpec.position < moveCursorSpec.position + moveCursorSpec.length) {
          moveCursorSpec.length += 2;
        }
      }
      if (isMoveCursorSpecRangeInverted) {
        invertMoveCursorSpecRange(moveCursorSpec);
      }
      return {opSpecsA:[addAnnotationSpec], opSpecsB:[moveCursorSpec]};
    }
    function transformAddAnnotationRemoveAnnotation(addAnnotationSpec, removeAnnotationSpec) {
      if (addAnnotationSpec.position < removeAnnotationSpec.position) {
        if (removeAnnotationSpec.position < addAnnotationSpec.position + addAnnotationSpec.length) {
          addAnnotationSpec.length -= removeAnnotationSpec.length + 2;
        }
        removeAnnotationSpec.position += 2;
      } else {
        addAnnotationSpec.position -= removeAnnotationSpec.length + 2;
      }
      return {opSpecsA:[addAnnotationSpec], opSpecsB:[removeAnnotationSpec]};
    }
    function transformAddAnnotationRemoveText(addAnnotationSpec, removeTextSpec) {
      var removeTextSpecPosition = removeTextSpec.position, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, annotationSpecEnd, helperOpspec, addAnnotationSpecResult = [addAnnotationSpec], removeTextSpecResult = [removeTextSpec];
      if (addAnnotationSpec.position <= removeTextSpec.position) {
        removeTextSpec.position += 2;
      } else {
        if (addAnnotationSpec.position < removeTextSpecEnd) {
          removeTextSpec.length = addAnnotationSpec.position - removeTextSpec.position;
          helperOpspec = {optype:"RemoveText", memberid:removeTextSpec.memberid, timestamp:removeTextSpec.timestamp, position:addAnnotationSpec.position + 2, length:removeTextSpecEnd - addAnnotationSpec.position};
          removeTextSpecResult.unshift(helperOpspec);
        }
      }
      if (removeTextSpec.position + removeTextSpec.length <= addAnnotationSpec.position) {
        addAnnotationSpec.position -= removeTextSpec.length;
        if (addAnnotationSpec.length !== undefined && helperOpspec) {
          if (helperOpspec.length >= addAnnotationSpec.length) {
            addAnnotationSpec.length = 0;
          } else {
            addAnnotationSpec.length -= helperOpspec.length;
          }
        }
      } else {
        if (addAnnotationSpec.length !== undefined) {
          annotationSpecEnd = addAnnotationSpec.position + addAnnotationSpec.length;
          if (removeTextSpecEnd <= annotationSpecEnd) {
            addAnnotationSpec.length -= removeTextSpec.length;
          } else {
            if (removeTextSpecPosition < annotationSpecEnd) {
              addAnnotationSpec.length = removeTextSpecPosition - addAnnotationSpec.position;
            }
          }
        }
      }
      return {opSpecsA:addAnnotationSpecResult, opSpecsB:removeTextSpecResult};
    }
    function transformAddAnnotationSetParagraphStyle(addAnnotationSpec, setParagraphStyleSpec) {
      if (addAnnotationSpec.position < setParagraphStyleSpec.position) {
        setParagraphStyleSpec.position += 2;
      }
      return {opSpecsA:[addAnnotationSpec], opSpecsB:[setParagraphStyleSpec]};
    }
    function transformAddAnnotationSplitParagraph(addAnnotationSpec, splitParagraphSpec) {
      if (addAnnotationSpec.position < splitParagraphSpec.sourceParagraphPosition) {
        splitParagraphSpec.sourceParagraphPosition += 2;
      }
      if (splitParagraphSpec.position <= addAnnotationSpec.position) {
        addAnnotationSpec.position += 1;
      } else {
        if (addAnnotationSpec.length !== undefined) {
          if (splitParagraphSpec.position <= addAnnotationSpec.position + addAnnotationSpec.length) {
            addAnnotationSpec.length += 1;
          }
        }
        splitParagraphSpec.position += 2;
      }
      return {opSpecsA:[addAnnotationSpec], opSpecsB:[splitParagraphSpec]};
    }
    function transformAddStyleRemoveStyle(addStyleSpec, removeStyleSpec) {
      var setAttributes, helperOpspec, addStyleSpecResult = [addStyleSpec], removeStyleSpecResult = [removeStyleSpec];
      if (addStyleSpec.styleFamily === removeStyleSpec.styleFamily) {
        setAttributes = getStyleReferencingAttributes(addStyleSpec.setProperties, removeStyleSpec.styleName);
        if (setAttributes.length > 0) {
          helperOpspec = {optype:"UpdateParagraphStyle", memberid:removeStyleSpec.memberid, timestamp:removeStyleSpec.timestamp, styleName:addStyleSpec.styleName, removedProperties:{attributes:setAttributes.join(",")}};
          removeStyleSpecResult.unshift(helperOpspec);
        }
        dropStyleReferencingAttributes(addStyleSpec.setProperties, removeStyleSpec.styleName);
      }
      return {opSpecsA:addStyleSpecResult, opSpecsB:removeStyleSpecResult};
    }
    function transformApplyDirectStylingApplyDirectStyling(applyDirectStylingSpecA, applyDirectStylingSpecB, hasAPriority) {
      var majorSpec, minorSpec, majorSpecResult, minorSpecResult, majorSpecEnd, minorSpecEnd, dropResult, originalMajorSpec, originalMinorSpec, helperOpspecBefore, helperOpspecAfter, applyDirectStylingSpecAResult = [applyDirectStylingSpecA], applyDirectStylingSpecBResult = [applyDirectStylingSpecB];
      if (!(applyDirectStylingSpecA.position + applyDirectStylingSpecA.length <= applyDirectStylingSpecB.position || applyDirectStylingSpecA.position >= applyDirectStylingSpecB.position + applyDirectStylingSpecB.length)) {
        majorSpec = hasAPriority ? applyDirectStylingSpecA : applyDirectStylingSpecB;
        minorSpec = hasAPriority ? applyDirectStylingSpecB : applyDirectStylingSpecA;
        if (applyDirectStylingSpecA.position !== applyDirectStylingSpecB.position || applyDirectStylingSpecA.length !== applyDirectStylingSpecB.length) {
          originalMajorSpec = cloneOpspec(majorSpec);
          originalMinorSpec = cloneOpspec(minorSpec);
        }
        dropResult = dropOverruledAndUnneededProperties(minorSpec.setProperties, null, majorSpec.setProperties, null, "style:text-properties");
        if (dropResult.majorChanged || dropResult.minorChanged) {
          majorSpecResult = [];
          minorSpecResult = [];
          majorSpecEnd = majorSpec.position + majorSpec.length;
          minorSpecEnd = minorSpec.position + minorSpec.length;
          if (minorSpec.position < majorSpec.position) {
            if (dropResult.minorChanged) {
              helperOpspecBefore = cloneOpspec(originalMinorSpec);
              helperOpspecBefore.length = majorSpec.position - minorSpec.position;
              minorSpecResult.push(helperOpspecBefore);
              minorSpec.position = majorSpec.position;
              minorSpec.length = minorSpecEnd - minorSpec.position;
            }
          } else {
            if (majorSpec.position < minorSpec.position) {
              if (dropResult.majorChanged) {
                helperOpspecBefore = cloneOpspec(originalMajorSpec);
                helperOpspecBefore.length = minorSpec.position - majorSpec.position;
                majorSpecResult.push(helperOpspecBefore);
                majorSpec.position = minorSpec.position;
                majorSpec.length = majorSpecEnd - majorSpec.position;
              }
            }
          }
          if (minorSpecEnd > majorSpecEnd) {
            if (dropResult.minorChanged) {
              helperOpspecAfter = originalMinorSpec;
              helperOpspecAfter.position = majorSpecEnd;
              helperOpspecAfter.length = minorSpecEnd - majorSpecEnd;
              minorSpecResult.push(helperOpspecAfter);
              minorSpec.length = majorSpecEnd - minorSpec.position;
            }
          } else {
            if (majorSpecEnd > minorSpecEnd) {
              if (dropResult.majorChanged) {
                helperOpspecAfter = originalMajorSpec;
                helperOpspecAfter.position = minorSpecEnd;
                helperOpspecAfter.length = majorSpecEnd - minorSpecEnd;
                majorSpecResult.push(helperOpspecAfter);
                majorSpec.length = minorSpecEnd - majorSpec.position;
              }
            }
          }
          if (majorSpec.setProperties && hasProperties(majorSpec.setProperties)) {
            majorSpecResult.push(majorSpec);
          }
          if (minorSpec.setProperties && hasProperties(minorSpec.setProperties)) {
            minorSpecResult.push(minorSpec);
          }
          if (hasAPriority) {
            applyDirectStylingSpecAResult = majorSpecResult;
            applyDirectStylingSpecBResult = minorSpecResult;
          } else {
            applyDirectStylingSpecAResult = minorSpecResult;
            applyDirectStylingSpecBResult = majorSpecResult;
          }
        }
      }
      return {opSpecsA:applyDirectStylingSpecAResult, opSpecsB:applyDirectStylingSpecBResult};
    }
    function transformApplyDirectStylingInsertText(applyDirectStylingSpec, insertTextSpec) {
      if (insertTextSpec.position <= applyDirectStylingSpec.position) {
        applyDirectStylingSpec.position += insertTextSpec.text.length;
      } else {
        if (insertTextSpec.position <= applyDirectStylingSpec.position + applyDirectStylingSpec.length) {
          applyDirectStylingSpec.length += insertTextSpec.text.length;
        }
      }
      return {opSpecsA:[applyDirectStylingSpec], opSpecsB:[insertTextSpec]};
    }
    function transformApplyDirectStylingMergeParagraph(applyDirectStylingSpec, mergeParagraphSpec) {
      var pointA = applyDirectStylingSpec.position, pointB = applyDirectStylingSpec.position + applyDirectStylingSpec.length;
      if (pointA >= mergeParagraphSpec.sourceStartPosition) {
        pointA -= 1;
      }
      if (pointB >= mergeParagraphSpec.sourceStartPosition) {
        pointB -= 1;
      }
      applyDirectStylingSpec.position = pointA;
      applyDirectStylingSpec.length = pointB - pointA;
      return {opSpecsA:[applyDirectStylingSpec], opSpecsB:[mergeParagraphSpec]};
    }
    function transformApplyDirectStylingRemoveAnnotation(applyDirectStylingSpec, removeAnnotationSpec) {
      var pointA = applyDirectStylingSpec.position, pointB = applyDirectStylingSpec.position + applyDirectStylingSpec.length, removeAnnotationEnd = removeAnnotationSpec.position + removeAnnotationSpec.length, applyDirectStylingSpecResult = [applyDirectStylingSpec], removeAnnotationSpecResult = [removeAnnotationSpec];
      if (removeAnnotationSpec.position <= pointA && pointB <= removeAnnotationEnd) {
        applyDirectStylingSpecResult = [];
      } else {
        if (removeAnnotationEnd < pointA) {
          pointA -= removeAnnotationSpec.length + 2;
        }
        if (removeAnnotationEnd < pointB) {
          pointB -= removeAnnotationSpec.length + 2;
        }
        applyDirectStylingSpec.position = pointA;
        applyDirectStylingSpec.length = pointB - pointA;
      }
      return {opSpecsA:applyDirectStylingSpecResult, opSpecsB:removeAnnotationSpecResult};
    }
    function transformApplyDirectStylingRemoveText(applyDirectStylingSpec, removeTextSpec) {
      var applyDirectStylingSpecEnd = applyDirectStylingSpec.position + applyDirectStylingSpec.length, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, applyDirectStylingSpecResult = [applyDirectStylingSpec], removeTextSpecResult = [removeTextSpec];
      if (removeTextSpecEnd <= applyDirectStylingSpec.position) {
        applyDirectStylingSpec.position -= removeTextSpec.length;
      } else {
        if (removeTextSpec.position < applyDirectStylingSpecEnd) {
          if (applyDirectStylingSpec.position < removeTextSpec.position) {
            if (removeTextSpecEnd < applyDirectStylingSpecEnd) {
              applyDirectStylingSpec.length -= removeTextSpec.length;
            } else {
              applyDirectStylingSpec.length = removeTextSpec.position - applyDirectStylingSpec.position;
            }
          } else {
            applyDirectStylingSpec.position = removeTextSpec.position;
            if (removeTextSpecEnd < applyDirectStylingSpecEnd) {
              applyDirectStylingSpec.length = applyDirectStylingSpecEnd - removeTextSpecEnd;
            } else {
              applyDirectStylingSpecResult = [];
            }
          }
        }
      }
      return {opSpecsA:applyDirectStylingSpecResult, opSpecsB:removeTextSpecResult};
    }
    function transformApplyDirectStylingSplitParagraph(applyDirectStylingSpec, splitParagraphSpec) {
      if (splitParagraphSpec.position < applyDirectStylingSpec.position) {
        applyDirectStylingSpec.position += 1;
      } else {
        if (splitParagraphSpec.position < applyDirectStylingSpec.position + applyDirectStylingSpec.length) {
          applyDirectStylingSpec.length += 1;
        }
      }
      return {opSpecsA:[applyDirectStylingSpec], opSpecsB:[splitParagraphSpec]};
    }
    function transformInsertTextInsertText(insertTextSpecA, insertTextSpecB, hasAPriority) {
      if (insertTextSpecA.position < insertTextSpecB.position) {
        insertTextSpecB.position += insertTextSpecA.text.length;
      } else {
        if (insertTextSpecA.position > insertTextSpecB.position) {
          insertTextSpecA.position += insertTextSpecB.text.length;
        } else {
          if (hasAPriority) {
            insertTextSpecB.position += insertTextSpecA.text.length;
          } else {
            insertTextSpecA.position += insertTextSpecB.text.length;
          }
        }
      }
      return {opSpecsA:[insertTextSpecA], opSpecsB:[insertTextSpecB]};
    }
    function transformInsertTextMergeParagraph(insertTextSpec, mergeParagraphSpec) {
      if (insertTextSpec.position >= mergeParagraphSpec.sourceStartPosition) {
        insertTextSpec.position -= 1;
      } else {
        if (insertTextSpec.position < mergeParagraphSpec.sourceStartPosition) {
          mergeParagraphSpec.sourceStartPosition += insertTextSpec.text.length;
        }
        if (insertTextSpec.position < mergeParagraphSpec.destinationStartPosition) {
          mergeParagraphSpec.destinationStartPosition += insertTextSpec.text.length;
        }
      }
      return {opSpecsA:[insertTextSpec], opSpecsB:[mergeParagraphSpec]};
    }
    function transformInsertTextMoveCursor(insertTextSpec, moveCursorSpec) {
      var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec);
      if (insertTextSpec.position < moveCursorSpec.position) {
        moveCursorSpec.position += insertTextSpec.text.length;
      } else {
        if (insertTextSpec.position < moveCursorSpec.position + moveCursorSpec.length) {
          moveCursorSpec.length += insertTextSpec.text.length;
        }
      }
      if (isMoveCursorSpecRangeInverted) {
        invertMoveCursorSpecRange(moveCursorSpec);
      }
      return {opSpecsA:[insertTextSpec], opSpecsB:[moveCursorSpec]};
    }
    function transformInsertTextRemoveAnnotation(insertTextSpec, removeAnnotationSpec) {
      var insertTextSpecPosition = insertTextSpec.position, removeAnnotationEnd = removeAnnotationSpec.position + removeAnnotationSpec.length, insertTextSpecResult = [insertTextSpec], removeAnnotationSpecResult = [removeAnnotationSpec];
      if (removeAnnotationSpec.position <= insertTextSpecPosition && insertTextSpecPosition <= removeAnnotationEnd) {
        insertTextSpecResult = [];
        removeAnnotationSpec.length += insertTextSpec.text.length;
      } else {
        if (removeAnnotationEnd < insertTextSpec.position) {
          insertTextSpec.position -= removeAnnotationSpec.length + 2;
        } else {
          removeAnnotationSpec.position += insertTextSpec.text.length;
        }
      }
      return {opSpecsA:insertTextSpecResult, opSpecsB:removeAnnotationSpecResult};
    }
    function transformInsertTextRemoveText(insertTextSpec, removeTextSpec) {
      var helperOpspec, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, insertTextSpecResult = [insertTextSpec], removeTextSpecResult = [removeTextSpec];
      if (removeTextSpecEnd <= insertTextSpec.position) {
        insertTextSpec.position -= removeTextSpec.length;
      } else {
        if (insertTextSpec.position <= removeTextSpec.position) {
          removeTextSpec.position += insertTextSpec.text.length;
        } else {
          removeTextSpec.length = insertTextSpec.position - removeTextSpec.position;
          helperOpspec = {optype:"RemoveText", memberid:removeTextSpec.memberid, timestamp:removeTextSpec.timestamp, position:insertTextSpec.position + insertTextSpec.text.length, length:removeTextSpecEnd - insertTextSpec.position};
          removeTextSpecResult.unshift(helperOpspec);
          insertTextSpec.position = removeTextSpec.position;
        }
      }
      return {opSpecsA:insertTextSpecResult, opSpecsB:removeTextSpecResult};
    }
    function transformInsertTextSetParagraphStyle(insertTextSpec, setParagraphStyleSpec) {
      if (setParagraphStyleSpec.position > insertTextSpec.position) {
        setParagraphStyleSpec.position += insertTextSpec.text.length;
      }
      return {opSpecsA:[insertTextSpec], opSpecsB:[setParagraphStyleSpec]};
    }
    function transformInsertTextSplitParagraph(insertTextSpec, splitParagraphSpec) {
      if (insertTextSpec.position < splitParagraphSpec.sourceParagraphPosition) {
        splitParagraphSpec.sourceParagraphPosition += insertTextSpec.text.length;
      }
      if (insertTextSpec.position <= splitParagraphSpec.position) {
        splitParagraphSpec.position += insertTextSpec.text.length;
      } else {
        insertTextSpec.position += 1;
      }
      return {opSpecsA:[insertTextSpec], opSpecsB:[splitParagraphSpec]};
    }
    function transformMergeParagraphMergeParagraph(mergeParagraphSpecA, mergeParagraphSpecB, hasAPriority) {
      var specsForB = [mergeParagraphSpecA], specsForA = [mergeParagraphSpecB], priorityOp, styleParagraphFixup, moveCursorA, moveCursorB;
      if (mergeParagraphSpecA.destinationStartPosition === mergeParagraphSpecB.destinationStartPosition) {
        specsForB = [];
        specsForA = [];
        if (mergeParagraphSpecA.moveCursor) {
          moveCursorA = {optype:"MoveCursor", memberid:mergeParagraphSpecA.memberid, timestamp:mergeParagraphSpecA.timestamp, position:mergeParagraphSpecA.sourceStartPosition - 1};
          specsForB.push(moveCursorA);
        }
        if (mergeParagraphSpecB.moveCursor) {
          moveCursorB = {optype:"MoveCursor", memberid:mergeParagraphSpecB.memberid, timestamp:mergeParagraphSpecB.timestamp, position:mergeParagraphSpecB.sourceStartPosition - 1};
          specsForA.push(moveCursorB);
        }
        priorityOp = hasAPriority ? mergeParagraphSpecA : mergeParagraphSpecB;
        styleParagraphFixup = {optype:"SetParagraphStyle", memberid:priorityOp.memberid, timestamp:priorityOp.timestamp, position:priorityOp.destinationStartPosition, styleName:priorityOp.paragraphStyleName};
        if (hasAPriority) {
          specsForB.push(styleParagraphFixup);
        } else {
          specsForA.push(styleParagraphFixup);
        }
      } else {
        if (mergeParagraphSpecB.sourceStartPosition === mergeParagraphSpecA.destinationStartPosition) {
          mergeParagraphSpecA.destinationStartPosition = mergeParagraphSpecB.destinationStartPosition;
          mergeParagraphSpecA.sourceStartPosition -= 1;
          mergeParagraphSpecA.paragraphStyleName = mergeParagraphSpecB.paragraphStyleName;
        } else {
          if (mergeParagraphSpecA.sourceStartPosition === mergeParagraphSpecB.destinationStartPosition) {
            mergeParagraphSpecB.destinationStartPosition = mergeParagraphSpecA.destinationStartPosition;
            mergeParagraphSpecB.sourceStartPosition -= 1;
            mergeParagraphSpecB.paragraphStyleName = mergeParagraphSpecA.paragraphStyleName;
          } else {
            if (mergeParagraphSpecA.destinationStartPosition < mergeParagraphSpecB.destinationStartPosition) {
              mergeParagraphSpecB.destinationStartPosition -= 1;
              mergeParagraphSpecB.sourceStartPosition -= 1;
            } else {
              mergeParagraphSpecA.destinationStartPosition -= 1;
              mergeParagraphSpecA.sourceStartPosition -= 1;
            }
          }
        }
      }
      return {opSpecsA:specsForB, opSpecsB:specsForA};
    }
    function transformMergeParagraphMoveCursor(mergeParagraphSpec, moveCursorSpec) {
      var pointA = moveCursorSpec.position, pointB = moveCursorSpec.position + moveCursorSpec.length, start = Math.min(pointA, pointB), end = Math.max(pointA, pointB);
      if (start >= mergeParagraphSpec.sourceStartPosition) {
        start -= 1;
      }
      if (end >= mergeParagraphSpec.sourceStartPosition) {
        end -= 1;
      }
      if (moveCursorSpec.length >= 0) {
        moveCursorSpec.position = start;
        moveCursorSpec.length = end - start;
      } else {
        moveCursorSpec.position = end;
        moveCursorSpec.length = start - end;
      }
      return {opSpecsA:[mergeParagraphSpec], opSpecsB:[moveCursorSpec]};
    }
    function transformMergeParagraphRemoveAnnotation(mergeParagraphSpec, removeAnnotationSpec) {
      var removeAnnotationEnd = removeAnnotationSpec.position + removeAnnotationSpec.length, mergeParagraphSpecResult = [mergeParagraphSpec], removeAnnotationSpecResult = [removeAnnotationSpec];
      if (removeAnnotationSpec.position <= mergeParagraphSpec.destinationStartPosition && mergeParagraphSpec.sourceStartPosition <= removeAnnotationEnd) {
        mergeParagraphSpecResult = [];
        removeAnnotationSpec.length -= 1;
      } else {
        if (mergeParagraphSpec.sourceStartPosition < removeAnnotationSpec.position) {
          removeAnnotationSpec.position -= 1;
        } else {
          if (removeAnnotationEnd < mergeParagraphSpec.destinationStartPosition) {
            mergeParagraphSpec.destinationStartPosition -= removeAnnotationSpec.length + 2;
          }
          if (removeAnnotationEnd < mergeParagraphSpec.sourceStartPosition) {
            mergeParagraphSpec.sourceStartPosition -= removeAnnotationSpec.length + 2;
          }
        }
      }
      return {opSpecsA:mergeParagraphSpecResult, opSpecsB:removeAnnotationSpecResult};
    }
    function transformMergeParagraphRemoveText(mergeParagraphSpec, removeTextSpec) {
      if (removeTextSpec.position >= mergeParagraphSpec.sourceStartPosition) {
        removeTextSpec.position -= 1;
      } else {
        if (removeTextSpec.position < mergeParagraphSpec.destinationStartPosition) {
          mergeParagraphSpec.destinationStartPosition -= removeTextSpec.length;
        }
        if (removeTextSpec.position < mergeParagraphSpec.sourceStartPosition) {
          mergeParagraphSpec.sourceStartPosition -= removeTextSpec.length;
        }
      }
      return {opSpecsA:[mergeParagraphSpec], opSpecsB:[removeTextSpec]};
    }
    function transformMergeParagraphSetParagraphStyle(mergeParagraphSpec, setParagraphStyleSpec) {
      var opSpecsA = [mergeParagraphSpec], opSpecsB = [setParagraphStyleSpec];
      if (setParagraphStyleSpec.position > mergeParagraphSpec.sourceStartPosition) {
        setParagraphStyleSpec.position -= 1;
      } else {
        if (setParagraphStyleSpec.position === mergeParagraphSpec.destinationStartPosition || setParagraphStyleSpec.position === mergeParagraphSpec.sourceStartPosition) {
          setParagraphStyleSpec.position = mergeParagraphSpec.destinationStartPosition;
          mergeParagraphSpec.paragraphStyleName = setParagraphStyleSpec.styleName;
        }
      }
      return {opSpecsA:opSpecsA, opSpecsB:opSpecsB};
    }
    function transformMergeParagraphSplitParagraph(mergeParagraphSpec, splitParagraphSpec) {
      var styleSplitParagraph, moveCursorOp, opSpecsA = [mergeParagraphSpec], opSpecsB = [splitParagraphSpec];
      if (splitParagraphSpec.position < mergeParagraphSpec.destinationStartPosition) {
        mergeParagraphSpec.destinationStartPosition += 1;
        mergeParagraphSpec.sourceStartPosition += 1;
      } else {
        if (splitParagraphSpec.position >= mergeParagraphSpec.destinationStartPosition && splitParagraphSpec.position < mergeParagraphSpec.sourceStartPosition) {
          splitParagraphSpec.paragraphStyleName = mergeParagraphSpec.paragraphStyleName;
          styleSplitParagraph = {optype:"SetParagraphStyle", memberid:mergeParagraphSpec.memberid, timestamp:mergeParagraphSpec.timestamp, position:mergeParagraphSpec.destinationStartPosition, styleName:mergeParagraphSpec.paragraphStyleName};
          opSpecsA.push(styleSplitParagraph);
          if (splitParagraphSpec.position === mergeParagraphSpec.sourceStartPosition - 1 && mergeParagraphSpec.moveCursor) {
            moveCursorOp = {optype:"MoveCursor", memberid:mergeParagraphSpec.memberid, timestamp:mergeParagraphSpec.timestamp, position:splitParagraphSpec.position, length:0};
            opSpecsA.push(moveCursorOp);
          }
          mergeParagraphSpec.destinationStartPosition = splitParagraphSpec.position + 1;
          mergeParagraphSpec.sourceStartPosition += 1;
        } else {
          if (splitParagraphSpec.position >= mergeParagraphSpec.sourceStartPosition) {
            splitParagraphSpec.position -= 1;
            splitParagraphSpec.sourceParagraphPosition -= 1;
          }
        }
      }
      return {opSpecsA:opSpecsA, opSpecsB:opSpecsB};
    }
    function transformUpdateParagraphStyleUpdateParagraphStyle(updateParagraphStyleSpecA, updateParagraphStyleSpecB, hasAPriority) {
      var majorSpec, minorSpec, updateParagraphStyleSpecAResult = [updateParagraphStyleSpecA], updateParagraphStyleSpecBResult = [updateParagraphStyleSpecB];
      if (updateParagraphStyleSpecA.styleName === updateParagraphStyleSpecB.styleName) {
        majorSpec = hasAPriority ? updateParagraphStyleSpecA : updateParagraphStyleSpecB;
        minorSpec = hasAPriority ? updateParagraphStyleSpecB : updateParagraphStyleSpecA;
        dropOverruledAndUnneededProperties(minorSpec.setProperties, minorSpec.removedProperties, majorSpec.setProperties, majorSpec.removedProperties, "style:paragraph-properties");
        dropOverruledAndUnneededProperties(minorSpec.setProperties, minorSpec.removedProperties, majorSpec.setProperties, majorSpec.removedProperties, "style:text-properties");
        dropOverruledAndUnneededAttributes(minorSpec.setProperties || null, minorSpec.removedProperties || null, majorSpec.setProperties || null, majorSpec.removedProperties || null);
        if (!(majorSpec.setProperties && hasProperties(majorSpec.setProperties)) && !(majorSpec.removedProperties && hasRemovedProperties(majorSpec.removedProperties))) {
          if (hasAPriority) {
            updateParagraphStyleSpecAResult = [];
          } else {
            updateParagraphStyleSpecBResult = [];
          }
        }
        if (!(minorSpec.setProperties && hasProperties(minorSpec.setProperties)) && !(minorSpec.removedProperties && hasRemovedProperties(minorSpec.removedProperties))) {
          if (hasAPriority) {
            updateParagraphStyleSpecBResult = [];
          } else {
            updateParagraphStyleSpecAResult = [];
          }
        }
      }
      return {opSpecsA:updateParagraphStyleSpecAResult, opSpecsB:updateParagraphStyleSpecBResult};
    }
    function transformUpdateMetadataUpdateMetadata(updateMetadataSpecA, updateMetadataSpecB, hasAPriority) {
      var majorSpec, minorSpec, updateMetadataSpecAResult = [updateMetadataSpecA], updateMetadataSpecBResult = [updateMetadataSpecB];
      majorSpec = hasAPriority ? updateMetadataSpecA : updateMetadataSpecB;
      minorSpec = hasAPriority ? updateMetadataSpecB : updateMetadataSpecA;
      dropOverruledAndUnneededAttributes(minorSpec.setProperties || null, minorSpec.removedProperties || null, majorSpec.setProperties || null, majorSpec.removedProperties || null);
      if (!(majorSpec.setProperties && hasProperties(majorSpec.setProperties)) && !(majorSpec.removedProperties && hasRemovedProperties(majorSpec.removedProperties))) {
        if (hasAPriority) {
          updateMetadataSpecAResult = [];
        } else {
          updateMetadataSpecBResult = [];
        }
      }
      if (!(minorSpec.setProperties && hasProperties(minorSpec.setProperties)) && !(minorSpec.removedProperties && hasRemovedProperties(minorSpec.removedProperties))) {
        if (hasAPriority) {
          updateMetadataSpecBResult = [];
        } else {
          updateMetadataSpecAResult = [];
        }
      }
      return {opSpecsA:updateMetadataSpecAResult, opSpecsB:updateMetadataSpecBResult};
    }
    function transformSetParagraphStyleSetParagraphStyle(setParagraphStyleSpecA, setParagraphStyleSpecB, hasAPriority) {
      if (setParagraphStyleSpecA.position === setParagraphStyleSpecB.position) {
        if (hasAPriority) {
          setParagraphStyleSpecB.styleName = setParagraphStyleSpecA.styleName;
        } else {
          setParagraphStyleSpecA.styleName = setParagraphStyleSpecB.styleName;
        }
      }
      return {opSpecsA:[setParagraphStyleSpecA], opSpecsB:[setParagraphStyleSpecB]};
    }
    function transformSetParagraphStyleSplitParagraph(setParagraphStyleSpec, splitParagraphSpec) {
      var opSpecsA = [setParagraphStyleSpec], opSpecsB = [splitParagraphSpec], setParagraphClone;
      if (setParagraphStyleSpec.position > splitParagraphSpec.position) {
        setParagraphStyleSpec.position += 1;
      } else {
        if (setParagraphStyleSpec.position === splitParagraphSpec.sourceParagraphPosition) {
          splitParagraphSpec.paragraphStyleName = setParagraphStyleSpec.styleName;
          setParagraphClone = cloneOpspec(setParagraphStyleSpec);
          setParagraphClone.position = splitParagraphSpec.position + 1;
          opSpecsA.push(setParagraphClone);
        }
      }
      return {opSpecsA:opSpecsA, opSpecsB:opSpecsB};
    }
    function transformSplitParagraphSplitParagraph(splitParagraphSpecA, splitParagraphSpecB, hasAPriority) {
      var specABeforeB, specBBeforeA;
      if (splitParagraphSpecA.position < splitParagraphSpecB.position) {
        specABeforeB = true;
      } else {
        if (splitParagraphSpecB.position < splitParagraphSpecA.position) {
          specBBeforeA = true;
        } else {
          if (splitParagraphSpecA.position === splitParagraphSpecB.position) {
            if (hasAPriority) {
              specABeforeB = true;
            } else {
              specBBeforeA = true;
            }
          }
        }
      }
      if (specABeforeB) {
        splitParagraphSpecB.position += 1;
        if (splitParagraphSpecA.position < splitParagraphSpecB.sourceParagraphPosition) {
          splitParagraphSpecB.sourceParagraphPosition += 1;
        } else {
          splitParagraphSpecB.sourceParagraphPosition = splitParagraphSpecA.position + 1;
        }
      } else {
        if (specBBeforeA) {
          splitParagraphSpecA.position += 1;
          if (splitParagraphSpecB.position < splitParagraphSpecB.sourceParagraphPosition) {
            splitParagraphSpecA.sourceParagraphPosition += 1;
          } else {
            splitParagraphSpecA.sourceParagraphPosition = splitParagraphSpecB.position + 1;
          }
        }
      }
      return {opSpecsA:[splitParagraphSpecA], opSpecsB:[splitParagraphSpecB]};
    }
    function transformMoveCursorRemoveAnnotation(moveCursorSpec, removeAnnotationSpec) {
      var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec), moveCursorSpecEnd = moveCursorSpec.position + moveCursorSpec.length, removeAnnotationEnd = removeAnnotationSpec.position + removeAnnotationSpec.length;
      if (removeAnnotationSpec.position <= moveCursorSpec.position && moveCursorSpecEnd <= removeAnnotationEnd) {
        moveCursorSpec.position = removeAnnotationSpec.position - 1;
        moveCursorSpec.length = 0;
      } else {
        if (removeAnnotationEnd < moveCursorSpec.position) {
          moveCursorSpec.position -= removeAnnotationSpec.length + 2;
        } else {
          if (removeAnnotationEnd < moveCursorSpecEnd) {
            moveCursorSpec.length -= removeAnnotationSpec.length + 2;
          }
        }
        if (isMoveCursorSpecRangeInverted) {
          invertMoveCursorSpecRange(moveCursorSpec);
        }
      }
      return {opSpecsA:[moveCursorSpec], opSpecsB:[removeAnnotationSpec]};
    }
    function transformMoveCursorRemoveCursor(moveCursorSpec, removeCursorSpec) {
      var isSameCursorRemoved = moveCursorSpec.memberid === removeCursorSpec.memberid;
      return {opSpecsA:isSameCursorRemoved ? [] : [moveCursorSpec], opSpecsB:[removeCursorSpec]};
    }
    function transformMoveCursorRemoveText(moveCursorSpec, removeTextSpec) {
      var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec), moveCursorSpecEnd = moveCursorSpec.position + moveCursorSpec.length, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length;
      if (removeTextSpecEnd <= moveCursorSpec.position) {
        moveCursorSpec.position -= removeTextSpec.length;
      } else {
        if (removeTextSpec.position < moveCursorSpecEnd) {
          if (moveCursorSpec.position < removeTextSpec.position) {
            if (removeTextSpecEnd < moveCursorSpecEnd) {
              moveCursorSpec.length -= removeTextSpec.length;
            } else {
              moveCursorSpec.length = removeTextSpec.position - moveCursorSpec.position;
            }
          } else {
            moveCursorSpec.position = removeTextSpec.position;
            if (removeTextSpecEnd < moveCursorSpecEnd) {
              moveCursorSpec.length = moveCursorSpecEnd - removeTextSpecEnd;
            } else {
              moveCursorSpec.length = 0;
            }
          }
        }
      }
      if (isMoveCursorSpecRangeInverted) {
        invertMoveCursorSpecRange(moveCursorSpec);
      }
      return {opSpecsA:[moveCursorSpec], opSpecsB:[removeTextSpec]};
    }
    function transformMoveCursorSplitParagraph(moveCursorSpec, splitParagraphSpec) {
      var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec);
      if (splitParagraphSpec.position < moveCursorSpec.position) {
        moveCursorSpec.position += 1;
      } else {
        if (splitParagraphSpec.position < moveCursorSpec.position + moveCursorSpec.length) {
          moveCursorSpec.length += 1;
        }
      }
      if (isMoveCursorSpecRangeInverted) {
        invertMoveCursorSpecRange(moveCursorSpec);
      }
      return {opSpecsA:[moveCursorSpec], opSpecsB:[splitParagraphSpec]};
    }
    function transformRemoveAnnotationRemoveAnnotation(removeAnnotationSpecA, removeAnnotationSpecB) {
      var removeAnnotationSpecAResult = [removeAnnotationSpecA], removeAnnotationSpecBResult = [removeAnnotationSpecB];
      if (removeAnnotationSpecA.position === removeAnnotationSpecB.position && removeAnnotationSpecA.length === removeAnnotationSpecB.length) {
        removeAnnotationSpecAResult = [];
        removeAnnotationSpecBResult = [];
      } else {
        if (removeAnnotationSpecA.position < removeAnnotationSpecB.position) {
          removeAnnotationSpecB.position -= removeAnnotationSpecA.length + 2;
        } else {
          removeAnnotationSpecA.position -= removeAnnotationSpecB.length + 2;
        }
      }
      return {opSpecsA:removeAnnotationSpecAResult, opSpecsB:removeAnnotationSpecBResult};
    }
    function transformRemoveAnnotationRemoveText(removeAnnotationSpec, removeTextSpec) {
      var removeAnnotationEnd = removeAnnotationSpec.position + removeAnnotationSpec.length, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, removeAnnotationSpecResult = [removeAnnotationSpec], removeTextSpecResult = [removeTextSpec];
      if (removeAnnotationSpec.position <= removeTextSpec.position && removeTextSpecEnd <= removeAnnotationEnd) {
        removeTextSpecResult = [];
        removeAnnotationSpec.length -= removeTextSpec.length;
      } else {
        if (removeTextSpecEnd < removeAnnotationSpec.position) {
          removeAnnotationSpec.position -= removeTextSpec.length;
        } else {
          if (removeTextSpec.position < removeAnnotationSpec.position) {
            removeAnnotationSpec.position = removeTextSpec.position + 1;
            removeTextSpec.length -= removeAnnotationSpec.length + 2;
          } else {
            removeTextSpec.position -= removeAnnotationSpec.length + 2;
          }
        }
      }
      return {opSpecsA:removeAnnotationSpecResult, opSpecsB:removeTextSpecResult};
    }
    function transformRemoveAnnotationSetParagraphStyle(removeAnnotationSpec, setParagraphStyleSpec) {
      var setParagraphStyleSpecPosition = setParagraphStyleSpec.position, removeAnnotationEnd = removeAnnotationSpec.position + removeAnnotationSpec.length, removeAnnotationSpecResult = [removeAnnotationSpec], setParagraphStyleSpecResult = [setParagraphStyleSpec];
      if (removeAnnotationSpec.position <= setParagraphStyleSpecPosition && setParagraphStyleSpecPosition <= removeAnnotationEnd) {
        setParagraphStyleSpecResult = [];
      } else {
        if (removeAnnotationEnd < setParagraphStyleSpecPosition) {
          setParagraphStyleSpec.position -= removeAnnotationSpec.length + 2;
        }
      }
      return {opSpecsA:removeAnnotationSpecResult, opSpecsB:setParagraphStyleSpecResult};
    }
    function transformRemoveAnnotationSplitParagraph(removeAnnotationSpec, splitParagraphSpec) {
      var splitParagraphSpecPosition = splitParagraphSpec.position, removeAnnotationEnd = removeAnnotationSpec.position + removeAnnotationSpec.length, removeAnnotationSpecResult = [removeAnnotationSpec], splitParagraphSpecResult = [splitParagraphSpec];
      if (removeAnnotationSpec.position <= splitParagraphSpecPosition && splitParagraphSpecPosition <= removeAnnotationEnd) {
        splitParagraphSpecResult = [];
        removeAnnotationSpec.length += 1;
      } else {
        if (removeAnnotationEnd < splitParagraphSpec.sourceParagraphPosition) {
          splitParagraphSpec.sourceParagraphPosition -= removeAnnotationSpec.length + 2;
        }
        if (removeAnnotationEnd < splitParagraphSpecPosition) {
          splitParagraphSpec.position -= removeAnnotationSpec.length + 2;
        } else {
          removeAnnotationSpec.position += 1;
        }
      }
      return {opSpecsA:removeAnnotationSpecResult, opSpecsB:splitParagraphSpecResult};
    }
    function transformRemoveCursorRemoveCursor(removeCursorSpecA, removeCursorSpecB) {
      var isSameMemberid = removeCursorSpecA.memberid === removeCursorSpecB.memberid;
      return {opSpecsA:isSameMemberid ? [] : [removeCursorSpecA], opSpecsB:isSameMemberid ? [] : [removeCursorSpecB]};
    }
    function transformRemoveStyleRemoveStyle(removeStyleSpecA, removeStyleSpecB) {
      var isSameStyle = removeStyleSpecA.styleName === removeStyleSpecB.styleName && removeStyleSpecA.styleFamily === removeStyleSpecB.styleFamily;
      return {opSpecsA:isSameStyle ? [] : [removeStyleSpecA], opSpecsB:isSameStyle ? [] : [removeStyleSpecB]};
    }
    function transformRemoveStyleSetParagraphStyle(removeStyleSpec, setParagraphStyleSpec) {
      var helperOpspec, removeStyleSpecResult = [removeStyleSpec], setParagraphStyleSpecResult = [setParagraphStyleSpec];
      if (removeStyleSpec.styleFamily === "paragraph" && removeStyleSpec.styleName === setParagraphStyleSpec.styleName) {
        helperOpspec = {optype:"SetParagraphStyle", memberid:removeStyleSpec.memberid, timestamp:removeStyleSpec.timestamp, position:setParagraphStyleSpec.position, styleName:""};
        removeStyleSpecResult.unshift(helperOpspec);
        setParagraphStyleSpec.styleName = "";
      }
      return {opSpecsA:removeStyleSpecResult, opSpecsB:setParagraphStyleSpecResult};
    }
    function transformRemoveStyleUpdateParagraphStyle(removeStyleSpec, updateParagraphStyleSpec) {
      var setAttributes, helperOpspec, removeStyleSpecResult = [removeStyleSpec], updateParagraphStyleSpecResult = [updateParagraphStyleSpec];
      if (removeStyleSpec.styleFamily === "paragraph") {
        setAttributes = getStyleReferencingAttributes(updateParagraphStyleSpec.setProperties, removeStyleSpec.styleName);
        if (setAttributes.length > 0) {
          helperOpspec = {optype:"UpdateParagraphStyle", memberid:removeStyleSpec.memberid, timestamp:removeStyleSpec.timestamp, styleName:updateParagraphStyleSpec.styleName, removedProperties:{attributes:setAttributes.join(",")}};
          removeStyleSpecResult.unshift(helperOpspec);
        }
        if (removeStyleSpec.styleName === updateParagraphStyleSpec.styleName) {
          updateParagraphStyleSpecResult = [];
        } else {
          dropStyleReferencingAttributes(updateParagraphStyleSpec.setProperties, removeStyleSpec.styleName);
        }
      }
      return {opSpecsA:removeStyleSpecResult, opSpecsB:updateParagraphStyleSpecResult};
    }
    function transformRemoveTextRemoveText(removeTextSpecA, removeTextSpecB) {
      var removeTextSpecAEnd = removeTextSpecA.position + removeTextSpecA.length, removeTextSpecBEnd = removeTextSpecB.position + removeTextSpecB.length, removeTextSpecAResult = [removeTextSpecA], removeTextSpecBResult = [removeTextSpecB];
      if (removeTextSpecBEnd <= removeTextSpecA.position) {
        removeTextSpecA.position -= removeTextSpecB.length;
      } else {
        if (removeTextSpecAEnd <= removeTextSpecB.position) {
          removeTextSpecB.position -= removeTextSpecA.length;
        } else {
          if (removeTextSpecB.position < removeTextSpecAEnd) {
            if (removeTextSpecA.position < removeTextSpecB.position) {
              if (removeTextSpecBEnd < removeTextSpecAEnd) {
                removeTextSpecA.length = removeTextSpecA.length - removeTextSpecB.length;
              } else {
                removeTextSpecA.length = removeTextSpecB.position - removeTextSpecA.position;
              }
              if (removeTextSpecAEnd < removeTextSpecBEnd) {
                removeTextSpecB.position = removeTextSpecA.position;
                removeTextSpecB.length = removeTextSpecBEnd - removeTextSpecAEnd;
              } else {
                removeTextSpecBResult = [];
              }
            } else {
              if (removeTextSpecAEnd < removeTextSpecBEnd) {
                removeTextSpecB.length = removeTextSpecB.length - removeTextSpecA.length;
              } else {
                if (removeTextSpecB.position < removeTextSpecA.position) {
                  removeTextSpecB.length = removeTextSpecA.position - removeTextSpecB.position;
                } else {
                  removeTextSpecBResult = [];
                }
              }
              if (removeTextSpecBEnd < removeTextSpecAEnd) {
                removeTextSpecA.position = removeTextSpecB.position;
                removeTextSpecA.length = removeTextSpecAEnd - removeTextSpecBEnd;
              } else {
                removeTextSpecAResult = [];
              }
            }
          }
        }
      }
      return {opSpecsA:removeTextSpecAResult, opSpecsB:removeTextSpecBResult};
    }
    function transformRemoveTextSetParagraphStyle(removeTextSpec, setParagraphStyleSpec) {
      if (removeTextSpec.position < setParagraphStyleSpec.position) {
        setParagraphStyleSpec.position -= removeTextSpec.length;
      }
      return {opSpecsA:[removeTextSpec], opSpecsB:[setParagraphStyleSpec]};
    }
    function transformRemoveTextSplitParagraph(removeTextSpec, splitParagraphSpec) {
      var removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, helperOpspec, removeTextSpecResult = [removeTextSpec], splitParagraphSpecResult = [splitParagraphSpec];
      if (splitParagraphSpec.position <= removeTextSpec.position) {
        removeTextSpec.position += 1;
      } else {
        if (splitParagraphSpec.position < removeTextSpecEnd) {
          removeTextSpec.length = splitParagraphSpec.position - removeTextSpec.position;
          helperOpspec = {optype:"RemoveText", memberid:removeTextSpec.memberid, timestamp:removeTextSpec.timestamp, position:splitParagraphSpec.position + 1, length:removeTextSpecEnd - splitParagraphSpec.position};
          removeTextSpecResult.unshift(helperOpspec);
        }
      }
      if (removeTextSpec.position + removeTextSpec.length <= splitParagraphSpec.position) {
        splitParagraphSpec.position -= removeTextSpec.length;
      } else {
        if (removeTextSpec.position < splitParagraphSpec.position) {
          splitParagraphSpec.position = removeTextSpec.position;
        }
      }
      if (removeTextSpec.position + removeTextSpec.length < splitParagraphSpec.sourceParagraphPosition) {
        splitParagraphSpec.sourceParagraphPosition -= removeTextSpec.length;
      }
      return {opSpecsA:removeTextSpecResult, opSpecsB:splitParagraphSpecResult};
    }
    function passUnchanged(opSpecA, opSpecB) {
      return {opSpecsA:[opSpecA], opSpecsB:[opSpecB]};
    }
    var transformations;
    transformations = {"AddAnnotation":{"AddAnnotation":transformAddAnnotationAddAnnotation, "AddCursor":passUnchanged, "AddMember":passUnchanged, "AddStyle":passUnchanged, "ApplyDirectStyling":transformAddAnnotationApplyDirectStyling, "InsertText":transformAddAnnotationInsertText, "MergeParagraph":transformAddAnnotationMergeParagraph, "MoveCursor":transformAddAnnotationMoveCursor, "RemoveAnnotation":transformAddAnnotationRemoveAnnotation, "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged,
    "RemoveStyle":passUnchanged, "RemoveText":transformAddAnnotationRemoveText, "SetParagraphStyle":transformAddAnnotationSetParagraphStyle, "SplitParagraph":transformAddAnnotationSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "AddCursor":{"AddCursor":passUnchanged, "AddMember":passUnchanged, "AddStyle":passUnchanged, "ApplyDirectStyling":passUnchanged, "InsertText":passUnchanged, "MergeParagraph":passUnchanged, "MoveCursor":passUnchanged,
    "RemoveAnnotation":passUnchanged, "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "AddMember":{"AddStyle":passUnchanged, "ApplyDirectStyling":passUnchanged, "InsertText":passUnchanged, "MergeParagraph":passUnchanged, "MoveCursor":passUnchanged, "RemoveAnnotation":passUnchanged,
    "RemoveCursor":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "AddStyle":{"AddStyle":passUnchanged, "ApplyDirectStyling":passUnchanged, "InsertText":passUnchanged, "MergeParagraph":passUnchanged, "MoveCursor":passUnchanged, "RemoveAnnotation":passUnchanged, "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":transformAddStyleRemoveStyle,
    "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "ApplyDirectStyling":{"ApplyDirectStyling":transformApplyDirectStylingApplyDirectStyling, "InsertText":transformApplyDirectStylingInsertText, "MergeParagraph":transformApplyDirectStylingMergeParagraph, "MoveCursor":passUnchanged, "RemoveAnnotation":transformApplyDirectStylingRemoveAnnotation, "RemoveCursor":passUnchanged,
    "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformApplyDirectStylingRemoveText, "SetParagraphStyle":passUnchanged, "SplitParagraph":transformApplyDirectStylingSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "InsertText":{"InsertText":transformInsertTextInsertText, "MergeParagraph":transformInsertTextMergeParagraph, "MoveCursor":transformInsertTextMoveCursor, "RemoveAnnotation":transformInsertTextRemoveAnnotation,
    "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformInsertTextRemoveText, "SetParagraphStyle":transformInsertTextSetParagraphStyle, "SplitParagraph":transformInsertTextSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "MergeParagraph":{"MergeParagraph":transformMergeParagraphMergeParagraph, "MoveCursor":transformMergeParagraphMoveCursor, "RemoveAnnotation":transformMergeParagraphRemoveAnnotation,
    "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformMergeParagraphRemoveText, "SetParagraphStyle":transformMergeParagraphSetParagraphStyle, "SplitParagraph":transformMergeParagraphSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "MoveCursor":{"MoveCursor":passUnchanged, "RemoveAnnotation":transformMoveCursorRemoveAnnotation, "RemoveCursor":transformMoveCursorRemoveCursor,
    "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformMoveCursorRemoveText, "SetParagraphStyle":passUnchanged, "SplitParagraph":transformMoveCursorSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "RemoveAnnotation":{"RemoveAnnotation":transformRemoveAnnotationRemoveAnnotation, "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformRemoveAnnotationRemoveText,
    "SetParagraphStyle":transformRemoveAnnotationSetParagraphStyle, "SplitParagraph":transformRemoveAnnotationSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "RemoveCursor":{"RemoveCursor":transformRemoveCursorRemoveCursor, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged,
    "UpdateParagraphStyle":passUnchanged}, "RemoveMember":{"RemoveStyle":passUnchanged, "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "RemoveStyle":{"RemoveStyle":transformRemoveStyleRemoveStyle, "RemoveText":passUnchanged, "SetParagraphStyle":transformRemoveStyleSetParagraphStyle, "SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":transformRemoveStyleUpdateParagraphStyle},
    "RemoveText":{"RemoveText":transformRemoveTextRemoveText, "SetParagraphStyle":transformRemoveTextSetParagraphStyle, "SplitParagraph":transformRemoveTextSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "SetParagraphStyle":{"SetParagraphStyle":transformSetParagraphStyleSetParagraphStyle, "SplitParagraph":transformSetParagraphStyleSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged},
    "SplitParagraph":{"SplitParagraph":transformSplitParagraphSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "UpdateMember":{"UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "UpdateMetadata":{"UpdateMetadata":transformUpdateMetadataUpdateMetadata, "UpdateParagraphStyle":passUnchanged}, "UpdateParagraphStyle":{"UpdateParagraphStyle":transformUpdateParagraphStyleUpdateParagraphStyle}};
    this.passUnchanged = passUnchanged;
    this.extendTransformations = function(moreTransformations) {
      Object.keys(moreTransformations).forEach(function(optypeA) {
        var moreTransformationsOptypeAMap = moreTransformations[optypeA], optypeAMap, isExtendingOptypeAMap = transformations.hasOwnProperty(optypeA);
        runtime.log((isExtendingOptypeAMap ? "Extending" : "Adding") + " map for optypeA: " + optypeA);
        if (!isExtendingOptypeAMap) {
          transformations[optypeA] = {};
        }
        optypeAMap = transformations[optypeA];
        Object.keys(moreTransformationsOptypeAMap).forEach(function(optypeB) {
          var isOverwritingOptypeBEntry = optypeAMap.hasOwnProperty(optypeB);
          runtime.assert(optypeA <= optypeB, "Wrong order:" + optypeA + ", " + optypeB);
          runtime.log("  " + (isOverwritingOptypeBEntry ? "Overwriting" : "Adding") + " entry for optypeB: " + optypeB);
          optypeAMap[optypeB] = moreTransformationsOptypeAMap[optypeB];
        });
      });
    };
    this.transformOpspecVsOpspec = function(opSpecA, opSpecB) {
      var isOptypeAAlphaNumericSmaller = opSpecA.optype <= opSpecB.optype, helper, transformationFunctionMap, transformationFunction, result;
      runtime.log("Crosstransforming:");
      runtime.log(runtime.toJson(opSpecA));
      runtime.log(runtime.toJson(opSpecB));
      if (!isOptypeAAlphaNumericSmaller) {
        helper = opSpecA;
        opSpecA = opSpecB;
        opSpecB = helper;
      }
      transformationFunctionMap = transformations[opSpecA.optype];
      transformationFunction = transformationFunctionMap && transformationFunctionMap[opSpecB.optype];
      if (transformationFunction) {
        result = transformationFunction(opSpecA, opSpecB, !isOptypeAAlphaNumericSmaller);
        if (!isOptypeAAlphaNumericSmaller && result !== null) {
          result = {opSpecsA:result.opSpecsB, opSpecsB:result.opSpecsA};
        }
      } else {
        result = null;
      }
      runtime.log("result:");
      if (result) {
        runtime.log(runtime.toJson(result.opSpecsA));
        runtime.log(runtime.toJson(result.opSpecsB));
      } else {
        runtime.log("null");
      }
      return result;
    };
  };
  ops.OperationTransformer = function OperationTransformer() {
    var operationTransformMatrix = new ops.OperationTransformMatrix;
    function transformOpVsOp(opSpecA, opSpecB) {
      return operationTransformMatrix.transformOpspecVsOpspec(opSpecA, opSpecB);
    }
    function transformOpListVsOp(opSpecsA, opSpecB) {
      var transformResult, transformListResult, transformedOpspecsA = [], transformedOpspecsB = [];
      while (opSpecsA.length > 0 && opSpecB) {
        transformResult = transformOpVsOp(opSpecsA.shift(), opSpecB);
        if (!transformResult) {
          return null;
        }
        transformedOpspecsA = transformedOpspecsA.concat(transformResult.opSpecsA);
        if (transformResult.opSpecsB.length === 0) {
          transformedOpspecsA = transformedOpspecsA.concat(opSpecsA);
          opSpecB = null;
          break;
        }
        while (transformResult.opSpecsB.length > 1) {
          transformListResult = transformOpListVsOp(opSpecsA, transformResult.opSpecsB.shift());
          if (!transformListResult) {
            return null;
          }
          transformedOpspecsB = transformedOpspecsB.concat(transformListResult.opSpecsB);
          opSpecsA = transformListResult.opSpecsA;
        }
        opSpecB = transformResult.opSpecsB.pop();
      }
      if (opSpecB) {
        transformedOpspecsB.push(opSpecB);
      }
      return {opSpecsA:transformedOpspecsA, opSpecsB:transformedOpspecsB};
    }
    this.getOperationTransformMatrix = function() {
      return operationTransformMatrix;
    };
    this.transform = function(opSpecsA, opSpecsB) {
      var transformResult, transformedOpspecsB = [];
      while (opSpecsB.length > 0) {
        transformResult = transformOpListVsOp(opSpecsA, opSpecsB.shift());
        if (!transformResult) {
          return null;
        }
        opSpecsA = transformResult.opSpecsA;
        transformedOpspecsB = transformedOpspecsB.concat(transformResult.opSpecsB);
      }
      return {opSpecsA:opSpecsA, opSpecsB:transformedOpspecsB};
    };
  };
  var webodf_css = '@namespace draw url(urn:oasis:names:tc:opendocument:xmlns:drawing:1.0);@namespace fo url(urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0);@namespace office url(urn:oasis:names:tc:opendocument:xmlns:office:1.0);@namespace presentation url(urn:oasis:names:tc:opendocument:xmlns:presentation:1.0);@namespace style url(urn:oasis:names:tc:opendocument:xmlns:style:1.0);@namespace svg url(urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0);@namespace table url(urn:oasis:names:tc:opendocument:xmlns:table:1.0);@namespace text url(urn:oasis:names:tc:opendocument:xmlns:text:1.0);@namespace webodfhelper url(urn:webodf:names:helper);@namespace cursor url(urn:webodf:names:cursor);@namespace editinfo url(urn:webodf:names:editinfo);@namespace annotation url(urn:webodf:names:annotation);@namespace dc url(http://purl.org/dc/elements/1.1/);@namespace svgns url(http://www.w3.org/2000/svg);office|document > *, office|document-content > * {display: none;}office|body, office|document {display: inline-block;position: relative;}text|p, text|h {display: block;padding: 0;margin: 0;line-height: normal;position: relative;}text|p::after, text|h::after {content: "\\200B";white-space: pre;}*[webodfhelper|containsparagraphanchor] {position: relative;}text|s {white-space: pre;}text|tab {display: inline;white-space: pre;}text|tracked-changes {display: none;}office|binary-data {display: none;}office|text {display: block;text-align: left;overflow: visible;word-wrap: break-word;}office|text::selection {background: transparent;}.webodf-virtualSelections *::selection {background: transparent;}.webodf-virtualSelections *::-moz-selection {background: transparent;}office|text * draw|text-box {display: block;border: 1px solid #d3d3d3;}office|text draw|frame {z-index: 1;}office|spreadsheet {display: block;border-collapse: collapse;empty-cells: show;font-family: sans-serif;font-size: 10pt;text-align: left;page-break-inside: avoid;overflow: hidden;}office|presentation {display: inline-block;text-align: left;}#shadowContent {display: inline-block;text-align: left;}draw|page {display: block;position: relative;overflow: hidden;}presentation|notes, presentation|footer-decl, presentation|date-time-decl {display: none;}@media print {draw|page {border: 1pt solid black;page-break-inside: avoid;}presentation|notes {}}office|spreadsheet text|p {border: 0px;padding: 1px;margin: 0px;}office|spreadsheet table|table {margin: 3px;}office|spreadsheet table|table:after {}office|spreadsheet table|table-row {counter-increment: row;}office|spreadsheet table|table-row:before {width: 3em;background: #cccccc;border: 1px solid black;text-align: center;content: counter(row);display: table-cell;}office|spreadsheet table|table-cell {border: 1px solid #cccccc;}table|table {display: table;}draw|frame table|table {width: 100%;height: 100%;background: white;}table|table-header-rows {display: table-header-group;}table|table-row {display: table-row;}table|table-column {display: table-column;}table|table-cell {width: 0.889in;display: table-cell;word-break: break-all;}draw|frame {display: block;}draw|image {display: block;width: 100%;height: 100%;top: 0px;left: 0px;background-repeat: no-repeat;background-size: 100% 100%;-moz-background-size: 100% 100%;}draw|frame > draw|image:nth-of-type(n+2) {display: none;}text|list:before {display: none;content:"";}text|list {display: block;}text|list-item {display: block;}text|number {display:none;}text|a {color: blue;text-decoration: underline;cursor: pointer;}.webodf-inactiveLinks text|a {cursor: text;}text|note-citation {vertical-align: super;font-size: smaller;}text|note-body {display: none;}text|note:hover text|note-citation {background: #dddddd;}text|note:hover text|note-body {display: block;left:1em;max-width: 80%;position: absolute;background: #ffffaa;}text|bibliography-source {display: none;}svg|title, svg|desc {display: none;}video {width: 100%;height: 100%}cursor|anchor {display: none;}cursor|cursor {display: none;}.webodf-caretOverlay {position: absolute;top: 5%;height: 1em;z-index: 10;padding-left: 1px;pointer-events: none;}.webodf-caretOverlay .caret {position: absolute;border-left: 2px solid black;top: 0;bottom: 0;right: 0;}.webodf-caretOverlay .handle {position: absolute;margin-top: 5px;padding-top: 3px;margin-left: auto;margin-right: auto;width: 64px;height: 68px;border-radius: 5px;opacity: 0.3;text-align: center;background-color: black;box-shadow: 0px 0px 5px rgb(90, 90, 90);border: 1px solid black;top: -85px;right: -32px;}.webodf-caretOverlay .handle > img {box-shadow: 0px 0px 5px rgb(90, 90, 90) inset;background-color: rgb(200, 200, 200);border-radius: 5px;border: 2px solid;height: 60px;width: 60px;display: block;margin: auto;}.webodf-caretOverlay .handle.active {opacity: 0.8;}.webodf-caretOverlay .handle:after {content: " ";position: absolute;width: 0px;height: 0px;border-style: solid;border-width: 8.7px 5px 0 5px;border-color: black transparent transparent transparent;top: 100%;left: 43%;}.webodf-caretSizer {display: inline-block;width: 0;visibility: hidden;}#eventTrap {display: block;position: absolute;bottom: 0;left: 0;outline: none;opacity: 0;color: rgba(255, 255, 255, 0);pointer-events: none;white-space: pre;overflow: hidden;}cursor|cursor > #composer {text-decoration: underline;}cursor|cursor[cursor|caret-sizer-active="true"],cursor|cursor[cursor|composing="true"] {display: inline;}editinfo|editinfo {display: inline-block;}.editInfoMarker {position: absolute;width: 10px;height: 100%;left: -20px;opacity: 0.8;top: 0;border-radius: 5px;background-color: transparent;box-shadow: 0px 0px 5px rgba(50, 50, 50, 0.75);}.editInfoMarker:hover {box-shadow: 0px 0px 8px rgba(0, 0, 0, 1);}.editInfoHandle {position: absolute;background-color: black;padding: 5px;border-radius: 5px;opacity: 0.8;box-shadow: 0px 0px 5px rgba(50, 50, 50, 0.75);bottom: 100%;margin-bottom: 10px;z-index: 3;left: -25px;}.editInfoHandle:after {content: " ";position: absolute;width: 0px;height: 0px;border-style: solid;border-width: 8.7px 5px 0 5px;border-color: black transparent transparent transparent;top: 100%;left: 5px;}.editInfo {font-family: sans-serif;font-weight: normal;font-style: normal;text-decoration: none;color: white;width: 100%;height: 12pt;}.editInfoColor {float: left;width: 10pt;height: 10pt;border: 1px solid white;}.editInfoAuthor {float: left;margin-left: 5pt;font-size: 10pt;text-align: left;height: 12pt;line-height: 12pt;}.editInfoTime {float: right;margin-left: 30pt;font-size: 8pt;font-style: italic;color: yellow;height: 12pt;line-height: 12pt;}.annotationWrapper {display: inline;position: relative;}.annotationRemoveButton:before {content: "\u00d7";color: white;padding: 5px;line-height: 1em;}.annotationRemoveButton {width: 20px;height: 20px;border-radius: 10px;background-color: black;box-shadow: 0px 0px 5px rgba(50, 50, 50, 0.75);position: absolute;top: -10px;left: -10px;z-index: 3;text-align: center;font-family: sans-serif;font-style: normal;font-weight: normal;text-decoration: none;font-size: 15px;}.annotationRemoveButton:hover {cursor: pointer;box-shadow: 0px 0px 5px rgba(0, 0, 0, 1);}.annotationNote {width: 4cm;position: absolute;display: inline;z-index: 10;top: 0;}.annotationNote > office|annotation {display: block;text-align: left;}.annotationConnector {position: absolute;display: inline;top: 0;z-index: 2;border-top: 1px dashed brown;}.annotationConnector.angular {-moz-transform-origin: left top;-webkit-transform-origin: left top;-ms-transform-origin: left top;transform-origin: left top;}.annotationConnector.horizontal {left: 0;}.annotationConnector.horizontal:before {content: "";display: inline;position: absolute;width: 0px;height: 0px;border-style: solid;border-width: 8.7px 5px 0 5px;border-color: brown transparent transparent transparent;top: -1px;left: -5px;}office|annotation {width: 100%;height: 100%;display: none;background: rgb(198, 238, 184);background: -moz-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);background: -webkit-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);background: -o-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);background: -ms-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);background: linear-gradient(180deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);box-shadow: 0 3px 4px -3px #ccc;}office|annotation > dc|creator {display: block;font-size: 10pt;font-weight: normal;font-style: normal;font-family: sans-serif;color: white;background-color: brown;padding: 4px;}office|annotation > dc|date {display: block;font-size: 10pt;font-weight: normal;font-style: normal;font-family: sans-serif;border: 4px solid transparent;color: black;}office|annotation > text|list {display: block;padding: 5px;}office|annotation text|p {font-size: 10pt;color: black;font-weight: normal;font-style: normal;text-decoration: none;font-family: sans-serif;}#annotationsPane {background-color: #EAEAEA;width: 4cm;height: 100%;display: none;position: absolute;outline: 1px solid #ccc;}.webodf-annotationHighlight {background-color: yellow;position: relative;}.webodf-selectionOverlay {position: absolute;pointer-events: none;top: 0;left: 0;top: 0;left: 0;width: 100%;height: 100%;z-index: 15;}.webodf-selectionOverlay > polygon {fill-opacity: 0.3;stroke-opacity: 0.8;stroke-width: 1;fill-rule: evenodd;}.webodf-selectionOverlay > .webodf-draggable {fill-opacity: 0.8;stroke-opacity: 0;stroke-width: 8;pointer-events: all;display: none;-moz-transform-origin: center center;-webkit-transform-origin: center center;-ms-transform-origin: center center;transform-origin: center center;}#imageSelector {display: none;position: absolute;border-style: solid;border-color: black;}#imageSelector > div {width: 5px;height: 5px;display: block;position: absolute;border: 1px solid black;background-color: #ffffff;}#imageSelector > .topLeft {top: -4px;left: -4px;}#imageSelector > .topRight {top: -4px;right: -4px;}#imageSelector > .bottomRight {right: -4px;bottom: -4px;}#imageSelector > .bottomLeft {bottom: -4px;left: -4px;}#imageSelector > .topMiddle {top: -4px;left: 50%;margin-left: -2.5px;}#imageSelector > .rightMiddle {top: 50%;right: -4px;margin-top: -2.5px;}#imageSelector > .bottomMiddle {bottom: -4px;left: 50%;margin-left: -2.5px;}#imageSelector > .leftMiddle {top: 50%;left: -4px;margin-top: -2.5px;}div.webodf-customScrollbars::-webkit-scrollbar{width: 8px;height: 8px;background-color: transparent;}div.webodf-customScrollbars::-webkit-scrollbar-track{background-color: transparent;}div.webodf-customScrollbars::-webkit-scrollbar-thumb{background-color: #444;border-radius: 4px;}.webodf-hyperlinkTooltip {display: none;color: white;background-color: black;border-radius: 5px;box-shadow: 2px 2px 5px gray;padding: 3px;position: absolute;max-width: 210px;text-align: left;word-break: break-all;z-index: 16;}.webodf-hyperlinkTooltipText {display: block;font-weight: bold;}';
  /*
  
   @licstart
  JSZip - A Javascript class for generating and reading zip files
  <http://stuartk.com/jszip>
  
  (c) 2009-2014 Stuart Knightley <stuart [at] stuartk.com>
  Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown.
  
  JSZip uses the library pako released under the MIT license :
  https://github.com/nodeca/pako/blob/master/LICENSE
   @licend
  */
  !function(e) {
    var globalScope = typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}, externs = globalScope.externs || (globalScope.externs = {});
    externs.JSZip = e();
  }(function() {
    var define, module, exports;
    return function e(t, n, r) {
      function s(o, u) {
        if (!n[o]) {
          if (!t[o]) {
            var a = typeof require == "function" && require;
            if (!u && a) {
              return a(o, !0);
            }
            if (i) {
              return i(o, !0);
            }
            throw new Error("Cannot find module '" + o + "'");
          }
          var f = n[o] = {exports:{}};
          t[o][0].call(f.exports, function(e) {
            var n = t[o][1][e];
            return s(n ? n : e);
          }, f, f.exports, e, t, n, r);
        }
        return n[o].exports;
      }
      var i = typeof require == "function" && require;
      for (var o = 0;o < r.length;o++) {
        s(r[o]);
      }
      return s;
    }({1:[function(_dereq_, module, exports) {
      var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
      exports.encode = function(input, utf8) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        while (i < input.length) {
          chr1 = input.charCodeAt(i++);
          chr2 = input.charCodeAt(i++);
          chr3 = input.charCodeAt(i++);
          enc1 = chr1 >> 2;
          enc2 = (chr1 & 3) << 4 | chr2 >> 4;
          enc3 = (chr2 & 15) << 2 | chr3 >> 6;
          enc4 = chr3 & 63;
          if (isNaN(chr2)) {
            enc3 = enc4 = 64;
          } else {
            if (isNaN(chr3)) {
              enc4 = 64;
            }
          }
          output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
        }
        return output;
      };
      exports.decode = function(input, utf8) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length) {
          enc1 = _keyStr.indexOf(input.charAt(i++));
          enc2 = _keyStr.indexOf(input.charAt(i++));
          enc3 = _keyStr.indexOf(input.charAt(i++));
          enc4 = _keyStr.indexOf(input.charAt(i++));
          chr1 = enc1 << 2 | enc2 >> 4;
          chr2 = (enc2 & 15) << 4 | enc3 >> 2;
          chr3 = (enc3 & 3) << 6 | enc4;
          output = output + String.fromCharCode(chr1);
          if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
          }
          if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
          }
        }
        return output;
      };
    }, {}], 2:[function(_dereq_, module, exports) {
      function CompressedObject() {
        this.compressedSize = 0;
        this.uncompressedSize = 0;
        this.crc32 = 0;
        this.compressionMethod = null;
        this.compressedContent = null;
      }
      CompressedObject.prototype = {getContent:function() {
        return null;
      }, getCompressedContent:function() {
        return null;
      }};
      module.exports = CompressedObject;
    }, {}], 3:[function(_dereq_, module, exports) {
      exports.STORE = {magic:"\x00\x00", compress:function(content) {
        return content;
      }, uncompress:function(content) {
        return content;
      }, compressInputType:null, uncompressInputType:null};
      exports.DEFLATE = _dereq_("./flate");
    }, {"./flate":8}], 4:[function(_dereq_, module, exports) {
      var utils = _dereq_("./utils");
      var table = [0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684,
      3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925,
      453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989,
      3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462,
      1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115,
      1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918E3, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117];
      module.exports = function crc32(input, crc) {
        if (typeof input === "undefined" || !input.length) {
          return 0;
        }
        var isArray = utils.getTypeOf(input) !== "string";
        if (typeof crc == "undefined") {
          crc = 0;
        }
        var x = 0;
        var y = 0;
        var b = 0;
        crc = crc ^ -1;
        for (var i = 0, iTop = input.length;i < iTop;i++) {
          b = isArray ? input[i] : input.charCodeAt(i);
          y = (crc ^ b) & 255;
          x = table[y];
          crc = crc >>> 8 ^ x;
        }
        return crc ^ -1;
      };
    }, {"./utils":21}], 5:[function(_dereq_, module, exports) {
      var utils = _dereq_("./utils");
      function DataReader(data) {
        this.data = null;
        this.length = 0;
        this.index = 0;
      }
      DataReader.prototype = {checkOffset:function(offset) {
        this.checkIndex(this.index + offset);
      }, checkIndex:function(newIndex) {
        if (this.length < newIndex || newIndex < 0) {
          throw new Error("End of data reached (data length = " + this.length + ", asked index = " + newIndex + "). Corrupted zip ?");
        }
      }, setIndex:function(newIndex) {
        this.checkIndex(newIndex);
        this.index = newIndex;
      }, skip:function(n) {
        this.setIndex(this.index + n);
      }, byteAt:function(i) {
      }, readInt:function(size) {
        var result = 0, i;
        this.checkOffset(size);
        for (i = this.index + size - 1;i >= this.index;i--) {
          result = (result << 8) + this.byteAt(i);
        }
        this.index += size;
        return result;
      }, readString:function(size) {
        return utils.transformTo("string", this.readData(size));
      }, readData:function(size) {
      }, lastIndexOfSignature:function(sig) {
      }, readDate:function() {
        var dostime = this.readInt(4);
        return new Date((dostime >> 25 & 127) + 1980, (dostime >> 21 & 15) - 1, dostime >> 16 & 31, dostime >> 11 & 31, dostime >> 5 & 63, (dostime & 31) << 1);
      }};
      module.exports = DataReader;
    }, {"./utils":21}], 6:[function(_dereq_, module, exports) {
      exports.base64 = false;
      exports.binary = false;
      exports.dir = false;
      exports.createFolders = false;
      exports.date = null;
      exports.compression = null;
      exports.comment = null;
    }, {}], 7:[function(_dereq_, module, exports) {
      var utils = _dereq_("./utils");
      exports.string2binary = function(str) {
        return utils.string2binary(str);
      };
      exports.string2Uint8Array = function(str) {
        return utils.transformTo("uint8array", str);
      };
      exports.uint8Array2String = function(array) {
        return utils.transformTo("string", array);
      };
      exports.string2Blob = function(str) {
        var buffer = utils.transformTo("arraybuffer", str);
        return utils.arrayBuffer2Blob(buffer);
      };
      exports.arrayBuffer2Blob = function(buffer) {
        return utils.arrayBuffer2Blob(buffer);
      };
      exports.transformTo = function(outputType, input) {
        return utils.transformTo(outputType, input);
      };
      exports.getTypeOf = function(input) {
        return utils.getTypeOf(input);
      };
      exports.checkSupport = function(type) {
        return utils.checkSupport(type);
      };
      exports.MAX_VALUE_16BITS = utils.MAX_VALUE_16BITS;
      exports.MAX_VALUE_32BITS = utils.MAX_VALUE_32BITS;
      exports.pretty = function(str) {
        return utils.pretty(str);
      };
      exports.findCompression = function(compressionMethod) {
        return utils.findCompression(compressionMethod);
      };
      exports.isRegExp = function(object) {
        return utils.isRegExp(object);
      };
    }, {"./utils":21}], 8:[function(_dereq_, module, exports) {
      var USE_TYPEDARRAY = typeof Uint8Array !== "undefined" && typeof Uint16Array !== "undefined" && typeof Uint32Array !== "undefined";
      var pako = _dereq_("pako");
      exports.uncompressInputType = USE_TYPEDARRAY ? "uint8array" : "array";
      exports.compressInputType = USE_TYPEDARRAY ? "uint8array" : "array";
      exports.magic = "\b\x00";
      exports.compress = function(input) {
        return pako.deflateRaw(input);
      };
      exports.uncompress = function(input) {
        return pako.inflateRaw(input);
      };
    }, {"pako":24}], 9:[function(_dereq_, module, exports) {
      var base64 = _dereq_("./base64");
      function JSZip(data, options) {
        if (!(this instanceof JSZip)) {
          return new JSZip(data, options);
        }
        this.files = {};
        this.comment = null;
        this.root = "";
        if (data) {
          this.load(data, options);
        }
        this.clone = function() {
          var newObj = new JSZip;
          for (var i in this) {
            if (typeof this[i] !== "function") {
              newObj[i] = this[i];
            }
          }
          return newObj;
        };
      }
      JSZip.prototype = _dereq_("./object");
      JSZip.prototype.load = _dereq_("./load");
      JSZip.support = _dereq_("./support");
      JSZip.defaults = _dereq_("./defaults");
      JSZip.utils = _dereq_("./deprecatedPublicUtils");
      JSZip.base64 = {encode:function(input) {
        return base64.encode(input);
      }, decode:function(input) {
        return base64.decode(input);
      }};
      JSZip.compressions = _dereq_("./compressions");
      module.exports = JSZip;
    }, {"./base64":1, "./compressions":3, "./defaults":6, "./deprecatedPublicUtils":7, "./load":10, "./object":13, "./support":17}], 10:[function(_dereq_, module, exports) {
      var base64 = _dereq_("./base64");
      var ZipEntries = _dereq_("./zipEntries");
      module.exports = function(data, options) {
        var files, zipEntries, i, input;
        options = options || {};
        if (options.base64) {
          data = base64.decode(data);
        }
        zipEntries = new ZipEntries(data, options);
        files = zipEntries.files;
        for (i = 0;i < files.length;i++) {
          input = files[i];
          this.file(input.fileName, input.decompressed, {binary:true, optimizedBinaryString:true, date:input.date, dir:input.dir, comment:input.fileComment.length ? input.fileComment : null, createFolders:options.createFolders});
        }
        if (zipEntries.zipComment.length) {
          this.comment = zipEntries.zipComment;
        }
        return this;
      };
    }, {"./base64":1, "./zipEntries":22}], 11:[function(_dereq_, module, exports) {
      (function(Buffer) {
        module.exports = function(data, encoding) {
          return new Buffer(data, encoding);
        };
        module.exports.test = function(b) {
          return Buffer.isBuffer(b);
        };
      }).call(this, typeof Buffer !== "undefined" ? Buffer : undefined);
    }, {}], 12:[function(_dereq_, module, exports) {
      var Uint8ArrayReader = _dereq_("./uint8ArrayReader");
      function NodeBufferReader(data) {
        this.data = data;
        this.length = this.data.length;
        this.index = 0;
      }
      NodeBufferReader.prototype = new Uint8ArrayReader;
      NodeBufferReader.prototype.readData = function(size) {
        this.checkOffset(size);
        var result = this.data.slice(this.index, this.index + size);
        this.index += size;
        return result;
      };
      module.exports = NodeBufferReader;
    }, {"./uint8ArrayReader":18}], 13:[function(_dereq_, module, exports) {
      var support = _dereq_("./support");
      var utils = _dereq_("./utils");
      var crc32 = _dereq_("./crc32");
      var signature = _dereq_("./signature");
      var defaults = _dereq_("./defaults");
      var base64 = _dereq_("./base64");
      var compressions = _dereq_("./compressions");
      var CompressedObject = _dereq_("./compressedObject");
      var nodeBuffer = _dereq_("./nodeBuffer");
      var utf8 = _dereq_("./utf8");
      var StringWriter = _dereq_("./stringWriter");
      var Uint8ArrayWriter = _dereq_("./uint8ArrayWriter");
      var getRawData = function(file) {
        if (file._data instanceof CompressedObject) {
          file._data = file._data.getContent();
          file.options.binary = true;
          file.options.base64 = false;
          if (utils.getTypeOf(file._data) === "uint8array") {
            var copy = file._data;
            file._data = new Uint8Array(copy.length);
            if (copy.length !== 0) {
              file._data.set(copy, 0);
            }
          }
        }
        return file._data;
      };
      var getBinaryData = function(file) {
        var result = getRawData(file), type = utils.getTypeOf(result);
        if (type === "string") {
          if (!file.options.binary) {
            if (support.nodebuffer) {
              return nodeBuffer(result, "utf-8");
            }
          }
          return file.asBinary();
        }
        return result;
      };
      var dataToString = function(asUTF8) {
        var result = getRawData(this);
        if (result === null || typeof result === "undefined") {
          return "";
        }
        if (this.options.base64) {
          result = base64.decode(result);
        }
        if (asUTF8 && this.options.binary) {
          result = out.utf8decode(result);
        } else {
          result = utils.transformTo("string", result);
        }
        if (!asUTF8 && !this.options.binary) {
          result = utils.transformTo("string", out.utf8encode(result));
        }
        return result;
      };
      var ZipObject = function(name, data, options) {
        this.name = name;
        this.dir = options.dir;
        this.date = options.date;
        this.comment = options.comment;
        this._data = data;
        this.options = options;
        this._initialMetadata = {dir:options.dir, date:options.date};
      };
      ZipObject.prototype = {asText:function() {
        return dataToString.call(this, true);
      }, asBinary:function() {
        return dataToString.call(this, false);
      }, asNodeBuffer:function() {
        var result = getBinaryData(this);
        return utils.transformTo("nodebuffer", result);
      }, asUint8Array:function() {
        var result = getBinaryData(this);
        return utils.transformTo("uint8array", result);
      }, asArrayBuffer:function() {
        return this.asUint8Array().buffer;
      }};
      var decToHex = function(dec, bytes) {
        var hex = "", i;
        for (i = 0;i < bytes;i++) {
          hex += String.fromCharCode(dec & 255);
          dec = dec >>> 8;
        }
        return hex;
      };
      var extend = function() {
        var result = {}, i, attr;
        for (i = 0;i < arguments.length;i++) {
          for (attr in arguments[i]) {
            if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
              result[attr] = arguments[i][attr];
            }
          }
        }
        return result;
      };
      var prepareFileAttrs = function(o) {
        o = o || {};
        if (o.base64 === true && (o.binary === null || o.binary === undefined)) {
          o.binary = true;
        }
        o = extend(o, defaults);
        o.date = o.date || new Date;
        if (o.compression !== null) {
          o.compression = o.compression.toUpperCase();
        }
        return o;
      };
      var fileAdd = function(name, data, o) {
        var dataType = utils.getTypeOf(data), parent;
        o = prepareFileAttrs(o);
        if (o.createFolders && (parent = parentFolder(name))) {
          folderAdd.call(this, parent, true);
        }
        if (o.dir || data === null || typeof data === "undefined") {
          o.base64 = false;
          o.binary = false;
          data = null;
        } else {
          if (dataType === "string") {
            if (o.binary && !o.base64) {
              if (o.optimizedBinaryString !== true) {
                data = utils.string2binary(data);
              }
            }
          } else {
            o.base64 = false;
            o.binary = true;
            if (!dataType && !(data instanceof CompressedObject)) {
              throw new Error("The data of '" + name + "' is in an unsupported format !");
            }
            if (dataType === "arraybuffer") {
              data = utils.transformTo("uint8array", data);
            }
          }
        }
        var object = new ZipObject(name, data, o);
        this.files[name] = object;
        return object;
      };
      var parentFolder = function(path) {
        if (path.slice(-1) == "/") {
          path = path.substring(0, path.length - 1);
        }
        var lastSlash = path.lastIndexOf("/");
        return lastSlash > 0 ? path.substring(0, lastSlash) : "";
      };
      var folderAdd = function(name, createFolders) {
        if (name.slice(-1) != "/") {
          name += "/";
        }
        createFolders = typeof createFolders !== "undefined" ? createFolders : false;
        if (!this.files[name]) {
          fileAdd.call(this, name, null, {dir:true, createFolders:createFolders});
        }
        return this.files[name];
      };
      var generateCompressedObjectFrom = function(file, compression) {
        var result = new CompressedObject, content;
        if (file._data instanceof CompressedObject) {
          result.uncompressedSize = file._data.uncompressedSize;
          result.crc32 = file._data.crc32;
          if (result.uncompressedSize === 0 || file.dir) {
            compression = compressions["STORE"];
            result.compressedContent = "";
            result.crc32 = 0;
          } else {
            if (file._data.compressionMethod === compression.magic) {
              result.compressedContent = file._data.getCompressedContent();
            } else {
              content = file._data.getContent();
              result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content));
            }
          }
        } else {
          content = getBinaryData(file);
          if (!content || content.length === 0 || file.dir) {
            compression = compressions["STORE"];
            content = "";
          }
          result.uncompressedSize = content.length;
          result.crc32 = crc32(content);
          result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content));
        }
        result.compressedSize = result.compressedContent.length;
        result.compressionMethod = compression.magic;
        return result;
      };
      var generateZipParts = function(name, file, compressedObject, offset) {
        var data = compressedObject.compressedContent, utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), comment = file.comment || "", utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)), useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, useUTF8ForComment = utfEncodedComment.length !== comment.length, o = file.options, dosTime, dosDate, extraFields = "", unicodePathExtraField = "", unicodeCommentExtraField = "", dir, date;
        if (file._initialMetadata.dir !== file.dir) {
          dir = file.dir;
        } else {
          dir = o.dir;
        }
        if (file._initialMetadata.date !== file.date) {
          date = file.date;
        } else {
          date = o.date;
        }
        dosTime = date.getHours();
        dosTime = dosTime << 6;
        dosTime = dosTime | date.getMinutes();
        dosTime = dosTime << 5;
        dosTime = dosTime | date.getSeconds() / 2;
        dosDate = date.getFullYear() - 1980;
        dosDate = dosDate << 4;
        dosDate = dosDate | date.getMonth() + 1;
        dosDate = dosDate << 5;
        dosDate = dosDate | date.getDate();
        if (useUTF8ForFileName) {
          unicodePathExtraField = decToHex(1, 1) + decToHex(crc32(utfEncodedFileName), 4) + utfEncodedFileName;
          extraFields += "up" + decToHex(unicodePathExtraField.length, 2) + unicodePathExtraField;
        }
        if (useUTF8ForComment) {
          unicodeCommentExtraField = decToHex(1, 1) + decToHex(this.crc32(utfEncodedComment), 4) + utfEncodedComment;
          extraFields += "uc" + decToHex(unicodeCommentExtraField.length, 2) + unicodeCommentExtraField;
        }
        var header = "";
        header += "\n\x00";
        header += useUTF8ForFileName || useUTF8ForComment ? "\x00\b" : "\x00\x00";
        header += compressedObject.compressionMethod;
        header += decToHex(dosTime, 2);
        header += decToHex(dosDate, 2);
        header += decToHex(compressedObject.crc32, 4);
        header += decToHex(compressedObject.compressedSize, 4);
        header += decToHex(compressedObject.uncompressedSize, 4);
        header += decToHex(utfEncodedFileName.length, 2);
        header += decToHex(extraFields.length, 2);
        var fileRecord = signature.LOCAL_FILE_HEADER + header + utfEncodedFileName + extraFields;
        var dirRecord = signature.CENTRAL_FILE_HEADER + "\u0014\x00" + header + decToHex(utfEncodedComment.length, 2) + "\x00\x00" + "\x00\x00" + (dir === true ? "\u0010\x00\x00\x00" : "\x00\x00\x00\x00") + decToHex(offset, 4) + utfEncodedFileName + extraFields + utfEncodedComment;
        return {fileRecord:fileRecord, dirRecord:dirRecord, compressedObject:compressedObject};
      };
      var out = {load:function(stream, options) {
        throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
      }, filter:function(search) {
        var result = [], filename, relativePath, file, fileClone;
        for (filename in this.files) {
          if (!this.files.hasOwnProperty(filename)) {
            continue;
          }
          file = this.files[filename];
          fileClone = new ZipObject(file.name, file._data, extend(file.options));
          relativePath = filename.slice(this.root.length, filename.length);
          if (filename.slice(0, this.root.length) === this.root && search(relativePath, fileClone)) {
            result.push(fileClone);
          }
        }
        return result;
      }, file:function(name, data, o) {
        if (arguments.length === 1) {
          if (utils.isRegExp(name)) {
            var regexp = name;
            return this.filter(function(relativePath, file) {
              return !file.dir && regexp.test(relativePath);
            });
          } else {
            return this.filter(function(relativePath, file) {
              return !file.dir && relativePath === name;
            })[0] || null;
          }
        } else {
          name = this.root + name;
          fileAdd.call(this, name, data, o);
        }
        return this;
      }, folder:function(arg) {
        if (!arg) {
          return this;
        }
        if (utils.isRegExp(arg)) {
          return this.filter(function(relativePath, file) {
            return file.dir && arg.test(relativePath);
          });
        }
        var name = this.root + arg;
        var newFolder = folderAdd.call(this, name);
        var ret = this.clone();
        ret.root = newFolder.name;
        return ret;
      }, remove:function(name) {
        name = this.root + name;
        var file = this.files[name];
        if (!file) {
          if (name.slice(-1) != "/") {
            name += "/";
          }
          file = this.files[name];
        }
        if (file && !file.dir) {
          delete this.files[name];
        } else {
          var kids = this.filter(function(relativePath, file) {
            return file.name.slice(0, name.length) === name;
          });
          for (var i = 0;i < kids.length;i++) {
            delete this.files[kids[i].name];
          }
        }
        return this;
      }, generate:function(options) {
        options = extend(options || {}, {base64:true, compression:"STORE", type:"base64", comment:null});
        utils.checkSupport(options.type);
        var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i, utfEncodedComment = utils.transformTo("string", this.utf8encode(options.comment || this.comment || ""));
        for (var name in this.files) {
          if (!this.files.hasOwnProperty(name)) {
            continue;
          }
          var file = this.files[name];
          var compressionName = file.options.compression || options.compression.toUpperCase();
          var compression = compressions[compressionName];
          if (!compression) {
            throw new Error(compressionName + " is not a valid compression method !");
          }
          var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
          var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength);
          localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
          centralDirLength += zipPart.dirRecord.length;
          zipData.push(zipPart);
        }
        var dirEnd = "";
        dirEnd = signature.CENTRAL_DIRECTORY_END + "\x00\x00" + "\x00\x00" + decToHex(zipData.length, 2) + decToHex(zipData.length, 2) + decToHex(centralDirLength, 4) + decToHex(localDirLength, 4) + decToHex(utfEncodedComment.length, 2) + utfEncodedComment;
        var typeName = options.type.toLowerCase();
        if (typeName === "uint8array" || typeName === "arraybuffer" || typeName === "blob" || typeName === "nodebuffer") {
          writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
        } else {
          writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
        }
        for (i = 0;i < zipData.length;i++) {
          writer.append(zipData[i].fileRecord);
          writer.append(zipData[i].compressedObject.compressedContent);
        }
        for (i = 0;i < zipData.length;i++) {
          writer.append(zipData[i].dirRecord);
        }
        writer.append(dirEnd);
        var zip = writer.finalize();
        switch(options.type.toLowerCase()) {
          case "uint8array":
          ;
          case "arraybuffer":
          ;
          case "nodebuffer":
            return utils.transformTo(options.type.toLowerCase(), zip);
          case "blob":
            return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip));
          case "base64":
            return options.base64 ? base64.encode(zip) : zip;
          default:
            return zip;
        }
      }, crc32:function(input, crc) {
        return crc32(input, crc);
      }, utf8encode:function(string) {
        return utils.transformTo("string", utf8.utf8encode(string));
      }, utf8decode:function(input) {
        return utf8.utf8decode(input);
      }};
      module.exports = out;
    }, {"./base64":1, "./compressedObject":2, "./compressions":3, "./crc32":4, "./defaults":6, "./nodeBuffer":11, "./signature":14, "./stringWriter":16, "./support":17, "./uint8ArrayWriter":19, "./utf8":20, "./utils":21}], 14:[function(_dereq_, module, exports) {
      exports.LOCAL_FILE_HEADER = "PK\u0003\u0004";
      exports.CENTRAL_FILE_HEADER = "PK\u0001\u0002";
      exports.CENTRAL_DIRECTORY_END = "PK\u0005\u0006";
      exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\u0006\u0007";
      exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\u0006\u0006";
      exports.DATA_DESCRIPTOR = "PK\u0007\b";
    }, {}], 15:[function(_dereq_, module, exports) {
      var DataReader = _dereq_("./dataReader");
      var utils = _dereq_("./utils");
      function StringReader(data, optimizedBinaryString) {
        this.data = data;
        if (!optimizedBinaryString) {
          this.data = utils.string2binary(this.data);
        }
        this.length = this.data.length;
        this.index = 0;
      }
      StringReader.prototype = new DataReader;
      StringReader.prototype.byteAt = function(i) {
        return this.data.charCodeAt(i);
      };
      StringReader.prototype.lastIndexOfSignature = function(sig) {
        return this.data.lastIndexOf(sig);
      };
      StringReader.prototype.readData = function(size) {
        this.checkOffset(size);
        var result = this.data.slice(this.index, this.index + size);
        this.index += size;
        return result;
      };
      module.exports = StringReader;
    }, {"./dataReader":5, "./utils":21}], 16:[function(_dereq_, module, exports) {
      var utils = _dereq_("./utils");
      var StringWriter = function() {
        this.data = [];
      };
      StringWriter.prototype = {append:function(input) {
        input = utils.transformTo("string", input);
        this.data.push(input);
      }, finalize:function() {
        return this.data.join("");
      }};
      module.exports = StringWriter;
    }, {"./utils":21}], 17:[function(_dereq_, module, exports) {
      (function(Buffer) {
        exports.base64 = true;
        exports.array = true;
        exports.string = true;
        exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
        exports.nodebuffer = typeof Buffer !== "undefined";
        exports.uint8array = typeof Uint8Array !== "undefined";
        if (typeof ArrayBuffer === "undefined") {
          exports.blob = false;
        } else {
          var buffer = new ArrayBuffer(0);
          try {
            exports.blob = (new Blob([buffer], {type:"application/zip"})).size === 0;
          } catch (e) {
            try {
              var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
              var builder = new Builder;
              builder.append(buffer);
              exports.blob = builder.getBlob("application/zip").size === 0;
            } catch (e) {
              exports.blob = false;
            }
          }
        }
      }).call(this, typeof Buffer !== "undefined" ? Buffer : undefined);
    }, {}], 18:[function(_dereq_, module, exports) {
      var DataReader = _dereq_("./dataReader");
      function Uint8ArrayReader(data) {
        if (data) {
          this.data = data;
          this.length = this.data.length;
          this.index = 0;
        }
      }
      Uint8ArrayReader.prototype = new DataReader;
      Uint8ArrayReader.prototype.byteAt = function(i) {
        return this.data[i];
      };
      Uint8ArrayReader.prototype.lastIndexOfSignature = function(sig) {
        var sig0 = sig.charCodeAt(0), sig1 = sig.charCodeAt(1), sig2 = sig.charCodeAt(2), sig3 = sig.charCodeAt(3);
        for (var i = this.length - 4;i >= 0;--i) {
          if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) {
            return i;
          }
        }
        return -1;
      };
      Uint8ArrayReader.prototype.readData = function(size) {
        this.checkOffset(size);
        if (size === 0) {
          return new Uint8Array(0);
        }
        var result = this.data.subarray(this.index, this.index + size);
        this.index += size;
        return result;
      };
      module.exports = Uint8ArrayReader;
    }, {"./dataReader":5}], 19:[function(_dereq_, module, exports) {
      var utils = _dereq_("./utils");
      var Uint8ArrayWriter = function(length) {
        this.data = new Uint8Array(length);
        this.index = 0;
      };
      Uint8ArrayWriter.prototype = {append:function(input) {
        if (input.length !== 0) {
          input = utils.transformTo("uint8array", input);
          this.data.set(input, this.index);
          this.index += input.length;
        }
      }, finalize:function() {
        return this.data;
      }};
      module.exports = Uint8ArrayWriter;
    }, {"./utils":21}], 20:[function(_dereq_, module, exports) {
      var utils = _dereq_("./utils");
      var support = _dereq_("./support");
      var nodeBuffer = _dereq_("./nodeBuffer");
      var _utf8len = new Array(256);
      for (var i = 0;i < 256;i++) {
        _utf8len[i] = i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1;
      }
      _utf8len[254] = _utf8len[254] = 1;
      var string2buf = function(str) {
        var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;
        for (m_pos = 0;m_pos < str_len;m_pos++) {
          c = str.charCodeAt(m_pos);
          if ((c & 64512) === 55296 && m_pos + 1 < str_len) {
            c2 = str.charCodeAt(m_pos + 1);
            if ((c2 & 64512) === 56320) {
              c = 65536 + (c - 55296 << 10) + (c2 - 56320);
              m_pos++;
            }
          }
          buf_len += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
        }
        if (support.uint8array) {
          buf = new Uint8Array(buf_len);
        } else {
          buf = new Array(buf_len);
        }
        for (i = 0, m_pos = 0;i < buf_len;m_pos++) {
          c = str.charCodeAt(m_pos);
          if ((c & 64512) === 55296 && m_pos + 1 < str_len) {
            c2 = str.charCodeAt(m_pos + 1);
            if ((c2 & 64512) === 56320) {
              c = 65536 + (c - 55296 << 10) + (c2 - 56320);
              m_pos++;
            }
          }
          if (c < 128) {
            buf[i++] = c;
          } else {
            if (c < 2048) {
              buf[i++] = 192 | c >>> 6;
              buf[i++] = 128 | c & 63;
            } else {
              if (c < 65536) {
                buf[i++] = 224 | c >>> 12;
                buf[i++] = 128 | c >>> 6 & 63;
                buf[i++] = 128 | c & 63;
              } else {
                buf[i++] = 240 | c >>> 18;
                buf[i++] = 128 | c >>> 12 & 63;
                buf[i++] = 128 | c >>> 6 & 63;
                buf[i++] = 128 | c & 63;
              }
            }
          }
        }
        return buf;
      };
      var utf8border = function(buf, max) {
        var pos;
        max = max || buf.length;
        if (max > buf.length) {
          max = buf.length;
        }
        pos = max - 1;
        while (pos >= 0 && (buf[pos] & 192) === 128) {
          pos--;
        }
        if (pos < 0) {
          return max;
        }
        if (pos === 0) {
          return max;
        }
        return pos + _utf8len[buf[pos]] > max ? pos : max;
      };
      var buf2string = function(buf) {
        var str, i, out, c, c_len;
        var len = buf.length;
        var utf16buf = new Array(len * 2);
        for (out = 0, i = 0;i < len;) {
          c = buf[i++];
          if (c < 128) {
            utf16buf[out++] = c;
            continue;
          }
          c_len = _utf8len[c];
          if (c_len > 4) {
            utf16buf[out++] = 65533;
            i += c_len - 1;
            continue;
          }
          c &= c_len === 2 ? 31 : c_len === 3 ? 15 : 7;
          while (c_len > 1 && i < len) {
            c = c << 6 | buf[i++] & 63;
            c_len--;
          }
          if (c_len > 1) {
            utf16buf[out++] = 65533;
            continue;
          }
          if (c < 65536) {
            utf16buf[out++] = c;
          } else {
            c -= 65536;
            utf16buf[out++] = 55296 | c >> 10 & 1023;
            utf16buf[out++] = 56320 | c & 1023;
          }
        }
        if (utf16buf.length !== out) {
          if (utf16buf.subarray) {
            utf16buf = utf16buf.subarray(0, out);
          } else {
            utf16buf.length = out;
          }
        }
        return utils.applyFromCharCode(utf16buf);
      };
      exports.utf8encode = function utf8encode(str) {
        if (support.nodebuffer) {
          return nodeBuffer(str, "utf-8");
        }
        return string2buf(str);
      };
      exports.utf8decode = function utf8decode(buf) {
        if (support.nodebuffer) {
          return utils.transformTo("nodebuffer", buf).toString("utf-8");
        }
        buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf);
        var result = [], k = 0, len = buf.length, chunk = 65536;
        while (k < len) {
          var nextBoundary = utf8border(buf, Math.min(k + chunk, len));
          if (support.uint8array) {
            result.push(buf2string(buf.subarray(k, nextBoundary)));
          } else {
            result.push(buf2string(buf.slice(k, nextBoundary)));
          }
          k = nextBoundary;
        }
        return result.join("");
      };
    }, {"./nodeBuffer":11, "./support":17, "./utils":21}], 21:[function(_dereq_, module, exports) {
      var support = _dereq_("./support");
      var compressions = _dereq_("./compressions");
      var nodeBuffer = _dereq_("./nodeBuffer");
      exports.string2binary = function(str) {
        var result = "";
        for (var i = 0;i < str.length;i++) {
          result += String.fromCharCode(str.charCodeAt(i) & 255);
        }
        return result;
      };
      exports.arrayBuffer2Blob = function(buffer) {
        exports.checkSupport("blob");
        try {
          return new Blob([buffer], {type:"application/zip"});
        } catch (e) {
          try {
            var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
            var builder = new Builder;
            builder.append(buffer);
            return builder.getBlob("application/zip");
          } catch (e) {
            throw new Error("Bug : can't construct the Blob.");
          }
        }
      };
      function identity(input) {
        return input;
      }
      function stringToArrayLike(str, array) {
        for (var i = 0;i < str.length;++i) {
          array[i] = str.charCodeAt(i) & 255;
        }
        return array;
      }
      function arrayLikeToString(array) {
        var chunk = 65536;
        var result = [], len = array.length, type = exports.getTypeOf(array), k = 0, canUseApply = true;
        try {
          switch(type) {
            case "uint8array":
              String.fromCharCode.apply(null, new Uint8Array(0));
              break;
            case "nodebuffer":
              String.fromCharCode.apply(null, nodeBuffer(0));
              break;
          }
        } catch (e) {
          canUseApply = false;
        }
        if (!canUseApply) {
          var resultStr = "";
          for (var i = 0;i < array.length;i++) {
            resultStr += String.fromCharCode(array[i]);
          }
          return resultStr;
        }
        while (k < len && chunk > 1) {
          try {
            if (type === "array" || type === "nodebuffer") {
              result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len))));
            } else {
              result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len))));
            }
            k += chunk;
          } catch (e) {
            chunk = Math.floor(chunk / 2);
          }
        }
        return result.join("");
      }
      exports.applyFromCharCode = arrayLikeToString;
      function arrayLikeToArrayLike(arrayFrom, arrayTo) {
        for (var i = 0;i < arrayFrom.length;i++) {
          arrayTo[i] = arrayFrom[i];
        }
        return arrayTo;
      }
      var transform = {};
      transform["string"] = {"string":identity, "array":function(input) {
        return stringToArrayLike(input, new Array(input.length));
      }, "arraybuffer":function(input) {
        return transform["string"]["uint8array"](input).buffer;
      }, "uint8array":function(input) {
        return stringToArrayLike(input, new Uint8Array(input.length));
      }, "nodebuffer":function(input) {
        return stringToArrayLike(input, nodeBuffer(input.length));
      }};
      transform["array"] = {"string":arrayLikeToString, "array":identity, "arraybuffer":function(input) {
        return (new Uint8Array(input)).buffer;
      }, "uint8array":function(input) {
        return new Uint8Array(input);
      }, "nodebuffer":function(input) {
        return nodeBuffer(input);
      }};
      transform["arraybuffer"] = {"string":function(input) {
        return arrayLikeToString(new Uint8Array(input));
      }, "array":function(input) {
        return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
      }, "arraybuffer":identity, "uint8array":function(input) {
        return new Uint8Array(input);
      }, "nodebuffer":function(input) {
        return nodeBuffer(new Uint8Array(input));
      }};
      transform["uint8array"] = {"string":arrayLikeToString, "array":function(input) {
        return arrayLikeToArrayLike(input, new Array(input.length));
      }, "arraybuffer":function(input) {
        return input.buffer;
      }, "uint8array":identity, "nodebuffer":function(input) {
        return nodeBuffer(input);
      }};
      transform["nodebuffer"] = {"string":arrayLikeToString, "array":function(input) {
        return arrayLikeToArrayLike(input, new Array(input.length));
      }, "arraybuffer":function(input) {
        return transform["nodebuffer"]["uint8array"](input).buffer;
      }, "uint8array":function(input) {
        return arrayLikeToArrayLike(input, new Uint8Array(input.length));
      }, "nodebuffer":identity};
      exports.transformTo = function(outputType, input) {
        if (!input) {
          input = "";
        }
        if (!outputType) {
          return input;
        }
        exports.checkSupport(outputType);
        var inputType = exports.getTypeOf(input);
        var result = transform[inputType][outputType](input);
        return result;
      };
      exports.getTypeOf = function(input) {
        if (typeof input === "string") {
          return "string";
        }
        if (Object.prototype.toString.call(input) === "[object Array]") {
          return "array";
        }
        if (support.nodebuffer && nodeBuffer.test(input)) {
          return "nodebuffer";
        }
        if (support.uint8array && input instanceof Uint8Array) {
          return "uint8array";
        }
        if (support.arraybuffer && input instanceof ArrayBuffer) {
          return "arraybuffer";
        }
      };
      exports.checkSupport = function(type) {
        var supported = support[type.toLowerCase()];
        if (!supported) {
          throw new Error(type + " is not supported by this browser");
        }
      };
      exports.MAX_VALUE_16BITS = 65535;
      exports.MAX_VALUE_32BITS = -1;
      exports.pretty = function(str) {
        var res = "", code, i;
        for (i = 0;i < (str || "").length;i++) {
          code = str.charCodeAt(i);
          res += "\\x" + (code < 16 ? "0" : "") + code.toString(16).toUpperCase();
        }
        return res;
      };
      exports.findCompression = function(compressionMethod) {
        for (var method in compressions) {
          if (!compressions.hasOwnProperty(method)) {
            continue;
          }
          if (compressions[method].magic === compressionMethod) {
            return compressions[method];
          }
        }
        return null;
      };
      exports.isRegExp = function(object) {
        return Object.prototype.toString.call(object) === "[object RegExp]";
      };
    }, {"./compressions":3, "./nodeBuffer":11, "./support":17}], 22:[function(_dereq_, module, exports) {
      var StringReader = _dereq_("./stringReader");
      var NodeBufferReader = _dereq_("./nodeBufferReader");
      var Uint8ArrayReader = _dereq_("./uint8ArrayReader");
      var utils = _dereq_("./utils");
      var sig = _dereq_("./signature");
      var ZipEntry = _dereq_("./zipEntry");
      var support = _dereq_("./support");
      var jszipProto = _dereq_("./object");
      function ZipEntries(data, loadOptions) {
        this.files = [];
        this.loadOptions = loadOptions;
        if (data) {
          this.load(data);
        }
      }
      ZipEntries.prototype = {checkSignature:function(expectedSignature) {
        var signature = this.reader.readString(4);
        if (signature !== expectedSignature) {
          throw new Error("Corrupted zip or bug : unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")");
        }
      }, readBlockEndOfCentral:function() {
        this.diskNumber = this.reader.readInt(2);
        this.diskWithCentralDirStart = this.reader.readInt(2);
        this.centralDirRecordsOnThisDisk = this.reader.readInt(2);
        this.centralDirRecords = this.reader.readInt(2);
        this.centralDirSize = this.reader.readInt(4);
        this.centralDirOffset = this.reader.readInt(4);
        this.zipCommentLength = this.reader.readInt(2);
        this.zipComment = this.reader.readString(this.zipCommentLength);
        this.zipComment = jszipProto.utf8decode(this.zipComment);
      }, readBlockZip64EndOfCentral:function() {
        this.zip64EndOfCentralSize = this.reader.readInt(8);
        this.versionMadeBy = this.reader.readString(2);
        this.versionNeeded = this.reader.readInt(2);
        this.diskNumber = this.reader.readInt(4);
        this.diskWithCentralDirStart = this.reader.readInt(4);
        this.centralDirRecordsOnThisDisk = this.reader.readInt(8);
        this.centralDirRecords = this.reader.readInt(8);
        this.centralDirSize = this.reader.readInt(8);
        this.centralDirOffset = this.reader.readInt(8);
        this.zip64ExtensibleData = {};
        var extraDataSize = this.zip64EndOfCentralSize - 44, index = 0, extraFieldId, extraFieldLength, extraFieldValue;
        while (index < extraDataSize) {
          extraFieldId = this.reader.readInt(2);
          extraFieldLength = this.reader.readInt(4);
          extraFieldValue = this.reader.readString(extraFieldLength);
          this.zip64ExtensibleData[extraFieldId] = {id:extraFieldId, length:extraFieldLength, value:extraFieldValue};
        }
      }, readBlockZip64EndOfCentralLocator:function() {
        this.diskWithZip64CentralDirStart = this.reader.readInt(4);
        this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8);
        this.disksCount = this.reader.readInt(4);
        if (this.disksCount > 1) {
          throw new Error("Multi-volumes zip are not supported");
        }
      }, readLocalFiles:function() {
        var i, file;
        for (i = 0;i < this.files.length;i++) {
          file = this.files[i];
          this.reader.setIndex(file.localHeaderOffset);
          this.checkSignature(sig.LOCAL_FILE_HEADER);
          file.readLocalPart(this.reader);
          file.handleUTF8();
        }
      }, readCentralDir:function() {
        var file;
        this.reader.setIndex(this.centralDirOffset);
        while (this.reader.readString(4) === sig.CENTRAL_FILE_HEADER) {
          file = new ZipEntry({zip64:this.zip64}, this.loadOptions);
          file.readCentralPart(this.reader);
          this.files.push(file);
        }
      }, readEndOfCentral:function() {
        var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END);
        if (offset === -1) {
          throw new Error("Corrupted zip : can't find end of central directory");
        }
        this.reader.setIndex(offset);
        this.checkSignature(sig.CENTRAL_DIRECTORY_END);
        this.readBlockEndOfCentral();
        if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) {
          this.zip64 = true;
          offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
          if (offset === -1) {
            throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");
          }
          this.reader.setIndex(offset);
          this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
          this.readBlockZip64EndOfCentralLocator();
          this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
          this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
          this.readBlockZip64EndOfCentral();
        }
      }, prepareReader:function(data) {
        var type = utils.getTypeOf(data);
        if (type === "string" && !support.uint8array) {
          this.reader = new StringReader(data, this.loadOptions.optimizedBinaryString);
        } else {
          if (type === "nodebuffer") {
            this.reader = new NodeBufferReader(data);
          } else {
            this.reader = new Uint8ArrayReader(utils.transformTo("uint8array", data));
          }
        }
      }, load:function(data) {
        this.prepareReader(data);
        this.readEndOfCentral();
        this.readCentralDir();
        this.readLocalFiles();
      }};
      module.exports = ZipEntries;
    }, {"./nodeBufferReader":12, "./object":13, "./signature":14, "./stringReader":15, "./support":17, "./uint8ArrayReader":18, "./utils":21, "./zipEntry":23}], 23:[function(_dereq_, module, exports) {
      var StringReader = _dereq_("./stringReader");
      var utils = _dereq_("./utils");
      var CompressedObject = _dereq_("./compressedObject");
      var jszipProto = _dereq_("./object");
      function ZipEntry(options, loadOptions) {
        this.options = options;
        this.loadOptions = loadOptions;
      }
      ZipEntry.prototype = {isEncrypted:function() {
        return (this.bitFlag & 1) === 1;
      }, useUTF8:function() {
        return (this.bitFlag & 2048) === 2048;
      }, prepareCompressedContent:function(reader, from, length) {
        return function() {
          var previousIndex = reader.index;
          reader.setIndex(from);
          var compressedFileData = reader.readData(length);
          reader.setIndex(previousIndex);
          return compressedFileData;
        };
      }, prepareContent:function(reader, from, length, compression, uncompressedSize) {
        return function() {
          var compressedFileData = utils.transformTo(compression.uncompressInputType, this.getCompressedContent());
          var uncompressedFileData = compression.uncompress(compressedFileData);
          if (uncompressedFileData.length !== uncompressedSize) {
            throw new Error("Bug : uncompressed data size mismatch");
          }
          return uncompressedFileData;
        };
      }, readLocalPart:function(reader) {
        var compression, localExtraFieldsLength;
        reader.skip(22);
        this.fileNameLength = reader.readInt(2);
        localExtraFieldsLength = reader.readInt(2);
        this.fileName = reader.readString(this.fileNameLength);
        reader.skip(localExtraFieldsLength);
        if (this.compressedSize == -1 || this.uncompressedSize == -1) {
          throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " + "(compressedSize == -1 || uncompressedSize == -1)");
        }
        compression = utils.findCompression(this.compressionMethod);
        if (compression === null) {
          throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + this.fileName + ")");
        }
        this.decompressed = new CompressedObject;
        this.decompressed.compressedSize = this.compressedSize;
        this.decompressed.uncompressedSize = this.uncompressedSize;
        this.decompressed.crc32 = this.crc32;
        this.decompressed.compressionMethod = this.compressionMethod;
        this.decompressed.getCompressedContent = this.prepareCompressedContent(reader, reader.index, this.compressedSize, compression);
        this.decompressed.getContent = this.prepareContent(reader, reader.index, this.compressedSize, compression, this.uncompressedSize);
        if (this.loadOptions.checkCRC32) {
          this.decompressed = utils.transformTo("string", this.decompressed.getContent());
          if (jszipProto.crc32(this.decompressed) !== this.crc32) {
            throw new Error("Corrupted zip : CRC32 mismatch");
          }
        }
      }, readCentralPart:function(reader) {
        this.versionMadeBy = reader.readString(2);
        this.versionNeeded = reader.readInt(2);
        this.bitFlag = reader.readInt(2);
        this.compressionMethod = reader.readString(2);
        this.date = reader.readDate();
        this.crc32 = reader.readInt(4);
        this.compressedSize = reader.readInt(4);
        this.uncompressedSize = reader.readInt(4);
        this.fileNameLength = reader.readInt(2);
        this.extraFieldsLength = reader.readInt(2);
        this.fileCommentLength = reader.readInt(2);
        this.diskNumberStart = reader.readInt(2);
        this.internalFileAttributes = reader.readInt(2);
        this.externalFileAttributes = reader.readInt(4);
        this.localHeaderOffset = reader.readInt(4);
        if (this.isEncrypted()) {
          throw new Error("Encrypted zip are not supported");
        }
        this.fileName = reader.readString(this.fileNameLength);
        this.readExtraFields(reader);
        this.parseZIP64ExtraField(reader);
        this.fileComment = reader.readString(this.fileCommentLength);
        this.dir = this.externalFileAttributes & 16 ? true : false;
      }, parseZIP64ExtraField:function(reader) {
        if (!this.extraFields[1]) {
          return;
        }
        var extraReader = new StringReader(this.extraFields[1].value);
        if (this.uncompressedSize === utils.MAX_VALUE_32BITS) {
          this.uncompressedSize = extraReader.readInt(8);
        }
        if (this.compressedSize === utils.MAX_VALUE_32BITS) {
          this.compressedSize = extraReader.readInt(8);
        }
        if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) {
          this.localHeaderOffset = extraReader.readInt(8);
        }
        if (this.diskNumberStart === utils.MAX_VALUE_32BITS) {
          this.diskNumberStart = extraReader.readInt(4);
        }
      }, readExtraFields:function(reader) {
        var start = reader.index, extraFieldId, extraFieldLength, extraFieldValue;
        this.extraFields = this.extraFields || {};
        while (reader.index < start + this.extraFieldsLength) {
          extraFieldId = reader.readInt(2);
          extraFieldLength = reader.readInt(2);
          extraFieldValue = reader.readString(extraFieldLength);
          this.extraFields[extraFieldId] = {id:extraFieldId, length:extraFieldLength, value:extraFieldValue};
        }
      }, handleUTF8:function() {
        if (this.useUTF8()) {
          this.fileName = jszipProto.utf8decode(this.fileName);
          this.fileComment = jszipProto.utf8decode(this.fileComment);
        } else {
          var upath = this.findExtraFieldUnicodePath();
          if (upath !== null) {
            this.fileName = upath;
          }
          var ucomment = this.findExtraFieldUnicodeComment();
          if (ucomment !== null) {
            this.fileComment = ucomment;
          }
        }
      }, findExtraFieldUnicodePath:function() {
        var upathField = this.extraFields[28789];
        if (upathField) {
          var extraReader = new StringReader(upathField.value);
          if (extraReader.readInt(1) !== 1) {
            return null;
          }
          if (jszipProto.crc32(this.fileName) !== extraReader.readInt(4)) {
            return null;
          }
          return jszipProto.utf8decode(extraReader.readString(upathField.length - 5));
        }
        return null;
      }, findExtraFieldUnicodeComment:function() {
        var ucommentField = this.extraFields[25461];
        if (ucommentField) {
          var extraReader = new StringReader(ucommentField.value);
          if (extraReader.readInt(1) !== 1) {
            return null;
          }
          if (jszipProto.crc32(this.fileComment) !== extraReader.readInt(4)) {
            return null;
          }
          return jszipProto.utf8decode(extraReader.readString(ucommentField.length - 5));
        }
        return null;
      }};
      module.exports = ZipEntry;
    }, {"./compressedObject":2, "./object":13, "./stringReader":15, "./utils":21}], 24:[function(_dereq_, module, exports) {
      var assign = _dereq_("./lib/utils/common").assign;
      var deflate = _dereq_("./lib/deflate");
      var inflate = _dereq_("./lib/inflate");
      var constants = _dereq_("./lib/zlib/constants");
      var pako = {};
      assign(pako, deflate, inflate, constants);
      module.exports = pako;
    }, {"./lib/deflate":25, "./lib/inflate":26, "./lib/utils/common":27, "./lib/zlib/constants":30}], 25:[function(_dereq_, module, exports) {
      var zlib_deflate = _dereq_("./zlib/deflate.js");
      var utils = _dereq_("./utils/common");
      var strings = _dereq_("./utils/strings");
      var msg = _dereq_("./zlib/messages");
      var zstream = _dereq_("./zlib/zstream");
      var Z_NO_FLUSH = 0;
      var Z_FINISH = 4;
      var Z_OK = 0;
      var Z_STREAM_END = 1;
      var Z_DEFAULT_COMPRESSION = -1;
      var Z_DEFAULT_STRATEGY = 0;
      var Z_DEFLATED = 8;
      var Deflate = function(options) {
        this.options = utils.assign({level:Z_DEFAULT_COMPRESSION, method:Z_DEFLATED, chunkSize:16384, windowBits:15, memLevel:8, strategy:Z_DEFAULT_STRATEGY, to:""}, options || {});
        var opt = this.options;
        if (opt.raw && opt.windowBits > 0) {
          opt.windowBits = -opt.windowBits;
        } else {
          if (opt.gzip && opt.windowBits > 0 && opt.windowBits < 16) {
            opt.windowBits += 16;
          }
        }
        this.err = 0;
        this.msg = "";
        this.ended = false;
        this.chunks = [];
        this.strm = new zstream;
        this.strm.avail_out = 0;
        var status = zlib_deflate.deflateInit2(this.strm, opt.level, opt.method, opt.windowBits, opt.memLevel, opt.strategy);
        if (status !== Z_OK) {
          throw new Error(msg[status]);
        }
        if (opt.header) {
          zlib_deflate.deflateSetHeader(this.strm, opt.header);
        }
      };
      Deflate.prototype.push = function(data, mode) {
        var strm = this.strm;
        var chunkSize = this.options.chunkSize;
        var status, _mode;
        if (this.ended) {
          return false;
        }
        _mode = mode === ~~mode ? mode : mode === true ? Z_FINISH : Z_NO_FLUSH;
        if (typeof data === "string") {
          strm.input = strings.string2buf(data);
        } else {
          strm.input = data;
        }
        strm.next_in = 0;
        strm.avail_in = strm.input.length;
        do {
          if (strm.avail_out === 0) {
            strm.output = new utils.Buf8(chunkSize);
            strm.next_out = 0;
            strm.avail_out = chunkSize;
          }
          status = zlib_deflate.deflate(strm, _mode);
          if (status !== Z_STREAM_END && status !== Z_OK) {
            this.onEnd(status);
            this.ended = true;
            return false;
          }
          if (strm.avail_out === 0 || strm.avail_in === 0 && _mode === Z_FINISH) {
            if (this.options.to === "string") {
              this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out)));
            } else {
              this.onData(utils.shrinkBuf(strm.output, strm.next_out));
            }
          }
        } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END);
        if (_mode === Z_FINISH) {
          status = zlib_deflate.deflateEnd(this.strm);
          this.onEnd(status);
          this.ended = true;
          return status === Z_OK;
        }
        return true;
      };
      Deflate.prototype.onData = function(chunk) {
        this.chunks.push(chunk);
      };
      Deflate.prototype.onEnd = function(status) {
        if (status === Z_OK) {
          if (this.options.to === "string") {
            this.result = this.chunks.join("");
          } else {
            this.result = utils.flattenChunks(this.chunks);
          }
        }
        this.chunks = [];
        this.err = status;
        this.msg = this.strm.msg;
      };
      function deflate(input, options) {
        var deflator = new Deflate(options);
        deflator.push(input, true);
        if (deflator.err) {
          throw deflator.msg;
        }
        return deflator.result;
      }
      function deflateRaw(input, options) {
        options = options || {};
        options.raw = true;
        return deflate(input, options);
      }
      function gzip(input, options) {
        options = options || {};
        options.gzip = true;
        return deflate(input, options);
      }
      exports.Deflate = Deflate;
      exports.deflate = deflate;
      exports.deflateRaw = deflateRaw;
      exports.gzip = gzip;
    }, {"./utils/common":27, "./utils/strings":28, "./zlib/deflate.js":32, "./zlib/messages":37, "./zlib/zstream":39}], 26:[function(_dereq_, module, exports) {
      var zlib_inflate = _dereq_("./zlib/inflate.js");
      var utils = _dereq_("./utils/common");
      var strings = _dereq_("./utils/strings");
      var c = _dereq_("./zlib/constants");
      var msg = _dereq_("./zlib/messages");
      var zstream = _dereq_("./zlib/zstream");
      var gzheader = _dereq_("./zlib/gzheader");
      var Inflate = function(options) {
        this.options = utils.assign({chunkSize:16384, windowBits:0, to:""}, options || {});
        var opt = this.options;
        if (opt.raw && opt.windowBits >= 0 && opt.windowBits < 16) {
          opt.windowBits = -opt.windowBits;
          if (opt.windowBits === 0) {
            opt.windowBits = -15;
          }
        }
        if (opt.windowBits >= 0 && opt.windowBits < 16 && !(options && options.windowBits)) {
          opt.windowBits += 32;
        }
        if (opt.windowBits > 15 && opt.windowBits < 48) {
          if ((opt.windowBits & 15) === 0) {
            opt.windowBits |= 15;
          }
        }
        this.err = 0;
        this.msg = "";
        this.ended = false;
        this.chunks = [];
        this.strm = new zstream;
        this.strm.avail_out = 0;
        var status = zlib_inflate.inflateInit2(this.strm, opt.windowBits);
        if (status !== c.Z_OK) {
          throw new Error(msg[status]);
        }
        this.header = new gzheader;
        zlib_inflate.inflateGetHeader(this.strm, this.header);
      };
      Inflate.prototype.push = function(data, mode) {
        var strm = this.strm;
        var chunkSize = this.options.chunkSize;
        var status, _mode;
        var next_out_utf8, tail, utf8str;
        if (this.ended) {
          return false;
        }
        _mode = mode === ~~mode ? mode : mode === true ? c.Z_FINISH : c.Z_NO_FLUSH;
        if (typeof data === "string") {
          strm.input = strings.binstring2buf(data);
        } else {
          strm.input = data;
        }
        strm.next_in = 0;
        strm.avail_in = strm.input.length;
        do {
          if (strm.avail_out === 0) {
            strm.output = new utils.Buf8(chunkSize);
            strm.next_out = 0;
            strm.avail_out = chunkSize;
          }
          status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH);
          if (status !== c.Z_STREAM_END && status !== c.Z_OK) {
            this.onEnd(status);
            this.ended = true;
            return false;
          }
          if (strm.next_out) {
            if (strm.avail_out === 0 || status === c.Z_STREAM_END || strm.avail_in === 0 && _mode === c.Z_FINISH) {
              if (this.options.to === "string") {
                next_out_utf8 = strings.utf8border(strm.output, strm.next_out);
                tail = strm.next_out - next_out_utf8;
                utf8str = strings.buf2string(strm.output, next_out_utf8);
                strm.next_out = tail;
                strm.avail_out = chunkSize - tail;
                if (tail) {
                  utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0);
                }
                this.onData(utf8str);
              } else {
                this.onData(utils.shrinkBuf(strm.output, strm.next_out));
              }
            }
          }
        } while (strm.avail_in > 0 && status !== c.Z_STREAM_END);
        if (status === c.Z_STREAM_END) {
          _mode = c.Z_FINISH;
        }
        if (_mode === c.Z_FINISH) {
          status = zlib_inflate.inflateEnd(this.strm);
          this.onEnd(status);
          this.ended = true;
          return status === c.Z_OK;
        }
        return true;
      };
      Inflate.prototype.onData = function(chunk) {
        this.chunks.push(chunk);
      };
      Inflate.prototype.onEnd = function(status) {
        if (status === c.Z_OK) {
          if (this.options.to === "string") {
            this.result = this.chunks.join("");
          } else {
            this.result = utils.flattenChunks(this.chunks);
          }
        }
        this.chunks = [];
        this.err = status;
        this.msg = this.strm.msg;
      };
      function inflate(input, options) {
        var inflator = new Inflate(options);
        inflator.push(input, true);
        if (inflator.err) {
          throw inflator.msg;
        }
        return inflator.result;
      }
      function inflateRaw(input, options) {
        options = options || {};
        options.raw = true;
        return inflate(input, options);
      }
      exports.Inflate = Inflate;
      exports.inflate = inflate;
      exports.inflateRaw = inflateRaw;
      exports.ungzip = inflate;
    }, {"./utils/common":27, "./utils/strings":28, "./zlib/constants":30, "./zlib/gzheader":33, "./zlib/inflate.js":35, "./zlib/messages":37, "./zlib/zstream":39}], 27:[function(_dereq_, module, exports) {
      var TYPED_OK = typeof Uint8Array !== "undefined" && typeof Uint16Array !== "undefined" && typeof Int32Array !== "undefined";
      exports.assign = function(obj) {
        var sources = Array.prototype.slice.call(arguments, 1);
        while (sources.length) {
          var source = sources.shift();
          if (!source) {
            continue;
          }
          if (typeof source !== "object") {
            throw new TypeError(source + "must be non-object");
          }
          for (var p in source) {
            if (source.hasOwnProperty(p)) {
              obj[p] = source[p];
            }
          }
        }
        return obj;
      };
      exports.shrinkBuf = function(buf, size) {
        if (buf.length === size) {
          return buf;
        }
        if (buf.subarray) {
          return buf.subarray(0, size);
        }
        buf.length = size;
        return buf;
      };
      var fnTyped = {arraySet:function(dest, src, src_offs, len, dest_offs) {
        if (src.subarray && dest.subarray) {
          dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
          return;
        }
        for (var i = 0;i < len;i++) {
          dest[dest_offs + i] = src[src_offs + i];
        }
      }, flattenChunks:function(chunks) {
        var i, l, len, pos, chunk, result;
        len = 0;
        for (i = 0, l = chunks.length;i < l;i++) {
          len += chunks[i].length;
        }
        result = new Uint8Array(len);
        pos = 0;
        for (i = 0, l = chunks.length;i < l;i++) {
          chunk = chunks[i];
          result.set(chunk, pos);
          pos += chunk.length;
        }
        return result;
      }};
      var fnUntyped = {arraySet:function(dest, src, src_offs, len, dest_offs) {
        for (var i = 0;i < len;i++) {
          dest[dest_offs + i] = src[src_offs + i];
        }
      }, flattenChunks:function(chunks) {
        return [].concat.apply([], chunks);
      }};
      exports.setTyped = function(on) {
        if (on) {
          exports.Buf8 = Uint8Array;
          exports.Buf16 = Uint16Array;
          exports.Buf32 = Int32Array;
          exports.assign(exports, fnTyped);
        } else {
          exports.Buf8 = Array;
          exports.Buf16 = Array;
          exports.Buf32 = Array;
          exports.assign(exports, fnUntyped);
        }
      };
      exports.setTyped(TYPED_OK);
    }, {}], 28:[function(_dereq_, module, exports) {
      var utils = _dereq_("./common");
      var STR_APPLY_OK = true;
      var STR_APPLY_UIA_OK = true;
      try {
        String.fromCharCode.apply(null, [0]);
      } catch (__) {
        STR_APPLY_OK = false;
      }
      try {
        String.fromCharCode.apply(null, new Uint8Array(1));
      } catch (__) {
        STR_APPLY_UIA_OK = false;
      }
      var _utf8len = new utils.Buf8(256);
      for (var i = 0;i < 256;i++) {
        _utf8len[i] = i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1;
      }
      _utf8len[254] = _utf8len[254] = 1;
      exports.string2buf = function(str) {
        var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;
        for (m_pos = 0;m_pos < str_len;m_pos++) {
          c = str.charCodeAt(m_pos);
          if ((c & 64512) === 55296 && m_pos + 1 < str_len) {
            c2 = str.charCodeAt(m_pos + 1);
            if ((c2 & 64512) === 56320) {
              c = 65536 + (c - 55296 << 10) + (c2 - 56320);
              m_pos++;
            }
          }
          buf_len += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
        }
        buf = new utils.Buf8(buf_len);
        for (i = 0, m_pos = 0;i < buf_len;m_pos++) {
          c = str.charCodeAt(m_pos);
          if ((c & 64512) === 55296 && m_pos + 1 < str_len) {
            c2 = str.charCodeAt(m_pos + 1);
            if ((c2 & 64512) === 56320) {
              c = 65536 + (c - 55296 << 10) + (c2 - 56320);
              m_pos++;
            }
          }
          if (c < 128) {
            buf[i++] = c;
          } else {
            if (c < 2048) {
              buf[i++] = 192 | c >>> 6;
              buf[i++] = 128 | c & 63;
            } else {
              if (c < 65536) {
                buf[i++] = 224 | c >>> 12;
                buf[i++] = 128 | c >>> 6 & 63;
                buf[i++] = 128 | c & 63;
              } else {
                buf[i++] = 240 | c >>> 18;
                buf[i++] = 128 | c >>> 12 & 63;
                buf[i++] = 128 | c >>> 6 & 63;
                buf[i++] = 128 | c & 63;
              }
            }
          }
        }
        return buf;
      };
      function buf2binstring(buf, len) {
        if (len < 65537) {
          if (buf.subarray && STR_APPLY_UIA_OK || !buf.subarray && STR_APPLY_OK) {
            return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
          }
        }
        var result = "";
        for (var i = 0;i < len;i++) {
          result += String.fromCharCode(buf[i]);
        }
        return result;
      }
      exports.buf2binstring = function(buf) {
        return buf2binstring(buf, buf.length);
      };
      exports.binstring2buf = function(str) {
        var buf = new utils.Buf8(str.length);
        for (var i = 0, len = buf.length;i < len;i++) {
          buf[i] = str.charCodeAt(i);
        }
        return buf;
      };
      exports.buf2string = function(buf, max) {
        var i, out, c, c_len;
        var len = max || buf.length;
        var utf16buf = new Array(len * 2);
        for (out = 0, i = 0;i < len;) {
          c = buf[i++];
          if (c < 128) {
            utf16buf[out++] = c;
            continue;
          }
          c_len = _utf8len[c];
          if (c_len > 4) {
            utf16buf[out++] = 65533;
            i += c_len - 1;
            continue;
          }
          c &= c_len === 2 ? 31 : c_len === 3 ? 15 : 7;
          while (c_len > 1 && i < len) {
            c = c << 6 | buf[i++] & 63;
            c_len--;
          }
          if (c_len > 1) {
            utf16buf[out++] = 65533;
            continue;
          }
          if (c < 65536) {
            utf16buf[out++] = c;
          } else {
            c -= 65536;
            utf16buf[out++] = 55296 | c >> 10 & 1023;
            utf16buf[out++] = 56320 | c & 1023;
          }
        }
        return buf2binstring(utf16buf, out);
      };
      exports.utf8border = function(buf, max) {
        var pos;
        max = max || buf.length;
        if (max > buf.length) {
          max = buf.length;
        }
        pos = max - 1;
        while (pos >= 0 && (buf[pos] & 192) === 128) {
          pos--;
        }
        if (pos < 0) {
          return max;
        }
        if (pos === 0) {
          return max;
        }
        return pos + _utf8len[buf[pos]] > max ? pos : max;
      };
    }, {"./common":27}], 29:[function(_dereq_, module, exports) {
      function adler32(adler, buf, len, pos) {
        var s1 = adler & 65535 | 0, s2 = adler >>> 16 & 65535 | 0, n = 0;
        while (len !== 0) {
          n = len > 2E3 ? 2E3 : len;
          len -= n;
          do {
            s1 = s1 + buf[pos++] | 0;
            s2 = s2 + s1 | 0;
          } while (--n);
          s1 %= 65521;
          s2 %= 65521;
        }
        return s1 | s2 << 16 | 0;
      }
      module.exports = adler32;
    }, {}], 30:[function(_dereq_, module, exports) {
      module.exports = {Z_NO_FLUSH:0, Z_PARTIAL_FLUSH:1, Z_SYNC_FLUSH:2, Z_FULL_FLUSH:3, Z_FINISH:4, Z_BLOCK:5, Z_TREES:6, Z_OK:0, Z_STREAM_END:1, Z_NEED_DICT:2, Z_ERRNO:-1, Z_STREAM_ERROR:-2, Z_DATA_ERROR:-3, Z_BUF_ERROR:-5, Z_NO_COMPRESSION:0, Z_BEST_SPEED:1, Z_BEST_COMPRESSION:9, Z_DEFAULT_COMPRESSION:-1, Z_FILTERED:1, Z_HUFFMAN_ONLY:2, Z_RLE:3, Z_FIXED:4, Z_DEFAULT_STRATEGY:0, Z_BINARY:0, Z_TEXT:1, Z_UNKNOWN:2, Z_DEFLATED:8};
    }, {}], 31:[function(_dereq_, module, exports) {
      function makeTable() {
        var c, table = [];
        for (var n = 0;n < 256;n++) {
          c = n;
          for (var k = 0;k < 8;k++) {
            c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
          }
          table[n] = c;
        }
        return table;
      }
      var crcTable = makeTable();
      function crc32(crc, buf, len, pos) {
        var t = crcTable, end = pos + len;
        crc = crc ^ -1;
        for (var i = pos;i < end;i++) {
          crc = crc >>> 8 ^ t[(crc ^ buf[i]) & 255];
        }
        return crc ^ -1;
      }
      module.exports = crc32;
    }, {}], 32:[function(_dereq_, module, exports) {
      var utils = _dereq_("../utils/common");
      var trees = _dereq_("./trees");
      var adler32 = _dereq_("./adler32");
      var crc32 = _dereq_("./crc32");
      var msg = _dereq_("./messages");
      var Z_NO_FLUSH = 0;
      var Z_PARTIAL_FLUSH = 1;
      var Z_FULL_FLUSH = 3;
      var Z_FINISH = 4;
      var Z_BLOCK = 5;
      var Z_OK = 0;
      var Z_STREAM_END = 1;
      var Z_STREAM_ERROR = -2;
      var Z_DATA_ERROR = -3;
      var Z_BUF_ERROR = -5;
      var Z_DEFAULT_COMPRESSION = -1;
      var Z_FILTERED = 1;
      var Z_HUFFMAN_ONLY = 2;
      var Z_RLE = 3;
      var Z_FIXED = 4;
      var Z_DEFAULT_STRATEGY = 0;
      var Z_UNKNOWN = 2;
      var Z_DEFLATED = 8;
      var MAX_MEM_LEVEL = 9;
      var MAX_WBITS = 15;
      var DEF_MEM_LEVEL = 8;
      var LENGTH_CODES = 29;
      var LITERALS = 256;
      var L_CODES = LITERALS + 1 + LENGTH_CODES;
      var D_CODES = 30;
      var BL_CODES = 19;
      var HEAP_SIZE = 2 * L_CODES + 1;
      var MAX_BITS = 15;
      var MIN_MATCH = 3;
      var MAX_MATCH = 258;
      var MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
      var PRESET_DICT = 32;
      var INIT_STATE = 42;
      var EXTRA_STATE = 69;
      var NAME_STATE = 73;
      var COMMENT_STATE = 91;
      var HCRC_STATE = 103;
      var BUSY_STATE = 113;
      var FINISH_STATE = 666;
      var BS_NEED_MORE = 1;
      var BS_BLOCK_DONE = 2;
      var BS_FINISH_STARTED = 3;
      var BS_FINISH_DONE = 4;
      var OS_CODE = 3;
      function err(strm, errorCode) {
        strm.msg = msg[errorCode];
        return errorCode;
      }
      function rank(f) {
        return (f << 1) - (f > 4 ? 9 : 0);
      }
      function zero(buf) {
        var len = buf.length;
        while (--len >= 0) {
          buf[len] = 0;
        }
      }
      function flush_pending(strm) {
        var s = strm.state;
        var len = s.pending;
        if (len > strm.avail_out) {
          len = strm.avail_out;
        }
        if (len === 0) {
          return;
        }
        utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);
        strm.next_out += len;
        s.pending_out += len;
        strm.total_out += len;
        strm.avail_out -= len;
        s.pending -= len;
        if (s.pending === 0) {
          s.pending_out = 0;
        }
      }
      function flush_block_only(s, last) {
        trees._tr_flush_block(s, s.block_start >= 0 ? s.block_start : -1, s.strstart - s.block_start, last);
        s.block_start = s.strstart;
        flush_pending(s.strm);
      }
      function put_byte(s, b) {
        s.pending_buf[s.pending++] = b;
      }
      function putShortMSB(s, b) {
        s.pending_buf[s.pending++] = b >>> 8 & 255;
        s.pending_buf[s.pending++] = b & 255;
      }
      function read_buf(strm, buf, start, size) {
        var len = strm.avail_in;
        if (len > size) {
          len = size;
        }
        if (len === 0) {
          return 0;
        }
        strm.avail_in -= len;
        utils.arraySet(buf, strm.input, strm.next_in, len, start);
        if (strm.state.wrap === 1) {
          strm.adler = adler32(strm.adler, buf, len, start);
        } else {
          if (strm.state.wrap === 2) {
            strm.adler = crc32(strm.adler, buf, len, start);
          }
        }
        strm.next_in += len;
        strm.total_in += len;
        return len;
      }
      function longest_match(s, cur_match) {
        var chain_length = s.max_chain_length;
        var scan = s.strstart;
        var match;
        var len;
        var best_len = s.prev_length;
        var nice_match = s.nice_match;
        var limit = s.strstart > s.w_size - MIN_LOOKAHEAD ? s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0;
        var _win = s.window;
        var wmask = s.w_mask;
        var prev = s.prev;
        var strend = s.strstart + MAX_MATCH;
        var scan_end1 = _win[scan + best_len - 1];
        var scan_end = _win[scan + best_len];
        if (s.prev_length >= s.good_match) {
          chain_length >>= 2;
        }
        if (nice_match > s.lookahead) {
          nice_match = s.lookahead;
        }
        do {
          match = cur_match;
          if (_win[match + best_len] !== scan_end || _win[match + best_len - 1] !== scan_end1 || _win[match] !== _win[scan] || _win[++match] !== _win[scan + 1]) {
            continue;
          }
          scan += 2;
          match++;
          do {
          } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && scan < strend);
          len = MAX_MATCH - (strend - scan);
          scan = strend - MAX_MATCH;
          if (len > best_len) {
            s.match_start = cur_match;
            best_len = len;
            if (len >= nice_match) {
              break;
            }
            scan_end1 = _win[scan + best_len - 1];
            scan_end = _win[scan + best_len];
          }
        } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);
        if (best_len <= s.lookahead) {
          return best_len;
        }
        return s.lookahead;
      }
      function fill_window(s) {
        var _w_size = s.w_size;
        var p, n, m, more, str;
        do {
          more = s.window_size - s.lookahead - s.strstart;
          if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {
            utils.arraySet(s.window, s.window, _w_size, _w_size, 0);
            s.match_start -= _w_size;
            s.strstart -= _w_size;
            s.block_start -= _w_size;
            n = s.hash_size;
            p = n;
            do {
              m = s.head[--p];
              s.head[p] = m >= _w_size ? m - _w_size : 0;
            } while (--n);
            n = _w_size;
            p = n;
            do {
              m = s.prev[--p];
              s.prev[p] = m >= _w_size ? m - _w_size : 0;
            } while (--n);
            more += _w_size;
          }
          if (s.strm.avail_in === 0) {
            break;
          }
          n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
          s.lookahead += n;
          if (s.lookahead + s.insert >= MIN_MATCH) {
            str = s.strstart - s.insert;
            s.ins_h = s.window[str];
            s.ins_h = (s.ins_h << s.hash_shift ^ s.window[str + 1]) & s.hash_mask;
            while (s.insert) {
              s.ins_h = (s.ins_h << s.hash_shift ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
              s.prev[str & s.w_mask] = s.head[s.ins_h];
              s.head[s.ins_h] = str;
              str++;
              s.insert--;
              if (s.lookahead + s.insert < MIN_MATCH) {
                break;
              }
            }
          }
        } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);
      }
      function deflate_stored(s, flush) {
        var max_block_size = 65535;
        if (max_block_size > s.pending_buf_size - 5) {
          max_block_size = s.pending_buf_size - 5;
        }
        for (;;) {
          if (s.lookahead <= 1) {
            fill_window(s);
            if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
              return BS_NEED_MORE;
            }
            if (s.lookahead === 0) {
              break;
            }
          }
          s.strstart += s.lookahead;
          s.lookahead = 0;
          var max_start = s.block_start + max_block_size;
          if (s.strstart === 0 || s.strstart >= max_start) {
            s.lookahead = s.strstart - max_start;
            s.strstart = max_start;
            flush_block_only(s, false);
            if (s.strm.avail_out === 0) {
              return BS_NEED_MORE;
            }
          }
          if (s.strstart - s.block_start >= s.w_size - MIN_LOOKAHEAD) {
            flush_block_only(s, false);
            if (s.strm.avail_out === 0) {
              return BS_NEED_MORE;
            }
          }
        }
        s.insert = 0;
        if (flush === Z_FINISH) {
          flush_block_only(s, true);
          if (s.strm.avail_out === 0) {
            return BS_FINISH_STARTED;
          }
          return BS_FINISH_DONE;
        }
        if (s.strstart > s.block_start) {
          flush_block_only(s, false);
          if (s.strm.avail_out === 0) {
            return BS_NEED_MORE;
          }
        }
        return BS_NEED_MORE;
      }
      function deflate_fast(s, flush) {
        var hash_head;
        var bflush;
        for (;;) {
          if (s.lookahead < MIN_LOOKAHEAD) {
            fill_window(s);
            if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
              return BS_NEED_MORE;
            }
            if (s.lookahead === 0) {
              break;
            }
          }
          hash_head = 0;
          if (s.lookahead >= MIN_MATCH) {
            s.ins_h = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
            hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
            s.head[s.ins_h] = s.strstart;
          }
          if (hash_head !== 0 && s.strstart - hash_head <= s.w_size - MIN_LOOKAHEAD) {
            s.match_length = longest_match(s, hash_head);
          }
          if (s.match_length >= MIN_MATCH) {
            bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);
            s.lookahead -= s.match_length;
            if (s.match_length <= s.max_lazy_match && s.lookahead >= MIN_MATCH) {
              s.match_length--;
              do {
                s.strstart++;
                s.ins_h = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
                hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
                s.head[s.ins_h] = s.strstart;
              } while (--s.match_length !== 0);
              s.strstart++;
            } else {
              s.strstart += s.match_length;
              s.match_length = 0;
              s.ins_h = s.window[s.strstart];
              s.ins_h = (s.ins_h << s.hash_shift ^ s.window[s.strstart + 1]) & s.hash_mask;
            }
          } else {
            bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
            s.lookahead--;
            s.strstart++;
          }
          if (bflush) {
            flush_block_only(s, false);
            if (s.strm.avail_out === 0) {
              return BS_NEED_MORE;
            }
          }
        }
        s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
        if (flush === Z_FINISH) {
          flush_block_only(s, true);
          if (s.strm.avail_out === 0) {
            return BS_FINISH_STARTED;
          }
          return BS_FINISH_DONE;
        }
        if (s.last_lit) {
          flush_block_only(s, false);
          if (s.strm.avail_out === 0) {
            return BS_NEED_MORE;
          }
        }
        return BS_BLOCK_DONE;
      }
      function deflate_slow(s, flush) {
        var hash_head;
        var bflush;
        var max_insert;
        for (;;) {
          if (s.lookahead < MIN_LOOKAHEAD) {
            fill_window(s);
            if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
              return BS_NEED_MORE;
            }
            if (s.lookahead === 0) {
              break;
            }
          }
          hash_head = 0;
          if (s.lookahead >= MIN_MATCH) {
            s.ins_h = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
            hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
            s.head[s.ins_h] = s.strstart;
          }
          s.prev_length = s.match_length;
          s.prev_match = s.match_start;
          s.match_length = MIN_MATCH - 1;
          if (hash_head !== 0 && s.prev_length < s.max_lazy_match && s.strstart - hash_head <= s.w_size - MIN_LOOKAHEAD) {
            s.match_length = longest_match(s, hash_head);
            if (s.match_length <= 5 && (s.strategy === Z_FILTERED || s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096)) {
              s.match_length = MIN_MATCH - 1;
            }
          }
          if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
            max_insert = s.strstart + s.lookahead - MIN_MATCH;
            bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
            s.lookahead -= s.prev_length - 1;
            s.prev_length -= 2;
            do {
              if (++s.strstart <= max_insert) {
                s.ins_h = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
                hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
                s.head[s.ins_h] = s.strstart;
              }
            } while (--s.prev_length !== 0);
            s.match_available = 0;
            s.match_length = MIN_MATCH - 1;
            s.strstart++;
            if (bflush) {
              flush_block_only(s, false);
              if (s.strm.avail_out === 0) {
                return BS_NEED_MORE;
              }
            }
          } else {
            if (s.match_available) {
              bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
              if (bflush) {
                flush_block_only(s, false);
              }
              s.strstart++;
              s.lookahead--;
              if (s.strm.avail_out === 0) {
                return BS_NEED_MORE;
              }
            } else {
              s.match_available = 1;
              s.strstart++;
              s.lookahead--;
            }
          }
        }
        if (s.match_available) {
          bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
          s.match_available = 0;
        }
        s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
        if (flush === Z_FINISH) {
          flush_block_only(s, true);
          if (s.strm.avail_out === 0) {
            return BS_FINISH_STARTED;
          }
          return BS_FINISH_DONE;
        }
        if (s.last_lit) {
          flush_block_only(s, false);
          if (s.strm.avail_out === 0) {
            return BS_NEED_MORE;
          }
        }
        return BS_BLOCK_DONE;
      }
      function deflate_rle(s, flush) {
        var bflush;
        var prev;
        var scan, strend;
        var _win = s.window;
        for (;;) {
          if (s.lookahead <= MAX_MATCH) {
            fill_window(s);
            if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {
              return BS_NEED_MORE;
            }
            if (s.lookahead === 0) {
              break;
            }
          }
          s.match_length = 0;
          if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
            scan = s.strstart - 1;
            prev = _win[scan];
            if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
              strend = s.strstart + MAX_MATCH;
              do {
              } while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && scan < strend);
              s.match_length = MAX_MATCH - (strend - scan);
              if (s.match_length > s.lookahead) {
                s.match_length = s.lookahead;
              }
            }
          }
          if (s.match_length >= MIN_MATCH) {
            bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);
            s.lookahead -= s.match_length;
            s.strstart += s.match_length;
            s.match_length = 0;
          } else {
            bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
            s.lookahead--;
            s.strstart++;
          }
          if (bflush) {
            flush_block_only(s, false);
            if (s.strm.avail_out === 0) {
              return BS_NEED_MORE;
            }
          }
        }
        s.insert = 0;
        if (flush === Z_FINISH) {
          flush_block_only(s, true);
          if (s.strm.avail_out === 0) {
            return BS_FINISH_STARTED;
          }
          return BS_FINISH_DONE;
        }
        if (s.last_lit) {
          flush_block_only(s, false);
          if (s.strm.avail_out === 0) {
            return BS_NEED_MORE;
          }
        }
        return BS_BLOCK_DONE;
      }
      function deflate_huff(s, flush) {
        var bflush;
        for (;;) {
          if (s.lookahead === 0) {
            fill_window(s);
            if (s.lookahead === 0) {
              if (flush === Z_NO_FLUSH) {
                return BS_NEED_MORE;
              }
              break;
            }
          }
          s.match_length = 0;
          bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
          s.lookahead--;
          s.strstart++;
          if (bflush) {
            flush_block_only(s, false);
            if (s.strm.avail_out === 0) {
              return BS_NEED_MORE;
            }
          }
        }
        s.insert = 0;
        if (flush === Z_FINISH) {
          flush_block_only(s, true);
          if (s.strm.avail_out === 0) {
            return BS_FINISH_STARTED;
          }
          return BS_FINISH_DONE;
        }
        if (s.last_lit) {
          flush_block_only(s, false);
          if (s.strm.avail_out === 0) {
            return BS_NEED_MORE;
          }
        }
        return BS_BLOCK_DONE;
      }
      var Config = function(good_length, max_lazy, nice_length, max_chain, func) {
        this.good_length = good_length;
        this.max_lazy = max_lazy;
        this.nice_length = nice_length;
        this.max_chain = max_chain;
        this.func = func;
      };
      var configuration_table;
      configuration_table = [new Config(0, 0, 0, 0, deflate_stored), new Config(4, 4, 8, 4, deflate_fast), new Config(4, 5, 16, 8, deflate_fast), new Config(4, 6, 32, 32, deflate_fast), new Config(4, 4, 16, 16, deflate_slow), new Config(8, 16, 32, 32, deflate_slow), new Config(8, 16, 128, 128, deflate_slow), new Config(8, 32, 128, 256, deflate_slow), new Config(32, 128, 258, 1024, deflate_slow), new Config(32, 258, 258, 4096, deflate_slow)];
      function lm_init(s) {
        s.window_size = 2 * s.w_size;
        zero(s.head);
        s.max_lazy_match = configuration_table[s.level].max_lazy;
        s.good_match = configuration_table[s.level].good_length;
        s.nice_match = configuration_table[s.level].nice_length;
        s.max_chain_length = configuration_table[s.level].max_chain;
        s.strstart = 0;
        s.block_start = 0;
        s.lookahead = 0;
        s.insert = 0;
        s.match_length = s.prev_length = MIN_MATCH - 1;
        s.match_available = 0;
        s.ins_h = 0;
      }
      function DeflateState() {
        this.strm = null;
        this.status = 0;
        this.pending_buf = null;
        this.pending_buf_size = 0;
        this.pending_out = 0;
        this.pending = 0;
        this.wrap = 0;
        this.gzhead = null;
        this.gzindex = 0;
        this.method = Z_DEFLATED;
        this.last_flush = -1;
        this.w_size = 0;
        this.w_bits = 0;
        this.w_mask = 0;
        this.window = null;
        this.window_size = 0;
        this.prev = null;
        this.head = null;
        this.ins_h = 0;
        this.hash_size = 0;
        this.hash_bits = 0;
        this.hash_mask = 0;
        this.hash_shift = 0;
        this.block_start = 0;
        this.match_length = 0;
        this.prev_match = 0;
        this.match_available = 0;
        this.strstart = 0;
        this.match_start = 0;
        this.lookahead = 0;
        this.prev_length = 0;
        this.max_chain_length = 0;
        this.max_lazy_match = 0;
        this.level = 0;
        this.strategy = 0;
        this.good_match = 0;
        this.nice_match = 0;
        this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2);
        this.dyn_dtree = new utils.Buf16((2 * D_CODES + 1) * 2);
        this.bl_tree = new utils.Buf16((2 * BL_CODES + 1) * 2);
        zero(this.dyn_ltree);
        zero(this.dyn_dtree);
        zero(this.bl_tree);
        this.l_desc = null;
        this.d_desc = null;
        this.bl_desc = null;
        this.bl_count = new utils.Buf16(MAX_BITS + 1);
        this.heap = new utils.Buf16(2 * L_CODES + 1);
        zero(this.heap);
        this.heap_len = 0;
        this.heap_max = 0;
        this.depth = new utils.Buf16(2 * L_CODES + 1);
        zero(this.depth);
        this.l_buf = 0;
        this.lit_bufsize = 0;
        this.last_lit = 0;
        this.d_buf = 0;
        this.opt_len = 0;
        this.static_len = 0;
        this.matches = 0;
        this.insert = 0;
        this.bi_buf = 0;
        this.bi_valid = 0;
      }
      function deflateResetKeep(strm) {
        var s;
        if (!strm || !strm.state) {
          return err(strm, Z_STREAM_ERROR);
        }
        strm.total_in = strm.total_out = 0;
        strm.data_type = Z_UNKNOWN;
        s = strm.state;
        s.pending = 0;
        s.pending_out = 0;
        if (s.wrap < 0) {
          s.wrap = -s.wrap;
        }
        s.status = s.wrap ? INIT_STATE : BUSY_STATE;
        strm.adler = s.wrap === 2 ? 0 : 1;
        s.last_flush = Z_NO_FLUSH;
        trees._tr_init(s);
        return Z_OK;
      }
      function deflateReset(strm) {
        var ret = deflateResetKeep(strm);
        if (ret === Z_OK) {
          lm_init(strm.state);
        }
        return ret;
      }
      function deflateSetHeader(strm, head) {
        if (!strm || !strm.state) {
          return Z_STREAM_ERROR;
        }
        if (strm.state.wrap !== 2) {
          return Z_STREAM_ERROR;
        }
        strm.state.gzhead = head;
        return Z_OK;
      }
      function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
        if (!strm) {
          return Z_STREAM_ERROR;
        }
        var wrap = 1;
        if (level === Z_DEFAULT_COMPRESSION) {
          level = 6;
        }
        if (windowBits < 0) {
          wrap = 0;
          windowBits = -windowBits;
        } else {
          if (windowBits > 15) {
            wrap = 2;
            windowBits -= 16;
          }
        }
        if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
          return err(strm, Z_STREAM_ERROR);
        }
        if (windowBits === 8) {
          windowBits = 9;
        }
        var s = new DeflateState;
        strm.state = s;
        s.strm = strm;
        s.wrap = wrap;
        s.gzhead = null;
        s.w_bits = windowBits;
        s.w_size = 1 << s.w_bits;
        s.w_mask = s.w_size - 1;
        s.hash_bits = memLevel + 7;
        s.hash_size = 1 << s.hash_bits;
        s.hash_mask = s.hash_size - 1;
        s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);
        s.window = new utils.Buf8(s.w_size * 2);
        s.head = new utils.Buf16(s.hash_size);
        s.prev = new utils.Buf16(s.w_size);
        s.lit_bufsize = 1 << memLevel + 6;
        s.pending_buf_size = s.lit_bufsize * 4;
        s.pending_buf = new utils.Buf8(s.pending_buf_size);
        s.d_buf = s.lit_bufsize >> 1;
        s.l_buf = (1 + 2) * s.lit_bufsize;
        s.level = level;
        s.strategy = strategy;
        s.method = method;
        return deflateReset(strm);
      }
      function deflateInit(strm, level) {
        return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
      }
      function deflate(strm, flush) {
        var old_flush, s;
        var beg, val;
        if (!strm || !strm.state || flush > Z_BLOCK || flush < 0) {
          return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
        }
        s = strm.state;
        if (!strm.output || !strm.input && strm.avail_in !== 0 || s.status === FINISH_STATE && flush !== Z_FINISH) {
          return err(strm, strm.avail_out === 0 ? Z_BUF_ERROR : Z_STREAM_ERROR);
        }
        s.strm = strm;
        old_flush = s.last_flush;
        s.last_flush = flush;
        if (s.status === INIT_STATE) {
          if (s.wrap === 2) {
            strm.adler = 0;
            put_byte(s, 31);
            put_byte(s, 139);
            put_byte(s, 8);
            if (!s.gzhead) {
              put_byte(s, 0);
              put_byte(s, 0);
              put_byte(s, 0);
              put_byte(s, 0);
              put_byte(s, 0);
              put_byte(s, s.level === 9 ? 2 : s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0);
              put_byte(s, OS_CODE);
              s.status = BUSY_STATE;
            } else {
              put_byte(s, (s.gzhead.text ? 1 : 0) + (s.gzhead.hcrc ? 2 : 0) + (!s.gzhead.extra ? 0 : 4) + (!s.gzhead.name ? 0 : 8) + (!s.gzhead.comment ? 0 : 16));
              put_byte(s, s.gzhead.time & 255);
              put_byte(s, s.gzhead.time >> 8 & 255);
              put_byte(s, s.gzhead.time >> 16 & 255);
              put_byte(s, s.gzhead.time >> 24 & 255);
              put_byte(s, s.level === 9 ? 2 : s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0);
              put_byte(s, s.gzhead.os & 255);
              if (s.gzhead.extra && s.gzhead.extra.length) {
                put_byte(s, s.gzhead.extra.length & 255);
                put_byte(s, s.gzhead.extra.length >> 8 & 255);
              }
              if (s.gzhead.hcrc) {
                strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);
              }
              s.gzindex = 0;
              s.status = EXTRA_STATE;
            }
          } else {
            var header = Z_DEFLATED + (s.w_bits - 8 << 4) << 8;
            var level_flags = -1;
            if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
              level_flags = 0;
            } else {
              if (s.level < 6) {
                level_flags = 1;
              } else {
                if (s.level === 6) {
                  level_flags = 2;
                } else {
                  level_flags = 3;
                }
              }
            }
            header |= level_flags << 6;
            if (s.strstart !== 0) {
              header |= PRESET_DICT;
            }
            header += 31 - header % 31;
            s.status = BUSY_STATE;
            putShortMSB(s, header);
            if (s.strstart !== 0) {
              putShortMSB(s, strm.adler >>> 16);
              putShortMSB(s, strm.adler & 65535);
            }
            strm.adler = 1;
          }
        }
        if (s.status === EXTRA_STATE) {
          if (s.gzhead.extra) {
            beg = s.pending;
            while (s.gzindex < (s.gzhead.extra.length & 65535)) {
              if (s.pending === s.pending_buf_size) {
                if (s.gzhead.hcrc && s.pending > beg) {
                  strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
                }
                flush_pending(strm);
                beg = s.pending;
                if (s.pending === s.pending_buf_size) {
                  break;
                }
              }
              put_byte(s, s.gzhead.extra[s.gzindex] & 255);
              s.gzindex++;
            }
            if (s.gzhead.hcrc && s.pending > beg) {
              strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
            }
            if (s.gzindex === s.gzhead.extra.length) {
              s.gzindex = 0;
              s.status = NAME_STATE;
            }
          } else {
            s.status = NAME_STATE;
          }
        }
        if (s.status === NAME_STATE) {
          if (s.gzhead.name) {
            beg = s.pending;
            do {
              if (s.pending === s.pending_buf_size) {
                if (s.gzhead.hcrc && s.pending > beg) {
                  strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
                }
                flush_pending(strm);
                beg = s.pending;
                if (s.pending === s.pending_buf_size) {
                  val = 1;
                  break;
                }
              }
              if (s.gzindex < s.gzhead.name.length) {
                val = s.gzhead.name.charCodeAt(s.gzindex++) & 255;
              } else {
                val = 0;
              }
              put_byte(s, val);
            } while (val !== 0);
            if (s.gzhead.hcrc && s.pending > beg) {
              strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
            }
            if (val === 0) {
              s.gzindex = 0;
              s.status = COMMENT_STATE;
            }
          } else {
            s.status = COMMENT_STATE;
          }
        }
        if (s.status === COMMENT_STATE) {
          if (s.gzhead.comment) {
            beg = s.pending;
            do {
              if (s.pending === s.pending_buf_size) {
                if (s.gzhead.hcrc && s.pending > beg) {
                  strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
                }
                flush_pending(strm);
                beg = s.pending;
                if (s.pending === s.pending_buf_size) {
                  val = 1;
                  break;
                }
              }
              if (s.gzindex < s.gzhead.comment.length) {
                val = s.gzhead.comment.charCodeAt(s.gzindex++) & 255;
              } else {
                val = 0;
              }
              put_byte(s, val);
            } while (val !== 0);
            if (s.gzhead.hcrc && s.pending > beg) {
              strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
            }
            if (val === 0) {
              s.status = HCRC_STATE;
            }
          } else {
            s.status = HCRC_STATE;
          }
        }
        if (s.status === HCRC_STATE) {
          if (s.gzhead.hcrc) {
            if (s.pending + 2 > s.pending_buf_size) {
              flush_pending(strm);
            }
            if (s.pending + 2 <= s.pending_buf_size) {
              put_byte(s, strm.adler & 255);
              put_byte(s, strm.adler >> 8 & 255);
              strm.adler = 0;
              s.status = BUSY_STATE;
            }
          } else {
            s.status = BUSY_STATE;
          }
        }
        if (s.pending !== 0) {
          flush_pending(strm);
          if (strm.avail_out === 0) {
            s.last_flush = -1;
            return Z_OK;
          }
        } else {
          if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && flush !== Z_FINISH) {
            return err(strm, Z_BUF_ERROR);
          }
        }
        if (s.status === FINISH_STATE && strm.avail_in !== 0) {
          return err(strm, Z_BUF_ERROR);
        }
        if (strm.avail_in !== 0 || s.lookahead !== 0 || flush !== Z_NO_FLUSH && s.status !== FINISH_STATE) {
          var bstate = s.strategy === Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : s.strategy === Z_RLE ? deflate_rle(s, flush) : configuration_table[s.level].func(s, flush);
          if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
            s.status = FINISH_STATE;
          }
          if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
            if (strm.avail_out === 0) {
              s.last_flush = -1;
            }
            return Z_OK;
          }
          if (bstate === BS_BLOCK_DONE) {
            if (flush === Z_PARTIAL_FLUSH) {
              trees._tr_align(s);
            } else {
              if (flush !== Z_BLOCK) {
                trees._tr_stored_block(s, 0, 0, false);
                if (flush === Z_FULL_FLUSH) {
                  zero(s.head);
                  if (s.lookahead === 0) {
                    s.strstart = 0;
                    s.block_start = 0;
                    s.insert = 0;
                  }
                }
              }
            }
            flush_pending(strm);
            if (strm.avail_out === 0) {
              s.last_flush = -1;
              return Z_OK;
            }
          }
        }
        if (flush !== Z_FINISH) {
          return Z_OK;
        }
        if (s.wrap <= 0) {
          return Z_STREAM_END;
        }
        if (s.wrap === 2) {
          put_byte(s, strm.adler & 255);
          put_byte(s, strm.adler >> 8 & 255);
          put_byte(s, strm.adler >> 16 & 255);
          put_byte(s, strm.adler >> 24 & 255);
          put_byte(s, strm.total_in & 255);
          put_byte(s, strm.total_in >> 8 & 255);
          put_byte(s, strm.total_in >> 16 & 255);
          put_byte(s, strm.total_in >> 24 & 255);
        } else {
          putShortMSB(s, strm.adler >>> 16);
          putShortMSB(s, strm.adler & 65535);
        }
        flush_pending(strm);
        if (s.wrap > 0) {
          s.wrap = -s.wrap;
        }
        return s.pending !== 0 ? Z_OK : Z_STREAM_END;
      }
      function deflateEnd(strm) {
        var status;
        if (!strm || !strm.state) {
          return Z_STREAM_ERROR;
        }
        status = strm.state.status;
        if (status !== INIT_STATE && status !== EXTRA_STATE && status !== NAME_STATE && status !== COMMENT_STATE && status !== HCRC_STATE && status !== BUSY_STATE && status !== FINISH_STATE) {
          return err(strm, Z_STREAM_ERROR);
        }
        strm.state = null;
        return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
      }
      exports.deflateInit = deflateInit;
      exports.deflateInit2 = deflateInit2;
      exports.deflateReset = deflateReset;
      exports.deflateResetKeep = deflateResetKeep;
      exports.deflateSetHeader = deflateSetHeader;
      exports.deflate = deflate;
      exports.deflateEnd = deflateEnd;
      exports.deflateInfo = "pako deflate (from Nodeca project)";
    }, {"../utils/common":27, "./adler32":29, "./crc32":31, "./messages":37, "./trees":38}], 33:[function(_dereq_, module, exports) {
      function GZheader() {
        this.text = 0;
        this.time = 0;
        this.xflags = 0;
        this.os = 0;
        this.extra = null;
        this.extra_len = 0;
        this.name = "";
        this.comment = "";
        this.hcrc = 0;
        this.done = false;
      }
      module.exports = GZheader;
    }, {}], 34:[function(_dereq_, module, exports) {
      var BAD = 30;
      var TYPE = 12;
      module.exports = function inflate_fast(strm, start) {
        var state;
        var _in;
        var last;
        var _out;
        var beg;
        var end;
        var dmax;
        var wsize;
        var whave;
        var wnext;
        var window;
        var hold;
        var bits;
        var lcode;
        var dcode;
        var lmask;
        var dmask;
        var here;
        var op;
        var len;
        var dist;
        var from;
        var from_source;
        var input, output;
        state = strm.state;
        _in = strm.next_in;
        input = strm.input;
        last = _in + (strm.avail_in - 5);
        _out = strm.next_out;
        output = strm.output;
        beg = _out - (start - strm.avail_out);
        end = _out + (strm.avail_out - 257);
        dmax = state.dmax;
        wsize = state.wsize;
        whave = state.whave;
        wnext = state.wnext;
        window = state.window;
        hold = state.hold;
        bits = state.bits;
        lcode = state.lencode;
        dcode = state.distcode;
        lmask = (1 << state.lenbits) - 1;
        dmask = (1 << state.distbits) - 1;
        top: do {
          if (bits < 15) {
            hold += input[_in++] << bits;
            bits += 8;
            hold += input[_in++] << bits;
            bits += 8;
          }
          here = lcode[hold & lmask];
          dolen: for (;;) {
            op = here >>> 24;
            hold >>>= op;
            bits -= op;
            op = here >>> 16 & 255;
            if (op === 0) {
              output[_out++] = here & 65535;
            } else {
              if (op & 16) {
                len = here & 65535;
                op &= 15;
                if (op) {
                  if (bits < op) {
                    hold += input[_in++] << bits;
                    bits += 8;
                  }
                  len += hold & (1 << op) - 1;
                  hold >>>= op;
                  bits -= op;
                }
                if (bits < 15) {
                  hold += input[_in++] << bits;
                  bits += 8;
                  hold += input[_in++] << bits;
                  bits += 8;
                }
                here = dcode[hold & dmask];
                dodist: for (;;) {
                  op = here >>> 24;
                  hold >>>= op;
                  bits -= op;
                  op = here >>> 16 & 255;
                  if (op & 16) {
                    dist = here & 65535;
                    op &= 15;
                    if (bits < op) {
                      hold += input[_in++] << bits;
                      bits += 8;
                      if (bits < op) {
                        hold += input[_in++] << bits;
                        bits += 8;
                      }
                    }
                    dist += hold & (1 << op) - 1;
                    if (dist > dmax) {
                      strm.msg = "invalid distance too far back";
                      state.mode = BAD;
                      break top;
                    }
                    hold >>>= op;
                    bits -= op;
                    op = _out - beg;
                    if (dist > op) {
                      op = dist - op;
                      if (op > whave) {
                        if (state.sane) {
                          strm.msg = "invalid distance too far back";
                          state.mode = BAD;
                          break top;
                        }
                      }
                      from = 0;
                      from_source = window;
                      if (wnext === 0) {
                        from += wsize - op;
                        if (op < len) {
                          len -= op;
                          do {
                            output[_out++] = window[from++];
                          } while (--op);
                          from = _out - dist;
                          from_source = output;
                        }
                      } else {
                        if (wnext < op) {
                          from += wsize + wnext - op;
                          op -= wnext;
                          if (op < len) {
                            len -= op;
                            do {
                              output[_out++] = window[from++];
                            } while (--op);
                            from = 0;
                            if (wnext < len) {
                              op = wnext;
                              len -= op;
                              do {
                                output[_out++] = window[from++];
                              } while (--op);
                              from = _out - dist;
                              from_source = output;
                            }
                          }
                        } else {
                          from += wnext - op;
                          if (op < len) {
                            len -= op;
                            do {
                              output[_out++] = window[from++];
                            } while (--op);
                            from = _out - dist;
                            from_source = output;
                          }
                        }
                      }
                      while (len > 2) {
                        output[_out++] = from_source[from++];
                        output[_out++] = from_source[from++];
                        output[_out++] = from_source[from++];
                        len -= 3;
                      }
                      if (len) {
                        output[_out++] = from_source[from++];
                        if (len > 1) {
                          output[_out++] = from_source[from++];
                        }
                      }
                    } else {
                      from = _out - dist;
                      do {
                        output[_out++] = output[from++];
                        output[_out++] = output[from++];
                        output[_out++] = output[from++];
                        len -= 3;
                      } while (len > 2);
                      if (len) {
                        output[_out++] = output[from++];
                        if (len > 1) {
                          output[_out++] = output[from++];
                        }
                      }
                    }
                  } else {
                    if ((op & 64) === 0) {
                      here = dcode[(here & 65535) + (hold & (1 << op) - 1)];
                      continue dodist;
                    } else {
                      strm.msg = "invalid distance code";
                      state.mode = BAD;
                      break top;
                    }
                  }
                  break;
                }
              } else {
                if ((op & 64) === 0) {
                  here = lcode[(here & 65535) + (hold & (1 << op) - 1)];
                  continue dolen;
                } else {
                  if (op & 32) {
                    state.mode = TYPE;
                    break top;
                  } else {
                    strm.msg = "invalid literal/length code";
                    state.mode = BAD;
                    break top;
                  }
                }
              }
            }
            break;
          }
        } while (_in < last && _out < end);
        len = bits >> 3;
        _in -= len;
        bits -= len << 3;
        hold &= (1 << bits) - 1;
        strm.next_in = _in;
        strm.next_out = _out;
        strm.avail_in = _in < last ? 5 + (last - _in) : 5 - (_in - last);
        strm.avail_out = _out < end ? 257 + (end - _out) : 257 - (_out - end);
        state.hold = hold;
        state.bits = bits;
        return;
      };
    }, {}], 35:[function(_dereq_, module, exports) {
      var utils = _dereq_("../utils/common");
      var adler32 = _dereq_("./adler32");
      var crc32 = _dereq_("./crc32");
      var inflate_fast = _dereq_("./inffast");
      var inflate_table = _dereq_("./inftrees");
      var CODES = 0;
      var LENS = 1;
      var DISTS = 2;
      var Z_FINISH = 4;
      var Z_BLOCK = 5;
      var Z_TREES = 6;
      var Z_OK = 0;
      var Z_STREAM_END = 1;
      var Z_NEED_DICT = 2;
      var Z_STREAM_ERROR = -2;
      var Z_DATA_ERROR = -3;
      var Z_MEM_ERROR = -4;
      var Z_BUF_ERROR = -5;
      var Z_DEFLATED = 8;
      var HEAD = 1;
      var FLAGS = 2;
      var TIME = 3;
      var OS = 4;
      var EXLEN = 5;
      var EXTRA = 6;
      var NAME = 7;
      var COMMENT = 8;
      var HCRC = 9;
      var DICTID = 10;
      var DICT = 11;
      var TYPE = 12;
      var TYPEDO = 13;
      var STORED = 14;
      var COPY_ = 15;
      var COPY = 16;
      var TABLE = 17;
      var LENLENS = 18;
      var CODELENS = 19;
      var LEN_ = 20;
      var LEN = 21;
      var LENEXT = 22;
      var DIST = 23;
      var DISTEXT = 24;
      var MATCH = 25;
      var LIT = 26;
      var CHECK = 27;
      var LENGTH = 28;
      var DONE = 29;
      var BAD = 30;
      var MEM = 31;
      var SYNC = 32;
      var ENOUGH_LENS = 852;
      var ENOUGH_DISTS = 592;
      var MAX_WBITS = 15;
      var DEF_WBITS = MAX_WBITS;
      function ZSWAP32(q) {
        return (q >>> 24 & 255) + (q >>> 8 & 65280) + ((q & 65280) << 8) + ((q & 255) << 24);
      }
      function InflateState() {
        this.mode = 0;
        this.last = false;
        this.wrap = 0;
        this.havedict = false;
        this.flags = 0;
        this.dmax = 0;
        this.check = 0;
        this.total = 0;
        this.head = null;
        this.wbits = 0;
        this.wsize = 0;
        this.whave = 0;
        this.wnext = 0;
        this.window = null;
        this.hold = 0;
        this.bits = 0;
        this.length = 0;
        this.offset = 0;
        this.extra = 0;
        this.lencode = null;
        this.distcode = null;
        this.lenbits = 0;
        this.distbits = 0;
        this.ncode = 0;
        this.nlen = 0;
        this.ndist = 0;
        this.have = 0;
        this.next = null;
        this.lens = new utils.Buf16(320);
        this.work = new utils.Buf16(288);
        this.lendyn = null;
        this.distdyn = null;
        this.sane = 0;
        this.back = 0;
        this.was = 0;
      }
      function inflateResetKeep(strm) {
        var state;
        if (!strm || !strm.state) {
          return Z_STREAM_ERROR;
        }
        state = strm.state;
        strm.total_in = strm.total_out = state.total = 0;
        strm.msg = "";
        if (state.wrap) {
          strm.adler = state.wrap & 1;
        }
        state.mode = HEAD;
        state.last = 0;
        state.havedict = 0;
        state.dmax = 32768;
        state.head = null;
        state.hold = 0;
        state.bits = 0;
        state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
        state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);
        state.sane = 1;
        state.back = -1;
        return Z_OK;
      }
      function inflateReset(strm) {
        var state;
        if (!strm || !strm.state) {
          return Z_STREAM_ERROR;
        }
        state = strm.state;
        state.wsize = 0;
        state.whave = 0;
        state.wnext = 0;
        return inflateResetKeep(strm);
      }
      function inflateReset2(strm, windowBits) {
        var wrap;
        var state;
        if (!strm || !strm.state) {
          return Z_STREAM_ERROR;
        }
        state = strm.state;
        if (windowBits < 0) {
          wrap = 0;
          windowBits = -windowBits;
        } else {
          wrap = (windowBits >> 4) + 1;
          if (windowBits < 48) {
            windowBits &= 15;
          }
        }
        if (windowBits && (windowBits < 8 || windowBits > 15)) {
          return Z_STREAM_ERROR;
        }
        if (state.window !== null && state.wbits !== windowBits) {
          state.window = null;
        }
        state.wrap = wrap;
        state.wbits = windowBits;
        return inflateReset(strm);
      }
      function inflateInit2(strm, windowBits) {
        var ret;
        var state;
        if (!strm) {
          return Z_STREAM_ERROR;
        }
        state = new InflateState;
        strm.state = state;
        state.window = null;
        ret = inflateReset2(strm, windowBits);
        if (ret !== Z_OK) {
          strm.state = null;
        }
        return ret;
      }
      function inflateInit(strm) {
        return inflateInit2(strm, DEF_WBITS);
      }
      var virgin = true;
      var lenfix, distfix;
      function fixedtables(state) {
        if (virgin) {
          var sym;
          lenfix = new utils.Buf32(512);
          distfix = new utils.Buf32(32);
          sym = 0;
          while (sym < 144) {
            state.lens[sym++] = 8;
          }
          while (sym < 256) {
            state.lens[sym++] = 9;
          }
          while (sym < 280) {
            state.lens[sym++] = 7;
          }
          while (sym < 288) {
            state.lens[sym++] = 8;
          }
          inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, {bits:9});
          sym = 0;
          while (sym < 32) {
            state.lens[sym++] = 5;
          }
          inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, {bits:5});
          virgin = false;
        }
        state.lencode = lenfix;
        state.lenbits = 9;
        state.distcode = distfix;
        state.distbits = 5;
      }
      function updatewindow(strm, src, end, copy) {
        var dist;
        var state = strm.state;
        if (state.window === null) {
          state.wsize = 1 << state.wbits;
          state.wnext = 0;
          state.whave = 0;
          state.window = new utils.Buf8(state.wsize);
        }
        if (copy >= state.wsize) {
          utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0);
          state.wnext = 0;
          state.whave = state.wsize;
        } else {
          dist = state.wsize - state.wnext;
          if (dist > copy) {
            dist = copy;
          }
          utils.arraySet(state.window, src, end - copy, dist, state.wnext);
          copy -= dist;
          if (copy) {
            utils.arraySet(state.window, src, end - copy, copy, 0);
            state.wnext = copy;
            state.whave = state.wsize;
          } else {
            state.wnext += dist;
            if (state.wnext === state.wsize) {
              state.wnext = 0;
            }
            if (state.whave < state.wsize) {
              state.whave += dist;
            }
          }
        }
        return 0;
      }
      function inflate(strm, flush) {
        var state;
        var input, output;
        var next;
        var put;
        var have, left;
        var hold;
        var bits;
        var _in, _out;
        var copy;
        var from;
        var from_source;
        var here = 0;
        var here_bits, here_op, here_val;
        var last_bits, last_op, last_val;
        var len;
        var ret;
        var hbuf = new utils.Buf8(4);
        var opts;
        var n;
        var order = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
        if (!strm || !strm.state || !strm.output || !strm.input && strm.avail_in !== 0) {
          return Z_STREAM_ERROR;
        }
        state = strm.state;
        if (state.mode === TYPE) {
          state.mode = TYPEDO;
        }
        put = strm.next_out;
        output = strm.output;
        left = strm.avail_out;
        next = strm.next_in;
        input = strm.input;
        have = strm.avail_in;
        hold = state.hold;
        bits = state.bits;
        _in = have;
        _out = left;
        ret = Z_OK;
        inf_leave: for (;;) {
          switch(state.mode) {
            case HEAD:
              if (state.wrap === 0) {
                state.mode = TYPEDO;
                break;
              }
              while (bits < 16) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              if (state.wrap & 2 && hold === 35615) {
                state.check = 0;
                hbuf[0] = hold & 255;
                hbuf[1] = hold >>> 8 & 255;
                state.check = crc32(state.check, hbuf, 2, 0);
                hold = 0;
                bits = 0;
                state.mode = FLAGS;
                break;
              }
              state.flags = 0;
              if (state.head) {
                state.head.done = false;
              }
              if (!(state.wrap & 1) || (((hold & 255) << 8) + (hold >> 8)) % 31) {
                strm.msg = "incorrect header check";
                state.mode = BAD;
                break;
              }
              if ((hold & 15) !== Z_DEFLATED) {
                strm.msg = "unknown compression method";
                state.mode = BAD;
                break;
              }
              hold >>>= 4;
              bits -= 4;
              len = (hold & 15) + 8;
              if (state.wbits === 0) {
                state.wbits = len;
              } else {
                if (len > state.wbits) {
                  strm.msg = "invalid window size";
                  state.mode = BAD;
                  break;
                }
              }
              state.dmax = 1 << len;
              strm.adler = state.check = 1;
              state.mode = hold & 512 ? DICTID : TYPE;
              hold = 0;
              bits = 0;
              break;
            case FLAGS:
              while (bits < 16) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              state.flags = hold;
              if ((state.flags & 255) !== Z_DEFLATED) {
                strm.msg = "unknown compression method";
                state.mode = BAD;
                break;
              }
              if (state.flags & 57344) {
                strm.msg = "unknown header flags set";
                state.mode = BAD;
                break;
              }
              if (state.head) {
                state.head.text = hold >> 8 & 1;
              }
              if (state.flags & 512) {
                hbuf[0] = hold & 255;
                hbuf[1] = hold >>> 8 & 255;
                state.check = crc32(state.check, hbuf, 2, 0);
              }
              hold = 0;
              bits = 0;
              state.mode = TIME;
            case TIME:
              while (bits < 32) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              if (state.head) {
                state.head.time = hold;
              }
              if (state.flags & 512) {
                hbuf[0] = hold & 255;
                hbuf[1] = hold >>> 8 & 255;
                hbuf[2] = hold >>> 16 & 255;
                hbuf[3] = hold >>> 24 & 255;
                state.check = crc32(state.check, hbuf, 4, 0);
              }
              hold = 0;
              bits = 0;
              state.mode = OS;
            case OS:
              while (bits < 16) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              if (state.head) {
                state.head.xflags = hold & 255;
                state.head.os = hold >> 8;
              }
              if (state.flags & 512) {
                hbuf[0] = hold & 255;
                hbuf[1] = hold >>> 8 & 255;
                state.check = crc32(state.check, hbuf, 2, 0);
              }
              hold = 0;
              bits = 0;
              state.mode = EXLEN;
            case EXLEN:
              if (state.flags & 1024) {
                while (bits < 16) {
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                state.length = hold;
                if (state.head) {
                  state.head.extra_len = hold;
                }
                if (state.flags & 512) {
                  hbuf[0] = hold & 255;
                  hbuf[1] = hold >>> 8 & 255;
                  state.check = crc32(state.check, hbuf, 2, 0);
                }
                hold = 0;
                bits = 0;
              } else {
                if (state.head) {
                  state.head.extra = null;
                }
              }
              state.mode = EXTRA;
            case EXTRA:
              if (state.flags & 1024) {
                copy = state.length;
                if (copy > have) {
                  copy = have;
                }
                if (copy) {
                  if (state.head) {
                    len = state.head.extra_len - state.length;
                    if (!state.head.extra) {
                      state.head.extra = new Array(state.head.extra_len);
                    }
                    utils.arraySet(state.head.extra, input, next, copy, len);
                  }
                  if (state.flags & 512) {
                    state.check = crc32(state.check, input, copy, next);
                  }
                  have -= copy;
                  next += copy;
                  state.length -= copy;
                }
                if (state.length) {
                  break inf_leave;
                }
              }
              state.length = 0;
              state.mode = NAME;
            case NAME:
              if (state.flags & 2048) {
                if (have === 0) {
                  break inf_leave;
                }
                copy = 0;
                do {
                  len = input[next + copy++];
                  if (state.head && len && state.length < 65536) {
                    state.head.name += String.fromCharCode(len);
                  }
                } while (len && copy < have);
                if (state.flags & 512) {
                  state.check = crc32(state.check, input, copy, next);
                }
                have -= copy;
                next += copy;
                if (len) {
                  break inf_leave;
                }
              } else {
                if (state.head) {
                  state.head.name = null;
                }
              }
              state.length = 0;
              state.mode = COMMENT;
            case COMMENT:
              if (state.flags & 4096) {
                if (have === 0) {
                  break inf_leave;
                }
                copy = 0;
                do {
                  len = input[next + copy++];
                  if (state.head && len && state.length < 65536) {
                    state.head.comment += String.fromCharCode(len);
                  }
                } while (len && copy < have);
                if (state.flags & 512) {
                  state.check = crc32(state.check, input, copy, next);
                }
                have -= copy;
                next += copy;
                if (len) {
                  break inf_leave;
                }
              } else {
                if (state.head) {
                  state.head.comment = null;
                }
              }
              state.mode = HCRC;
            case HCRC:
              if (state.flags & 512) {
                while (bits < 16) {
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                if (hold !== (state.check & 65535)) {
                  strm.msg = "header crc mismatch";
                  state.mode = BAD;
                  break;
                }
                hold = 0;
                bits = 0;
              }
              if (state.head) {
                state.head.hcrc = state.flags >> 9 & 1;
                state.head.done = true;
              }
              strm.adler = state.check = 0;
              state.mode = TYPE;
              break;
            case DICTID:
              while (bits < 32) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              strm.adler = state.check = ZSWAP32(hold);
              hold = 0;
              bits = 0;
              state.mode = DICT;
            case DICT:
              if (state.havedict === 0) {
                strm.next_out = put;
                strm.avail_out = left;
                strm.next_in = next;
                strm.avail_in = have;
                state.hold = hold;
                state.bits = bits;
                return Z_NEED_DICT;
              }
              strm.adler = state.check = 1;
              state.mode = TYPE;
            case TYPE:
              if (flush === Z_BLOCK || flush === Z_TREES) {
                break inf_leave;
              }
            ;
            case TYPEDO:
              if (state.last) {
                hold >>>= bits & 7;
                bits -= bits & 7;
                state.mode = CHECK;
                break;
              }
              while (bits < 3) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              state.last = hold & 1;
              hold >>>= 1;
              bits -= 1;
              switch(hold & 3) {
                case 0:
                  state.mode = STORED;
                  break;
                case 1:
                  fixedtables(state);
                  state.mode = LEN_;
                  if (flush === Z_TREES) {
                    hold >>>= 2;
                    bits -= 2;
                    break inf_leave;
                  }
                  break;
                case 2:
                  state.mode = TABLE;
                  break;
                case 3:
                  strm.msg = "invalid block type";
                  state.mode = BAD;
              }
              hold >>>= 2;
              bits -= 2;
              break;
            case STORED:
              hold >>>= bits & 7;
              bits -= bits & 7;
              while (bits < 32) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              if ((hold & 65535) !== (hold >>> 16 ^ 65535)) {
                strm.msg = "invalid stored block lengths";
                state.mode = BAD;
                break;
              }
              state.length = hold & 65535;
              hold = 0;
              bits = 0;
              state.mode = COPY_;
              if (flush === Z_TREES) {
                break inf_leave;
              }
            ;
            case COPY_:
              state.mode = COPY;
            case COPY:
              copy = state.length;
              if (copy) {
                if (copy > have) {
                  copy = have;
                }
                if (copy > left) {
                  copy = left;
                }
                if (copy === 0) {
                  break inf_leave;
                }
                utils.arraySet(output, input, next, copy, put);
                have -= copy;
                next += copy;
                left -= copy;
                put += copy;
                state.length -= copy;
                break;
              }
              state.mode = TYPE;
              break;
            case TABLE:
              while (bits < 14) {
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              state.nlen = (hold & 31) + 257;
              hold >>>= 5;
              bits -= 5;
              state.ndist = (hold & 31) + 1;
              hold >>>= 5;
              bits -= 5;
              state.ncode = (hold & 15) + 4;
              hold >>>= 4;
              bits -= 4;
              if (state.nlen > 286 || state.ndist > 30) {
                strm.msg = "too many length or distance symbols";
                state.mode = BAD;
                break;
              }
              state.have = 0;
              state.mode = LENLENS;
            case LENLENS:
              while (state.have < state.ncode) {
                while (bits < 3) {
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                state.lens[order[state.have++]] = hold & 7;
                hold >>>= 3;
                bits -= 3;
              }
              while (state.have < 19) {
                state.lens[order[state.have++]] = 0;
              }
              state.lencode = state.lendyn;
              state.lenbits = 7;
              opts = {bits:state.lenbits};
              ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
              state.lenbits = opts.bits;
              if (ret) {
                strm.msg = "invalid code lengths set";
                state.mode = BAD;
                break;
              }
              state.have = 0;
              state.mode = CODELENS;
            case CODELENS:
              while (state.have < state.nlen + state.ndist) {
                for (;;) {
                  here = state.lencode[hold & (1 << state.lenbits) - 1];
                  here_bits = here >>> 24;
                  here_op = here >>> 16 & 255;
                  here_val = here & 65535;
                  if (here_bits <= bits) {
                    break;
                  }
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                if (here_val < 16) {
                  hold >>>= here_bits;
                  bits -= here_bits;
                  state.lens[state.have++] = here_val;
                } else {
                  if (here_val === 16) {
                    n = here_bits + 2;
                    while (bits < n) {
                      if (have === 0) {
                        break inf_leave;
                      }
                      have--;
                      hold += input[next++] << bits;
                      bits += 8;
                    }
                    hold >>>= here_bits;
                    bits -= here_bits;
                    if (state.have === 0) {
                      strm.msg = "invalid bit length repeat";
                      state.mode = BAD;
                      break;
                    }
                    len = state.lens[state.have - 1];
                    copy = 3 + (hold & 3);
                    hold >>>= 2;
                    bits -= 2;
                  } else {
                    if (here_val === 17) {
                      n = here_bits + 3;
                      while (bits < n) {
                        if (have === 0) {
                          break inf_leave;
                        }
                        have--;
                        hold += input[next++] << bits;
                        bits += 8;
                      }
                      hold >>>= here_bits;
                      bits -= here_bits;
                      len = 0;
                      copy = 3 + (hold & 7);
                      hold >>>= 3;
                      bits -= 3;
                    } else {
                      n = here_bits + 7;
                      while (bits < n) {
                        if (have === 0) {
                          break inf_leave;
                        }
                        have--;
                        hold += input[next++] << bits;
                        bits += 8;
                      }
                      hold >>>= here_bits;
                      bits -= here_bits;
                      len = 0;
                      copy = 11 + (hold & 127);
                      hold >>>= 7;
                      bits -= 7;
                    }
                  }
                  if (state.have + copy > state.nlen + state.ndist) {
                    strm.msg = "invalid bit length repeat";
                    state.mode = BAD;
                    break;
                  }
                  while (copy--) {
                    state.lens[state.have++] = len;
                  }
                }
              }
              if (state.mode === BAD) {
                break;
              }
              if (state.lens[256] === 0) {
                strm.msg = "invalid code -- missing end-of-block";
                state.mode = BAD;
                break;
              }
              state.lenbits = 9;
              opts = {bits:state.lenbits};
              ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
              state.lenbits = opts.bits;
              if (ret) {
                strm.msg = "invalid literal/lengths set";
                state.mode = BAD;
                break;
              }
              state.distbits = 6;
              state.distcode = state.distdyn;
              opts = {bits:state.distbits};
              ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
              state.distbits = opts.bits;
              if (ret) {
                strm.msg = "invalid distances set";
                state.mode = BAD;
                break;
              }
              state.mode = LEN_;
              if (flush === Z_TREES) {
                break inf_leave;
              }
            ;
            case LEN_:
              state.mode = LEN;
            case LEN:
              if (have >= 6 && left >= 258) {
                strm.next_out = put;
                strm.avail_out = left;
                strm.next_in = next;
                strm.avail_in = have;
                state.hold = hold;
                state.bits = bits;
                inflate_fast(strm, _out);
                put = strm.next_out;
                output = strm.output;
                left = strm.avail_out;
                next = strm.next_in;
                input = strm.input;
                have = strm.avail_in;
                hold = state.hold;
                bits = state.bits;
                if (state.mode === TYPE) {
                  state.back = -1;
                }
                break;
              }
              state.back = 0;
              for (;;) {
                here = state.lencode[hold & (1 << state.lenbits) - 1];
                here_bits = here >>> 24;
                here_op = here >>> 16 & 255;
                here_val = here & 65535;
                if (here_bits <= bits) {
                  break;
                }
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              if (here_op && (here_op & 240) === 0) {
                last_bits = here_bits;
                last_op = here_op;
                last_val = here_val;
                for (;;) {
                  here = state.lencode[last_val + ((hold & (1 << last_bits + last_op) - 1) >> last_bits)];
                  here_bits = here >>> 24;
                  here_op = here >>> 16 & 255;
                  here_val = here & 65535;
                  if (last_bits + here_bits <= bits) {
                    break;
                  }
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                hold >>>= last_bits;
                bits -= last_bits;
                state.back += last_bits;
              }
              hold >>>= here_bits;
              bits -= here_bits;
              state.back += here_bits;
              state.length = here_val;
              if (here_op === 0) {
                state.mode = LIT;
                break;
              }
              if (here_op & 32) {
                state.back = -1;
                state.mode = TYPE;
                break;
              }
              if (here_op & 64) {
                strm.msg = "invalid literal/length code";
                state.mode = BAD;
                break;
              }
              state.extra = here_op & 15;
              state.mode = LENEXT;
            case LENEXT:
              if (state.extra) {
                n = state.extra;
                while (bits < n) {
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                state.length += hold & (1 << state.extra) - 1;
                hold >>>= state.extra;
                bits -= state.extra;
                state.back += state.extra;
              }
              state.was = state.length;
              state.mode = DIST;
            case DIST:
              for (;;) {
                here = state.distcode[hold & (1 << state.distbits) - 1];
                here_bits = here >>> 24;
                here_op = here >>> 16 & 255;
                here_val = here & 65535;
                if (here_bits <= bits) {
                  break;
                }
                if (have === 0) {
                  break inf_leave;
                }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              if ((here_op & 240) === 0) {
                last_bits = here_bits;
                last_op = here_op;
                last_val = here_val;
                for (;;) {
                  here = state.distcode[last_val + ((hold & (1 << last_bits + last_op) - 1) >> last_bits)];
                  here_bits = here >>> 24;
                  here_op = here >>> 16 & 255;
                  here_val = here & 65535;
                  if (last_bits + here_bits <= bits) {
                    break;
                  }
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                hold >>>= last_bits;
                bits -= last_bits;
                state.back += last_bits;
              }
              hold >>>= here_bits;
              bits -= here_bits;
              state.back += here_bits;
              if (here_op & 64) {
                strm.msg = "invalid distance code";
                state.mode = BAD;
                break;
              }
              state.offset = here_val;
              state.extra = here_op & 15;
              state.mode = DISTEXT;
            case DISTEXT:
              if (state.extra) {
                n = state.extra;
                while (bits < n) {
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                state.offset += hold & (1 << state.extra) - 1;
                hold >>>= state.extra;
                bits -= state.extra;
                state.back += state.extra;
              }
              if (state.offset > state.dmax) {
                strm.msg = "invalid distance too far back";
                state.mode = BAD;
                break;
              }
              state.mode = MATCH;
            case MATCH:
              if (left === 0) {
                break inf_leave;
              }
              copy = _out - left;
              if (state.offset > copy) {
                copy = state.offset - copy;
                if (copy > state.whave) {
                  if (state.sane) {
                    strm.msg = "invalid distance too far back";
                    state.mode = BAD;
                    break;
                  }
                }
                if (copy > state.wnext) {
                  copy -= state.wnext;
                  from = state.wsize - copy;
                } else {
                  from = state.wnext - copy;
                }
                if (copy > state.length) {
                  copy = state.length;
                }
                from_source = state.window;
              } else {
                from_source = output;
                from = put - state.offset;
                copy = state.length;
              }
              if (copy > left) {
                copy = left;
              }
              left -= copy;
              state.length -= copy;
              do {
                output[put++] = from_source[from++];
              } while (--copy);
              if (state.length === 0) {
                state.mode = LEN;
              }
              break;
            case LIT:
              if (left === 0) {
                break inf_leave;
              }
              output[put++] = state.length;
              left--;
              state.mode = LEN;
              break;
            case CHECK:
              if (state.wrap) {
                while (bits < 32) {
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold |= input[next++] << bits;
                  bits += 8;
                }
                _out -= left;
                strm.total_out += _out;
                state.total += _out;
                if (_out) {
                  strm.adler = state.check = state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out);
                }
                _out = left;
                if ((state.flags ? hold : ZSWAP32(hold)) !== state.check) {
                  strm.msg = "incorrect data check";
                  state.mode = BAD;
                  break;
                }
                hold = 0;
                bits = 0;
              }
              state.mode = LENGTH;
            case LENGTH:
              if (state.wrap && state.flags) {
                while (bits < 32) {
                  if (have === 0) {
                    break inf_leave;
                  }
                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                }
                if (hold !== (state.total & 4294967295)) {
                  strm.msg = "incorrect length check";
                  state.mode = BAD;
                  break;
                }
                hold = 0;
                bits = 0;
              }
              state.mode = DONE;
            case DONE:
              ret = Z_STREAM_END;
              break inf_leave;
            case BAD:
              ret = Z_DATA_ERROR;
              break inf_leave;
            case MEM:
              return Z_MEM_ERROR;
            case SYNC:
            ;
            default:
              return Z_STREAM_ERROR;
          }
        }
        strm.next_out = put;
        strm.avail_out = left;
        strm.next_in = next;
        strm.avail_in = have;
        state.hold = hold;
        state.bits = bits;
        if (state.wsize || _out !== strm.avail_out && state.mode < BAD && (state.mode < CHECK || flush !== Z_FINISH)) {
          if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
            state.mode = MEM;
            return Z_MEM_ERROR;
          }
        }
        _in -= strm.avail_in;
        _out -= strm.avail_out;
        strm.total_in += _in;
        strm.total_out += _out;
        state.total += _out;
        if (state.wrap && _out) {
          strm.adler = state.check = state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out);
        }
        strm.data_type = state.bits + (state.last ? 64 : 0) + (state.mode === TYPE ? 128 : 0) + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
        if ((_in === 0 && _out === 0 || flush === Z_FINISH) && ret === Z_OK) {
          ret = Z_BUF_ERROR;
        }
        return ret;
      }
      function inflateEnd(strm) {
        if (!strm || !strm.state) {
          return Z_STREAM_ERROR;
        }
        var state = strm.state;
        if (state.window) {
          state.window = null;
        }
        strm.state = null;
        return Z_OK;
      }
      function inflateGetHeader(strm, head) {
        var state;
        if (!strm || !strm.state) {
          return Z_STREAM_ERROR;
        }
        state = strm.state;
        if ((state.wrap & 2) === 0) {
          return Z_STREAM_ERROR;
        }
        state.head = head;
        head.done = false;
        return Z_OK;
      }
      exports.inflateReset = inflateReset;
      exports.inflateReset2 = inflateReset2;
      exports.inflateResetKeep = inflateResetKeep;
      exports.inflateInit = inflateInit;
      exports.inflateInit2 = inflateInit2;
      exports.inflate = inflate;
      exports.inflateEnd = inflateEnd;
      exports.inflateGetHeader = inflateGetHeader;
      exports.inflateInfo = "pako inflate (from Nodeca project)";
    }, {"../utils/common":27, "./adler32":29, "./crc32":31, "./inffast":34, "./inftrees":36}], 36:[function(_dereq_, module, exports) {
      var utils = _dereq_("../utils/common");
      var MAXBITS = 15;
      var ENOUGH_LENS = 852;
      var ENOUGH_DISTS = 592;
      var CODES = 0;
      var LENS = 1;
      var DISTS = 2;
      var lbase = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0];
      var lext = [16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78];
      var dbase = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0];
      var dext = [16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 64, 64];
      module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) {
        var bits = opts.bits;
        var len = 0;
        var sym = 0;
        var min = 0, max = 0;
        var root = 0;
        var curr = 0;
        var drop = 0;
        var left = 0;
        var used = 0;
        var huff = 0;
        var incr;
        var fill;
        var low;
        var mask;
        var next;
        var base = null;
        var base_index = 0;
        var end;
        var count = new utils.Buf16(MAXBITS + 1);
        var offs = new utils.Buf16(MAXBITS + 1);
        var extra = null;
        var extra_index = 0;
        var here_bits, here_op, here_val;
        for (len = 0;len <= MAXBITS;len++) {
          count[len] = 0;
        }
        for (sym = 0;sym < codes;sym++) {
          count[lens[lens_index + sym]]++;
        }
        root = bits;
        for (max = MAXBITS;max >= 1;max--) {
          if (count[max] !== 0) {
            break;
          }
        }
        if (root > max) {
          root = max;
        }
        if (max === 0) {
          table[table_index++] = 1 << 24 | 64 << 16 | 0;
          table[table_index++] = 1 << 24 | 64 << 16 | 0;
          opts.bits = 1;
          return 0;
        }
        for (min = 1;min < max;min++) {
          if (count[min] !== 0) {
            break;
          }
        }
        if (root < min) {
          root = min;
        }
        left = 1;
        for (len = 1;len <= MAXBITS;len++) {
          left <<= 1;
          left -= count[len];
          if (left < 0) {
            return -1;
          }
        }
        if (left > 0 && (type === CODES || max !== 1)) {
          return -1;
        }
        offs[1] = 0;
        for (len = 1;len < MAXBITS;len++) {
          offs[len + 1] = offs[len] + count[len];
        }
        for (sym = 0;sym < codes;sym++) {
          if (lens[lens_index + sym] !== 0) {
            work[offs[lens[lens_index + sym]]++] = sym;
          }
        }
        if (type === CODES) {
          base = extra = work;
          end = 19;
        } else {
          if (type === LENS) {
            base = lbase;
            base_index -= 257;
            extra = lext;
            extra_index -= 257;
            end = 256;
          } else {
            base = dbase;
            extra = dext;
            end = -1;
          }
        }
        huff = 0;
        sym = 0;
        len = min;
        next = table_index;
        curr = root;
        drop = 0;
        low = -1;
        used = 1 << root;
        mask = used - 1;
        if (type === LENS && used > ENOUGH_LENS || type === DISTS && used > ENOUGH_DISTS) {
          return 1;
        }
        var i = 0;
        for (;;) {
          i++;
          here_bits = len - drop;
          if (work[sym] < end) {
            here_op = 0;
            here_val = work[sym];
          } else {
            if (work[sym] > end) {
              here_op = extra[extra_index + work[sym]];
              here_val = base[base_index + work[sym]];
            } else {
              here_op = 32 + 64;
              here_val = 0;
            }
          }
          incr = 1 << len - drop;
          fill = 1 << curr;
          min = fill;
          do {
            fill -= incr;
            table[next + (huff >> drop) + fill] = here_bits << 24 | here_op << 16 | here_val | 0;
          } while (fill !== 0);
          incr = 1 << len - 1;
          while (huff & incr) {
            incr >>= 1;
          }
          if (incr !== 0) {
            huff &= incr - 1;
            huff += incr;
          } else {
            huff = 0;
          }
          sym++;
          if (--count[len] === 0) {
            if (len === max) {
              break;
            }
            len = lens[lens_index + work[sym]];
          }
          if (len > root && (huff & mask) !== low) {
            if (drop === 0) {
              drop = root;
            }
            next += min;
            curr = len - drop;
            left = 1 << curr;
            while (curr + drop < max) {
              left -= count[curr + drop];
              if (left <= 0) {
                break;
              }
              curr++;
              left <<= 1;
            }
            used += 1 << curr;
            if (type === LENS && used > ENOUGH_LENS || type === DISTS && used > ENOUGH_DISTS) {
              return 1;
            }
            low = huff & mask;
            table[low] = root << 24 | curr << 16 | next - table_index | 0;
          }
        }
        if (huff !== 0) {
          table[next + huff] = len - drop << 24 | 64 << 16 | 0;
        }
        opts.bits = root;
        return 0;
      };
    }, {"../utils/common":27}], 37:[function(_dereq_, module, exports) {
      module.exports = {2:"need dictionary", 1:"stream end", 0:"", "-1":"file error", "-2":"stream error", "-3":"data error", "-4":"insufficient memory", "-5":"buffer error", "-6":"incompatible version"};
    }, {}], 38:[function(_dereq_, module, exports) {
      var utils = _dereq_("../utils/common");
      var Z_FIXED = 4;
      var Z_BINARY = 0;
      var Z_TEXT = 1;
      var Z_UNKNOWN = 2;
      function zero(buf) {
        var len = buf.length;
        while (--len >= 0) {
          buf[len] = 0;
        }
      }
      var STORED_BLOCK = 0;
      var STATIC_TREES = 1;
      var DYN_TREES = 2;
      var MIN_MATCH = 3;
      var MAX_MATCH = 258;
      var LENGTH_CODES = 29;
      var LITERALS = 256;
      var L_CODES = LITERALS + 1 + LENGTH_CODES;
      var D_CODES = 30;
      var BL_CODES = 19;
      var HEAP_SIZE = 2 * L_CODES + 1;
      var MAX_BITS = 15;
      var Buf_size = 16;
      var MAX_BL_BITS = 7;
      var END_BLOCK = 256;
      var REP_3_6 = 16;
      var REPZ_3_10 = 17;
      var REPZ_11_138 = 18;
      var extra_lbits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
      var extra_dbits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
      var extra_blbits = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7];
      var bl_order = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
      var DIST_CODE_LEN = 512;
      var static_ltree = new Array((L_CODES + 2) * 2);
      zero(static_ltree);
      var static_dtree = new Array(D_CODES * 2);
      zero(static_dtree);
      var _dist_code = new Array(DIST_CODE_LEN);
      zero(_dist_code);
      var _length_code = new Array(MAX_MATCH - MIN_MATCH + 1);
      zero(_length_code);
      var base_length = new Array(LENGTH_CODES);
      zero(base_length);
      var base_dist = new Array(D_CODES);
      zero(base_dist);
      var StaticTreeDesc = function(static_tree, extra_bits, extra_base, elems, max_length) {
        this.static_tree = static_tree;
        this.extra_bits = extra_bits;
        this.extra_base = extra_base;
        this.elems = elems;
        this.max_length = max_length;
        this.has_stree = static_tree && static_tree.length;
      };
      var static_l_desc;
      var static_d_desc;
      var static_bl_desc;
      var TreeDesc = function(dyn_tree, stat_desc) {
        this.dyn_tree = dyn_tree;
        this.max_code = 0;
        this.stat_desc = stat_desc;
      };
      function d_code(dist) {
        return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
      }
      function put_short(s, w) {
        s.pending_buf[s.pending++] = w & 255;
        s.pending_buf[s.pending++] = w >>> 8 & 255;
      }
      function send_bits(s, value, length) {
        if (s.bi_valid > Buf_size - length) {
          s.bi_buf |= value << s.bi_valid & 65535;
          put_short(s, s.bi_buf);
          s.bi_buf = value >> Buf_size - s.bi_valid;
          s.bi_valid += length - Buf_size;
        } else {
          s.bi_buf |= value << s.bi_valid & 65535;
          s.bi_valid += length;
        }
      }
      function send_code(s, c, tree) {
        send_bits(s, tree[c * 2], tree[c * 2 + 1]);
      }
      function bi_reverse(code, len) {
        var res = 0;
        do {
          res |= code & 1;
          code >>>= 1;
          res <<= 1;
        } while (--len > 0);
        return res >>> 1;
      }
      function bi_flush(s) {
        if (s.bi_valid === 16) {
          put_short(s, s.bi_buf);
          s.bi_buf = 0;
          s.bi_valid = 0;
        } else {
          if (s.bi_valid >= 8) {
            s.pending_buf[s.pending++] = s.bi_buf & 255;
            s.bi_buf >>= 8;
            s.bi_valid -= 8;
          }
        }
      }
      function gen_bitlen(s, desc) {
        var tree = desc.dyn_tree;
        var max_code = desc.max_code;
        var stree = desc.stat_desc.static_tree;
        var has_stree = desc.stat_desc.has_stree;
        var extra = desc.stat_desc.extra_bits;
        var base = desc.stat_desc.extra_base;
        var max_length = desc.stat_desc.max_length;
        var h;
        var n, m;
        var bits;
        var xbits;
        var f;
        var overflow = 0;
        for (bits = 0;bits <= MAX_BITS;bits++) {
          s.bl_count[bits] = 0;
        }
        tree[s.heap[s.heap_max] * 2 + 1] = 0;
        for (h = s.heap_max + 1;h < HEAP_SIZE;h++) {
          n = s.heap[h];
          bits = tree[tree[n * 2 + 1] * 2 + 1] + 1;
          if (bits > max_length) {
            bits = max_length;
            overflow++;
          }
          tree[n * 2 + 1] = bits;
          if (n > max_code) {
            continue;
          }
          s.bl_count[bits]++;
          xbits = 0;
          if (n >= base) {
            xbits = extra[n - base];
          }
          f = tree[n * 2];
          s.opt_len += f * (bits + xbits);
          if (has_stree) {
            s.static_len += f * (stree[n * 2 + 1] + xbits);
          }
        }
        if (overflow === 0) {
          return;
        }
        do {
          bits = max_length - 1;
          while (s.bl_count[bits] === 0) {
            bits--;
          }
          s.bl_count[bits]--;
          s.bl_count[bits + 1] += 2;
          s.bl_count[max_length]--;
          overflow -= 2;
        } while (overflow > 0);
        for (bits = max_length;bits !== 0;bits--) {
          n = s.bl_count[bits];
          while (n !== 0) {
            m = s.heap[--h];
            if (m > max_code) {
              continue;
            }
            if (tree[m * 2 + 1] !== bits) {
              s.opt_len += (bits - tree[m * 2 + 1]) * tree[m * 2];
              tree[m * 2 + 1] = bits;
            }
            n--;
          }
        }
      }
      function gen_codes(tree, max_code, bl_count) {
        var next_code = new Array(MAX_BITS + 1);
        var code = 0;
        var bits;
        var n;
        for (bits = 1;bits <= MAX_BITS;bits++) {
          next_code[bits] = code = code + bl_count[bits - 1] << 1;
        }
        for (n = 0;n <= max_code;n++) {
          var len = tree[n * 2 + 1];
          if (len === 0) {
            continue;
          }
          tree[n * 2] = bi_reverse(next_code[len]++, len);
        }
      }
      function tr_static_init() {
        var n;
        var bits;
        var length;
        var code;
        var dist;
        var bl_count = new Array(MAX_BITS + 1);
        length = 0;
        for (code = 0;code < LENGTH_CODES - 1;code++) {
          base_length[code] = length;
          for (n = 0;n < 1 << extra_lbits[code];n++) {
            _length_code[length++] = code;
          }
        }
        _length_code[length - 1] = code;
        dist = 0;
        for (code = 0;code < 16;code++) {
          base_dist[code] = dist;
          for (n = 0;n < 1 << extra_dbits[code];n++) {
            _dist_code[dist++] = code;
          }
        }
        dist >>= 7;
        for (;code < D_CODES;code++) {
          base_dist[code] = dist << 7;
          for (n = 0;n < 1 << extra_dbits[code] - 7;n++) {
            _dist_code[256 + dist++] = code;
          }
        }
        for (bits = 0;bits <= MAX_BITS;bits++) {
          bl_count[bits] = 0;
        }
        n = 0;
        while (n <= 143) {
          static_ltree[n * 2 + 1] = 8;
          n++;
          bl_count[8]++;
        }
        while (n <= 255) {
          static_ltree[n * 2 + 1] = 9;
          n++;
          bl_count[9]++;
        }
        while (n <= 279) {
          static_ltree[n * 2 + 1] = 7;
          n++;
          bl_count[7]++;
        }
        while (n <= 287) {
          static_ltree[n * 2 + 1] = 8;
          n++;
          bl_count[8]++;
        }
        gen_codes(static_ltree, L_CODES + 1, bl_count);
        for (n = 0;n < D_CODES;n++) {
          static_dtree[n * 2 + 1] = 5;
          static_dtree[n * 2] = bi_reverse(n, 5);
        }
        static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
        static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS);
        static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS);
      }
      function init_block(s) {
        var n;
        for (n = 0;n < L_CODES;n++) {
          s.dyn_ltree[n * 2] = 0;
        }
        for (n = 0;n < D_CODES;n++) {
          s.dyn_dtree[n * 2] = 0;
        }
        for (n = 0;n < BL_CODES;n++) {
          s.bl_tree[n * 2] = 0;
        }
        s.dyn_ltree[END_BLOCK * 2] = 1;
        s.opt_len = s.static_len = 0;
        s.last_lit = s.matches = 0;
      }
      function bi_windup(s) {
        if (s.bi_valid > 8) {
          put_short(s, s.bi_buf);
        } else {
          if (s.bi_valid > 0) {
            s.pending_buf[s.pending++] = s.bi_buf;
          }
        }
        s.bi_buf = 0;
        s.bi_valid = 0;
      }
      function copy_block(s, buf, len, header) {
        bi_windup(s);
        if (header) {
          put_short(s, len);
          put_short(s, ~len);
        }
        utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);
        s.pending += len;
      }
      function smaller(tree, n, m, depth) {
        var _n2 = n * 2;
        var _m2 = m * 2;
        return tree[_n2] < tree[_m2] || tree[_n2] === tree[_m2] && depth[n] <= depth[m];
      }
      function pqdownheap(s, tree, k) {
        var v = s.heap[k];
        var j = k << 1;
        while (j <= s.heap_len) {
          if (j < s.heap_len && smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
            j++;
          }
          if (smaller(tree, v, s.heap[j], s.depth)) {
            break;
          }
          s.heap[k] = s.heap[j];
          k = j;
          j <<= 1;
        }
        s.heap[k] = v;
      }
      function compress_block(s, ltree, dtree) {
        var dist;
        var lc;
        var lx = 0;
        var code;
        var extra;
        if (s.last_lit !== 0) {
          do {
            dist = s.pending_buf[s.d_buf + lx * 2] << 8 | s.pending_buf[s.d_buf + lx * 2 + 1];
            lc = s.pending_buf[s.l_buf + lx];
            lx++;
            if (dist === 0) {
              send_code(s, lc, ltree);
            } else {
              code = _length_code[lc];
              send_code(s, code + LITERALS + 1, ltree);
              extra = extra_lbits[code];
              if (extra !== 0) {
                lc -= base_length[code];
                send_bits(s, lc, extra);
              }
              dist--;
              code = d_code(dist);
              send_code(s, code, dtree);
              extra = extra_dbits[code];
              if (extra !== 0) {
                dist -= base_dist[code];
                send_bits(s, dist, extra);
              }
            }
          } while (lx < s.last_lit);
        }
        send_code(s, END_BLOCK, ltree);
      }
      function build_tree(s, desc) {
        var tree = desc.dyn_tree;
        var stree = desc.stat_desc.static_tree;
        var has_stree = desc.stat_desc.has_stree;
        var elems = desc.stat_desc.elems;
        var n, m;
        var max_code = -1;
        var node;
        s.heap_len = 0;
        s.heap_max = HEAP_SIZE;
        for (n = 0;n < elems;n++) {
          if (tree[n * 2] !== 0) {
            s.heap[++s.heap_len] = max_code = n;
            s.depth[n] = 0;
          } else {
            tree[n * 2 + 1] = 0;
          }
        }
        while (s.heap_len < 2) {
          node = s.heap[++s.heap_len] = max_code < 2 ? ++max_code : 0;
          tree[node * 2] = 1;
          s.depth[node] = 0;
          s.opt_len--;
          if (has_stree) {
            s.static_len -= stree[node * 2 + 1];
          }
        }
        desc.max_code = max_code;
        for (n = s.heap_len >> 1;n >= 1;n--) {
          pqdownheap(s, tree, n);
        }
        node = elems;
        do {
          n = s.heap[1];
          s.heap[1] = s.heap[s.heap_len--];
          pqdownheap(s, tree, 1);
          m = s.heap[1];
          s.heap[--s.heap_max] = n;
          s.heap[--s.heap_max] = m;
          tree[node * 2] = tree[n * 2] + tree[m * 2];
          s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
          tree[n * 2 + 1] = tree[m * 2 + 1] = node;
          s.heap[1] = node++;
          pqdownheap(s, tree, 1);
        } while (s.heap_len >= 2);
        s.heap[--s.heap_max] = s.heap[1];
        gen_bitlen(s, desc);
        gen_codes(tree, max_code, s.bl_count);
      }
      function scan_tree(s, tree, max_code) {
        var n;
        var prevlen = -1;
        var curlen;
        var nextlen = tree[0 * 2 + 1];
        var count = 0;
        var max_count = 7;
        var min_count = 4;
        if (nextlen === 0) {
          max_count = 138;
          min_count = 3;
        }
        tree[(max_code + 1) * 2 + 1] = 65535;
        for (n = 0;n <= max_code;n++) {
          curlen = nextlen;
          nextlen = tree[(n + 1) * 2 + 1];
          if (++count < max_count && curlen === nextlen) {
            continue;
          } else {
            if (count < min_count) {
              s.bl_tree[curlen * 2] += count;
            } else {
              if (curlen !== 0) {
                if (curlen !== prevlen) {
                  s.bl_tree[curlen * 2]++;
                }
                s.bl_tree[REP_3_6 * 2]++;
              } else {
                if (count <= 10) {
                  s.bl_tree[REPZ_3_10 * 2]++;
                } else {
                  s.bl_tree[REPZ_11_138 * 2]++;
                }
              }
            }
          }
          count = 0;
          prevlen = curlen;
          if (nextlen === 0) {
            max_count = 138;
            min_count = 3;
          } else {
            if (curlen === nextlen) {
              max_count = 6;
              min_count = 3;
            } else {
              max_count = 7;
              min_count = 4;
            }
          }
        }
      }
      function send_tree(s, tree, max_code) {
        var n;
        var prevlen = -1;
        var curlen;
        var nextlen = tree[0 * 2 + 1];
        var count = 0;
        var max_count = 7;
        var min_count = 4;
        if (nextlen === 0) {
          max_count = 138;
          min_count = 3;
        }
        for (n = 0;n <= max_code;n++) {
          curlen = nextlen;
          nextlen = tree[(n + 1) * 2 + 1];
          if (++count < max_count && curlen === nextlen) {
            continue;
          } else {
            if (count < min_count) {
              do {
                send_code(s, curlen, s.bl_tree);
              } while (--count !== 0);
            } else {
              if (curlen !== 0) {
                if (curlen !== prevlen) {
                  send_code(s, curlen, s.bl_tree);
                  count--;
                }
                send_code(s, REP_3_6, s.bl_tree);
                send_bits(s, count - 3, 2);
              } else {
                if (count <= 10) {
                  send_code(s, REPZ_3_10, s.bl_tree);
                  send_bits(s, count - 3, 3);
                } else {
                  send_code(s, REPZ_11_138, s.bl_tree);
                  send_bits(s, count - 11, 7);
                }
              }
            }
          }
          count = 0;
          prevlen = curlen;
          if (nextlen === 0) {
            max_count = 138;
            min_count = 3;
          } else {
            if (curlen === nextlen) {
              max_count = 6;
              min_count = 3;
            } else {
              max_count = 7;
              min_count = 4;
            }
          }
        }
      }
      function build_bl_tree(s) {
        var max_blindex;
        scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
        scan_tree(s, s.dyn_dtree, s.d_desc.max_code);
        build_tree(s, s.bl_desc);
        for (max_blindex = BL_CODES - 1;max_blindex >= 3;max_blindex--) {
          if (s.bl_tree[bl_order[max_blindex] * 2 + 1] !== 0) {
            break;
          }
        }
        s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
        return max_blindex;
      }
      function send_all_trees(s, lcodes, dcodes, blcodes) {
        var rank;
        send_bits(s, lcodes - 257, 5);
        send_bits(s, dcodes - 1, 5);
        send_bits(s, blcodes - 4, 4);
        for (rank = 0;rank < blcodes;rank++) {
          send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1], 3);
        }
        send_tree(s, s.dyn_ltree, lcodes - 1);
        send_tree(s, s.dyn_dtree, dcodes - 1);
      }
      function detect_data_type(s) {
        var black_mask = 4093624447;
        var n;
        for (n = 0;n <= 31;n++, black_mask >>>= 1) {
          if (black_mask & 1 && s.dyn_ltree[n * 2] !== 0) {
            return Z_BINARY;
          }
        }
        if (s.dyn_ltree[9 * 2] !== 0 || s.dyn_ltree[10 * 2] !== 0 || s.dyn_ltree[13 * 2] !== 0) {
          return Z_TEXT;
        }
        for (n = 32;n < LITERALS;n++) {
          if (s.dyn_ltree[n * 2] !== 0) {
            return Z_TEXT;
          }
        }
        return Z_BINARY;
      }
      var static_init_done = false;
      function _tr_init(s) {
        if (!static_init_done) {
          tr_static_init();
          static_init_done = true;
        }
        s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc);
        s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc);
        s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);
        s.bi_buf = 0;
        s.bi_valid = 0;
        init_block(s);
      }
      function _tr_stored_block(s, buf, stored_len, last) {
        send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);
        copy_block(s, buf, stored_len, true);
      }
      function _tr_align(s) {
        send_bits(s, STATIC_TREES << 1, 3);
        send_code(s, END_BLOCK, static_ltree);
        bi_flush(s);
      }
      function _tr_flush_block(s, buf, stored_len, last) {
        var opt_lenb, static_lenb;
        var max_blindex = 0;
        if (s.level > 0) {
          if (s.strm.data_type === Z_UNKNOWN) {
            s.strm.data_type = detect_data_type(s);
          }
          build_tree(s, s.l_desc);
          build_tree(s, s.d_desc);
          max_blindex = build_bl_tree(s);
          opt_lenb = s.opt_len + 3 + 7 >>> 3;
          static_lenb = s.static_len + 3 + 7 >>> 3;
          if (static_lenb <= opt_lenb) {
            opt_lenb = static_lenb;
          }
        } else {
          opt_lenb = static_lenb = stored_len + 5;
        }
        if (stored_len + 4 <= opt_lenb && buf !== -1) {
          _tr_stored_block(s, buf, stored_len, last);
        } else {
          if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {
            send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
            compress_block(s, static_ltree, static_dtree);
          } else {
            send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
            send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
            compress_block(s, s.dyn_ltree, s.dyn_dtree);
          }
        }
        init_block(s);
        if (last) {
          bi_windup(s);
        }
      }
      function _tr_tally(s, dist, lc) {
        s.pending_buf[s.d_buf + s.last_lit * 2] = dist >>> 8 & 255;
        s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 255;
        s.pending_buf[s.l_buf + s.last_lit] = lc & 255;
        s.last_lit++;
        if (dist === 0) {
          s.dyn_ltree[lc * 2]++;
        } else {
          s.matches++;
          dist--;
          s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]++;
          s.dyn_dtree[d_code(dist) * 2]++;
        }
        return s.last_lit === s.lit_bufsize - 1;
      }
      exports._tr_init = _tr_init;
      exports._tr_stored_block = _tr_stored_block;
      exports._tr_flush_block = _tr_flush_block;
      exports._tr_tally = _tr_tally;
      exports._tr_align = _tr_align;
    }, {"../utils/common":27}], 39:[function(_dereq_, module, exports) {
      function ZStream() {
        this.input = null;
        this.next_in = 0;
        this.avail_in = 0;
        this.total_in = 0;
        this.output = null;
        this.next_out = 0;
        this.avail_out = 0;
        this.total_out = 0;
        this.msg = "";
        this.state = null;
        this.data_type = 2;
        this.adler = 0;
      }
      module.exports = ZStream;
    }, {}]}, {}, [9])(9);
  });
  
  