--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore');
+
+
+var Fragment = function(document) {
+ this.document = document;
+};
+$.extend(Fragment.prototype, {
+ isValid: function() {
+ return false;
+ }
+});
+
+
+var NodeFragment = function(document, params) {
+ Fragment.call(this, document);
+ this.node = params.node;
+};
+NodeFragment.prototype = Object.create(Fragment.prototype);
+$.extend(NodeFragment.prototype, {
+ isValid: function() {
+ return this.document.containsNode(this.node);
+ }
+});
+
+
+var CaretFragment = function(document, params) {
+ NodeFragment.call(this, document, params);
+ this.offset = params.offset;
+
+};
+CaretFragment.prototype = Object.create(NodeFragment.prototype);
+$.extend(CaretFragment.prototype, {
+ isValid: function() {
+ /* globals Node */
+ return NodeFragment.prototype.isValid.call(this) &&
+ this.node.nodeType === Node.TEXT_NODE &&
+ _.isNumber(this.offset);
+ }
+});
+
+
+
+var RangeFragment = function(document, params) {
+ Fragment.call(this, document);
+
+ if(params.node1.sameNode(params.node2)) {
+ this.startNode = this.endNode = params.node1;
+ } else {
+ /*jshint bitwise: false*/
+ /* globals Node */
+ var node1First = params.node1.nativeNode.compareDocumentPosition(params.node2.nativeNode) & Node.DOCUMENT_POSITION_FOLLOWING;
+ (node1First ? ['start', 'end'] : ['end','start']).forEach(function(prefix, idx) {
+ this[prefix + 'Node'] = params['node'+(idx+1)];
+ }.bind(this));
+ }
+};
+RangeFragment.prototype = Object.create(Fragment.prototype);
+$.extend(RangeFragment.prototype, {
+ isValid: function() {
+ return this.document.containsNode(this.startNode) && this.document.containsNode(this.endNode);
+ },
+ hasSiblingBoundries: function() {
+ return this.isValid() && this.startNode.isSiblingOf(this.endNode);
+ },
+ boundriesSiblingParents: function() {
+ return this.startNode.document.getSiblingParents({
+ node1: this.startNode,
+ node2: this.endNode
+ });
+ },
+ getCommonParent: function() {
+ var siblingParents = this.boundriesSiblingParents();
+ if(siblingParents) {
+ return siblingParents.node1.parent();
+ }
+ },
+});
+
+var TextRangeFragment = function(document, params) {
+ var orderChanged;
+
+ RangeFragment.call(this, document, params);
+
+ if(this.startNode.sameNode(this.endNode)) {
+ this.startOffset = Math.min(params.offset1, params.offset2);
+ this.endOffset = Math.max(params.offset1, params.offset2);
+ } else {
+ orderChanged = !params.node1.sameNode(this.startNode);
+ this.startOffset = orderChanged ? params.offset2 : params.offset1;
+ this.endOffset = orderChanged ? params.offset1 : params.offset2;
+ }
+};
+TextRangeFragment.prototype = Object.create(RangeFragment.prototype);
+$.extend(TextRangeFragment.prototype, {
+ isValid: function() {
+ return RangeFragment.prototype.isValid.call(this) &&
+ _.isNumber(this.startOffset) &&
+ _.isNumber(this.endOffset);
+ }
+});
+
+var FragmentTypes = {
+ Fragment: Fragment,
+ NodeFragment: NodeFragment,
+ CaretFragment: CaretFragment,
+ RangeFragment: RangeFragment,
+ TextRangeFragment: TextRangeFragment
+};
+_.values(FragmentTypes).forEach(function(Type) {
+ $.extend(Type.prototype, FragmentTypes);
+});
+
+return FragmentTypes;
+
+});
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+/* global describe, it */
+/* jshint expr:true */
+
+
+var chai = require('libs/chai'),
+ smartxml = require('./smartxml.js');
+
+
+var expect = chai.expect;
+
+var getDocumentFromXML = function(xml) {
+ return smartxml.documentFromXML(xml);
+};
+
+describe('Fragments API', function() {
+ describe('node fragment', function() {
+ it('describes a single node', function() {
+ var doc = getDocumentFromXML('<section></section');
+
+ var fragment = doc.createFragment(doc.NodeFragment, {node:doc.root});
+ expect(fragment instanceof fragment.NodeFragment).to.be.true;
+ expect(fragment.node.sameNode(doc.root)).to.be.true;
+ });
+ });
+
+ describe('caret fragment', function() {
+ it('describes place in a text', function() {
+ var doc = getDocumentFromXML('<section>Alice</section>');
+
+ var fragment = doc.createFragment(doc.CaretFragment, {node: doc.root.contents()[0], offset: 1});
+
+ expect(fragment instanceof fragment.CaretFragment).to.be.true;
+ expect(fragment instanceof fragment.NodeFragment).to.be.true;
+ expect(fragment.node.getText()).to.equal('Alice');
+ expect(fragment.offset).to.equal(1);
+ });
+ });
+
+ describe('text range fragment', function() {
+ it('describes fragment of a text node', function() {
+ var doc = getDocumentFromXML('<section>Alice</section>'),
+ textNode = doc.root.contents()[0];
+
+ var fragment = doc.createFragment(doc.TextRangeFragment, {
+ node1: textNode,
+ offset1: 4,
+ node2: textNode,
+ offset2: 1
+ });
+
+ expect(fragment instanceof fragment.TextRangeFragment).to.be.true;
+ expect(fragment instanceof fragment.RangeFragment).to.be.true;
+ expect(fragment.startNode.getText()).to.equal('Alice');
+ expect(fragment.startOffset).to.equal(1);
+ expect(fragment.endNode.getText()).to.equal('Alice');
+ expect(fragment.endOffset).to.equal(4);
+ });
+ it('describes text spanning multiple nodes', function() {
+ var doc = getDocumentFromXML('<section>Alice <span>has</span> a cat!</section>'),
+ textNode1 = doc.root.contents()[0],
+ textNode2 = doc.root.contents()[2];
+
+ var fragment = doc.createFragment(doc.TextRangeFragment, {
+ node1: textNode2,
+ offset1: 4,
+ node2: textNode1,
+ offset2: 1
+ });
+
+ expect(fragment instanceof fragment.TextRangeFragment).to.be.true;
+ expect(fragment.startNode.getText()).to.equal('Alice ');
+ expect(fragment.startOffset).to.equal(1);
+ expect(fragment.endNode.getText()).to.equal(' a cat!');
+ expect(fragment.endOffset).to.equal(4);
+ });
+ });
+});
+
+});
\ No newline at end of file
'libs/backbone',
'smartxml/events',
'smartxml/transformations',
- 'smartxml/core'
-], function($, _, Backbone, events, transformations, coreTransformations) {
+ 'smartxml/core',
+ 'smartxml/fragments'
+], function($, _, Backbone, events, transformations, coreTransformations, fragments) {
'use strict';
/* globals Node */
this.loadXML(xml);
};
-$.extend(Document.prototype, Backbone.Events, {
+$.extend(Document.prototype, Backbone.Events, fragments, {
ElementNodeFactory: ElementNode,
TextNodeFactory: TextNode,
}
return $document[0];
}, configurable: true});
+ },
+
+ createFragment: function(Type, params) {
+ if(!Type.prototype instanceof fragments.Fragment) {
+ throw new Error('Can\'t create a fragment: `Type` is not a valid Fragment');
+ }
+ return new Type(this, params);
}
});