+});
+
+var TextNode = function(nativeNode, document) {
+    DocumentNode.call(this, nativeNode, document);
+};
+TextNode.prototype = Object.create(DocumentNode.prototype);
+
+$.extend(TextNode.prototype, {
+    nodeType: Node.TEXT_NODE,
+
+    getText: function() {
+        return this.nativeNode.data;
+    },
+
+    setText: function(text) {
+        this.nativeNode.data = text;
+        this.triggerTextChangeEvent();
+    },
+
+    appendText: function(text) {
+        this.nativeNode.data = this.nativeNode.data + text;
+        this.triggerTextChangeEvent();
+    },
+
+    prependText: function(text) {
+        this.nativeNode.data = text + this.nativeNode.data;
+        this.triggerTextChangeEvent();
+    },
+
+    wrapWith: function(desc) {
+        if(typeof desc.start === 'number' && typeof desc.end === 'number') {
+            return this.document._wrapText({
+                inside: this.parent(),
+                textNodeIdx: this.parent().indexOf(this),
+                offsetStart: Math.min(desc.start, desc.end),
+                offsetEnd: Math.max(desc.start, desc.end),
+                _with: {tagName: desc.tagName, attrs: desc.attrs}
+            });
+        } else {
+            return DocumentNode.prototype.wrapWith.call(this, desc);
+        }
+    },
+
+    split: function(params) {
+        var parentElement = this.parent(),
+            passed = false,
+            succeedingChildren = [],
+            prefix = this.getText().substr(0, params.offset),
+            suffix = this.getText().substr(params.offset);
+
+        parentElement.contents().forEach(function(child) {
+            if(passed) {
+                succeedingChildren.push(child);
+            }
+            if(child.sameNode(this)) {
+                passed = true;
+            }
+        }.bind(this));
+
+        if(prefix.length > 0) {
+            this.setText(prefix);
+        }
+        else {
+            this.detach();
+        }
+
+        var attrs = {};
+        parentElement.getAttrs().forEach(function(attr) {attrs[attr.name] = attr.value; });
+        var newElement = this.document.createDocumentNode({tagName: parentElement.getTagName(), attrs: attrs});
+        parentElement.after(newElement);
+
+        if(suffix.length > 0) {
+            newElement.append({text: suffix});
+        }
+        succeedingChildren.forEach(function(child) {
+            newElement.append(child);
+        });
+
+        return {first: parentElement, second: newElement};
+    },
+
+    triggerTextChangeEvent: function() {
+        var event = new events.ChangeEvent('nodeTextChange', {node: this});
+        this.document.trigger('change', event);
+    }
+});
+
+
+var parseXML = function(xml) {
+    return $($.trim(xml))[0];
+};
+
+var Document = function(xml) {
+    this.loadXML(xml);
+};
+
+$.extend(Document.prototype, Backbone.Events, {
+    ElementNodeFactory: ElementNode,
+    TextNodeFactory: TextNode,
+
+    createDocumentNode: function(from) {
+        if(!(from instanceof Node)) {
+            if(from.text !== undefined) {
+                /* globals document */
+                from = document.createTextNode(from.text);
+            } else {
+                var node = $('<' + from.tagName + '>');
+
+                _.keys(from.attrs || {}).forEach(function(key) {
+                    node.attr(key, from.attrs[key]);
+                });
+
+                from = node[0];
+            }
+        }
+        var Factory;
+        if(from.nodeType === Node.TEXT_NODE) {
+            Factory = this.TextNodeFactory;
+        } else if(from.nodeType === Node.ELEMENT_NODE) {
+            Factory = this.ElementNodeFactory;
+        }
+        return new Factory(from, this);
+    },
+
+    loadXML: function(xml, options) {
+        options = options || {};
+        defineDocumentProperties(this, $(parseXML(xml)));
+        if(!options.silent) {
+            this.trigger('contentSet');
+        }
+    },
+
+    toXML: function() {
+        return this.root.toXML();
+    },
+
+    containsNode: function(node) {
+        return this.root && (node.nativeNode === this.root.nativeNode || node._$.parents().index(this.root._$) !== -1);
+    },
+
+    wrapNodes: function(params) {
+        if(!(params.node1.parent().sameNode(params.node2.parent()))) {
+            throw new Error('Wrapping non-sibling nodes not supported.');
+        }
+
+        var parent = params.node1.parent(),
+            parentContents = parent.contents(),
+            wrapper = this.createDocumentNode({
+                tagName: params._with.tagName,
+                attrs: params._with.attrs}),
+            idx1 = parent.indexOf(params.node1),
+            idx2 = parent.indexOf(params.node2);
+
+        if(idx1 > idx2) {
+            var tmp = idx1;
+            idx1 = idx2;
+            idx2 = tmp;
+        }
+
+        var insertingMethod, insertingTarget;
+        if(idx1 === 0) {
+            insertingMethod = 'prepend';
+            insertingTarget = parent;
+        } else {
+            insertingMethod = 'after';
+            insertingTarget = parentContents[idx1-1];
+        }
+
+        for(var i = idx1; i <= idx2; i++) {
+            wrapper.append(parentContents[i].detach());
+        }