From 3b0a39df2e007f33b027b665f0f42604aecfc8b9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 24 Oct 2013 10:38:28 +0200 Subject: [PATCH 01/16] smartxml: setting text on text node --- src/smartxml/smartxml.js | 4 ++++ src/smartxml/smartxml.test.js | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index b81b0f0..53eefed 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -209,6 +209,10 @@ $.extend(TextNode.prototype, DocumentNode.prototype, { return this.nativeNode.data; }, + setText: function(text) { + this.nativeNode.data = text; + }, + appendText: function(text) { this.nativeNode.data = this.nativeNode.data + text; }, diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 2ccfa6d..b33ef1f 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -158,6 +158,16 @@ describe('smartxml', function() { }); }); + describe('Basic TextNode properties', function() { + it('can have its text set', function() { + var node = elementNodeFromXML('
Alice
'), + textNode = node.contents()[0]; + + textNode.setText('Cat'); + expect(textNode.getText()).to.equal('Cat'); + }); + }); + describe('Manipulations', function() { it('appends element node to another element node', function() { -- 2.20.1 From 9a93c89991001d4ed088bee3b20cea374aa91d37 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 24 Oct 2013 10:45:20 +0200 Subject: [PATCH 02/16] smartxml: changing TextNode text emits nodeTextChange event --- src/smartxml/smartxml.js | 8 ++++++++ src/smartxml/smartxml.test.js | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 53eefed..6e00a7d 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -211,14 +211,22 @@ $.extend(TextNode.prototype, DocumentNode.prototype, { 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(); + }, + + triggerTextChangeEvent: function() { + var event = new events.ChangeEvent('nodeTextChange', {node: this}); + this.document.trigger('change', event); } }); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index b33ef1f..15fe394 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -166,6 +166,18 @@ describe('smartxml', function() { textNode.setText('Cat'); expect(textNode.getText()).to.equal('Cat'); }); + + it('emits nodeTextChange', function() { + var node = elementNodeFromXML('
Alice
'), + textNode = node.contents()[0], + spy = sinon.spy(); + + textNode.document.on('change', spy); + textNode.setText('Cat'); + + var event = spy.args[0][0]; + expect(event.type).to.equal('nodeTextChange'); + }); }); describe('Manipulations', function() { -- 2.20.1 From 02aa7e89997fd35f7c57dd300507b5dd9ad64992 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 24 Oct 2013 11:33:38 +0200 Subject: [PATCH 03/16] smartxml: handling undefined value sent to DocumentNode.sameNode --- src/smartxml/smartxml.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 6e00a7d..7c68e06 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -29,7 +29,7 @@ $.extend(DocumentNode.prototype, { detach: function() { this._$.detach(); }, sameNode: function(otherNode) { - return this.nativeNode === otherNode.nativeNode; + return otherNode && this.nativeNode === otherNode.nativeNode; }, parent: function() { -- 2.20.1 From 58425daa1aa7e717e92eaec170262de31ed47bc0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 24 Oct 2013 12:51:19 +0200 Subject: [PATCH 04/16] smartxml: wrapping TextNode content A little bit different api to introduce additional features. --- src/smartxml/smartxml.js | 5 ++++- src/smartxml/smartxml.test.js | 27 +++++++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 7c68e06..2e507db 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -41,10 +41,13 @@ $.extend(DocumentNode.prototype, { }, wrapWith: function(node) { + node = node instanceof ElementNode ? node : this.document.createElementNode(node); + if(this.parent()) { this.before(node); } node.append(this); + return node; }, triggerChangeEvent: function(type, metaData) { @@ -245,7 +248,7 @@ $.extend(Document.prototype, Backbone.Events, { createElementNode: function(from) { if(!(from instanceof HTMLElement)) { - from = $('<' + from.tagName + '>'); + from = $('<' + from.tagName + '>')[0]; } return new this.ElementNodeFactory(from, this); }, diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 15fe394..58156c2 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -178,6 +178,23 @@ describe('smartxml', function() { var event = spy.args[0][0]; expect(event.type).to.equal('nodeTextChange'); }); + + describe('Wrapping TextNode contents', function() { + + it('wraps DocumentTextElement', function() { + var node = elementNodeFromXML('
Alice
'), + textNode = node.contents()[0]; + + var returned = textNode.wrapWith({tagName: 'header'}), + parent = textNode.parent(), + parent2 = node.contents()[0]; + + expect(returned.sameNode(parent)).to.be.equal(true, 'wrapper is a parent'); + expect(returned.sameNode(parent2)).to.be.equal(true, 'wrapper has a correct parent'); + expect(returned.getTagName()).to.equal('header'); + }); + }); + }); describe('Manipulations', function() { @@ -197,16 +214,6 @@ describe('smartxml', function() { expect(node.parent().sameNode(wrapper)).to.be.true; }); - it('wraps text node with element node', function() { - var node = elementNodeFromXML('
Alice
'), - textNode = node.contents()[0], - wrapper = elementNodeFromXML(''); - - textNode.wrapWith(wrapper); - expect(textNode.parent().sameNode(wrapper)).to.be.true; - expect(node.contents()).to.have.length(1); - }); - it('unwraps element node contents', function() { var node = elementNodeFromXML('
Alice
has propably a cat
!
'), outerDiv = node.contents()[1]; -- 2.20.1 From 676e34c6c2ce383f61cb4b20fec74aaba0dcbdbd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 24 Oct 2013 13:32:11 +0200 Subject: [PATCH 05/16] smartxml: TextNode.before/after --- src/smartxml/smartxml.js | 14 +++++++++++++- src/smartxml/smartxml.test.js | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 2e507db..47e2b2e 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -36,8 +36,16 @@ $.extend(DocumentNode.prototype, { return this.nativeNode.parentNode ? this.document.createElementNode(this.nativeNode.parentNode) : null; }, + after: function(node) { + node = node instanceof ElementNode ? node : this.document.createElementNode(node); + this._$.after(node.nativeNode); + return node; + }, + before: function(node) { + node = node instanceof ElementNode ? node : this.document.createElementNode(node); this._$.before(node.nativeNode); + return node; }, wrapWith: function(node) { @@ -248,7 +256,11 @@ $.extend(Document.prototype, Backbone.Events, { createElementNode: function(from) { if(!(from instanceof HTMLElement)) { - from = $('<' + from.tagName + '>')[0]; + if(from.text) { + from = document.createTextNode(from.text); + } else { + from = $('<' + from.tagName + '>')[0]; + } } return new this.ElementNodeFactory(from, this); }, diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 58156c2..31d854a 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -179,6 +179,20 @@ describe('smartxml', function() { expect(event.type).to.equal('nodeTextChange'); }); + it('puts NodeElement after itself', function() { + var node = elementNodeFromXML('
Alice
'), + textNode = node.contents()[0], + returned = textNode.after({tagName:'div'}); + expect(returned.sameNode(node.contents()[1])).to.be.true; + }); + + it('puts NodeElement before itself', function() { + var node = elementNodeFromXML('
Alice
'), + textNode = node.contents()[0], + returned = textNode.before({tagName:'div'}); + expect(returned.sameNode(node.contents()[0])).to.be.true; + }); + describe('Wrapping TextNode contents', function() { it('wraps DocumentTextElement', function() { -- 2.20.1 From 6a5fbef4db2934efd05c251124bd2b62180fe549 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 24 Oct 2013 15:02:16 +0200 Subject: [PATCH 06/16] smartxml: fixing inheritance to support for instanceof operator to work properly --- src/smartxml/smartxml.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 47e2b2e..a396d3e 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -67,8 +67,9 @@ $.extend(DocumentNode.prototype, { var ElementNode = function(nativeNode, document) { DocumentNode.call(this, nativeNode, document); }; +ElementNode.prototype = Object.create(DocumentNode.prototype); -$.extend(ElementNode.prototype, DocumentNode.prototype, { +$.extend(ElementNode.prototype, { nodeType: Node.ELEMENT_NODE, setData: function(key, value) { @@ -212,8 +213,9 @@ $.extend(ElementNode.prototype, DocumentNode.prototype, { var TextNode = function(nativeNode, document) { DocumentNode.call(this, nativeNode, document); }; +TextNode.prototype = Object.create(DocumentNode.prototype); -$.extend(TextNode.prototype, DocumentNode.prototype, { +$.extend(TextNode.prototype, { nodeType: Node.TEXT_NODE, getText: function() { -- 2.20.1 From 6f88a272f930c30868c43b724368b4bcf6f0a3a7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 24 Oct 2013 15:30:43 +0200 Subject: [PATCH 07/16] smartxml: wrapping part of text in TextElement --- src/smartxml/smartxml.js | 73 +++++++++++++++++++++++++++++++++-- src/smartxml/smartxml.test.js | 38 ++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index a396d3e..d8b6bf0 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -149,8 +149,9 @@ $.extend(ElementNode.prototype, { return toret; }, - append: function(documentNode) { - this._$.append(documentNode.nativeNode); + append: function(node) { + node = node instanceof DocumentNode ? node : this.document.createElementNode(node); + this._$.append(node.nativeNode); }, unwrapContent: function() { @@ -237,6 +238,20 @@ $.extend(TextNode.prototype, { 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: {tag: desc.tagName, attrs: desc.attrs} + }); + } else { + return DocumentNode.prototype.wrapWith.call(this, desc); + } + }, + triggerTextChangeEvent: function() { var event = new events.ChangeEvent('nodeTextChange', {node: this}); this.document.trigger('change', event); @@ -261,7 +276,13 @@ $.extend(Document.prototype, Backbone.Events, { if(from.text) { from = document.createTextNode(from.text); } else { - from = $('<' + from.tagName + '>')[0]; + var node = $('<' + from.tagName + '>'); + + _.keys(from.attrs || {}).forEach(function(key) { + node.attr(key, from.attrs[key]); + }); + + from = node[0]; } } return new this.ElementNodeFactory(from, this); @@ -281,6 +302,52 @@ $.extend(Document.prototype, Backbone.Events, { toXML: function() { return this.root.toXML(); + }, + + _wrapText: function(params) { + params = _.extend({textNodeIdx: 0}, params); + if(typeof params.textNodeIdx === 'number') { + params.textNodeIdx = [params.textNodeIdx]; + } + + var contentsInside = params.inside.contents(), + idx1 = Math.min.apply(Math, params.textNodeIdx), + idx2 = Math.max.apply(Math, params.textNodeIdx), + textNode1 = contentsInside[idx1], + textNode2 = contentsInside[idx2], + sameNode = textNode1.sameNode(textNode2), + prefixOutside = textNode1.getText().substr(0, params.offsetStart), + prefixInside = textNode1.getText().substr(params.offsetStart), + suffixInside = textNode2.getText().substr(0, params.offsetEnd), + suffixOutside = textNode2.getText().substr(params.offsetEnd) + ; + + var wrapperElement = this.createElementNode({tagName: params._with.tag, attrs: params._with.attrs}); + textNode1.after(wrapperElement); + textNode1.detach(); + + if(prefixOutside.length > 0) { + wrapperElement.before({text:prefixOutside}); + } + if(sameNode) { + var core = textNode1.getText().substr(params.offsetStart, params.offsetEnd - params.offsetStart); + wrapperElement.append({text: core}); + } else { + textNode2.detach(); + if(prefixInside.length > 0) { + wrapperElement.append({text: prefixInside}); + } + for(var i = idx1 + 1; i < idx2; i++) { + wrapperElement.append(contentsInside[i]); + } + if(suffixInside.length > 0) { + wrapperElement.append({text: suffixInside}); + } + } + if(suffixOutside.length > 0) { + wrapperElement.after({text: suffixOutside}); + } + return wrapperElement; } }); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 31d854a..a7f3fca 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -207,6 +207,44 @@ describe('smartxml', function() { expect(returned.sameNode(parent2)).to.be.equal(true, 'wrapper has a correct parent'); expect(returned.getTagName()).to.equal('header'); }); + + describe('wrapping part of DocumentTextElement', function() { + [{start: 5, end: 12}, {start: 12, end: 5}].forEach(function(offsets) { + it('wraps in the middle ' + offsets.start + '/' + offsets.end, function() { + var node = elementNodeFromXML('
Alice has a cat
'), + textNode = node.contents()[0]; + + var returned = textNode.wrapWith({tagName: 'header', attrs: {'attr1': 'value1'}, start: offsets.start, end: offsets.end}), + contents = node.contents(); + + expect(contents.length).to.equal(3); + + expect(contents[0].nodeType).to.be.equal(Node.TEXT_NODE, 'first node is text node'); + expect(contents[0].getText()).to.equal('Alice'); + + expect(contents[1].sameNode(returned)).to.be.true; + expect(returned.getTagName()).to.equal('header'); + expect(returned.getAttr('attr1')).to.equal('value1'); + expect(contents[1].contents().length).to.equal(1, 'wrapper has one node inside'); + expect(contents[1].contents()[0].getText()).to.equal(' has a '); + + expect(contents[2].nodeType).to.be.equal(Node.TEXT_NODE, 'third node is text node'); + expect(contents[2].getText()).to.equal('cat'); + }); + }); + + it('wraps whole text inside DocumentTextElement if offsets span entire content', function() { + var node = elementNodeFromXML('
Alice has a cat
'), + textNode = node.contents()[0]; + + textNode.wrapWith({tagName: 'header', start: 0, end: 15}); + + var contents = node.contents(); + expect(contents.length).to.equal(1); + expect(contents[0].getTagName()).to.equal('header'); + expect(contents[0].contents()[0].getText()).to.equal('Alice has a cat'); + }); + }); }); }); -- 2.20.1 From 5625717737822065383ef29c6b5709e9984c3508 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 09:54:13 +0200 Subject: [PATCH 08/16] smartxml: Wrapping text spanning multiple sibling TextNodes --- src/smartxml/smartxml.js | 8 ++++++++ src/smartxml/smartxml.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index d8b6bf0..56e78af 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -200,6 +200,10 @@ $.extend(ElementNode.prototype, { }; }, + wrapText: function(params) { + return this.document._wrapText(_.extend({inside: this}, params)); + }, + toXML: function() { var wrapper = $('
'); wrapper.append(this._getXMLDOMToDump()); @@ -321,6 +325,10 @@ $.extend(Document.prototype, Backbone.Events, { suffixInside = textNode2.getText().substr(0, params.offsetEnd), suffixOutside = textNode2.getText().substr(params.offsetEnd) ; + + if(!(textNode1.parent().sameNode(textNode2.parent()))) { + throw new Error('Wrapping text in non-sibling text nodes not supported.'); + } var wrapperElement = this.createElementNode({tagName: params._with.tag, attrs: params._with.attrs}); textNode1.after(wrapperElement); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index a7f3fca..7880017 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -278,6 +278,32 @@ describe('smartxml', function() { expect(node.contents()[2].getText()).to.equal(' a cat!'); }); + describe('Wrapping text', function() { + it('wraps text spanning multiple sibling TextNodes', function() { + var section = elementNodeFromXML('
Alice has a small cat
'), + wrapper = section.wrapText({ + _with: {tag: 'span', attrs: {'attr1': 'value1'}}, + offsetStart: 6, + offsetEnd: 4, + textNodeIdx: [0,2] + }); + + expect(section.contents().length).to.equal(2); + expect(section.contents()[0].nodeType).to.equal(Node.TEXT_NODE); + expect(section.contents()[0].getText()).to.equal('Alice '); + + var wrapper2 = section.contents()[1]; + expect(wrapper2.sameNode(wrapper)).to.be.true; + + var wrapperContents = wrapper.contents(); + expect(wrapperContents.length).to.equal(3); + expect(wrapperContents[0].getText()).to.equal('has a '); + + expect(wrapperContents[1].nodeType).to.equal(Node.ELEMENT_NODE); + expect(wrapperContents[1].contents().length).to.equal(1); + expect(wrapperContents[1].contents()[0].getText()).to.equal('small'); + }); + }); }); describe('Serializing document to WLXML', function() { -- 2.20.1 From 9156bcda05de78865d7f2ef202824dd2a1a03ea1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 10:28:01 +0200 Subject: [PATCH 09/16] smartxml: ElementNode.prepend --- src/smartxml/smartxml.js | 5 +++++ src/smartxml/smartxml.test.js | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 56e78af..91dd8bb 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -154,6 +154,11 @@ $.extend(ElementNode.prototype, { this._$.append(node.nativeNode); }, + prepend: function(node) { + node = node instanceof DocumentNode ? node : this.document.createElementNode(node); + this._$.prepend(node.nativeNode); + }, + unwrapContent: function() { var parent = this.parent(); if(!parent) { diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 7880017..29c4244 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -258,6 +258,16 @@ describe('smartxml', function() { expect(node1.contents()[0].sameNode(node2)).to.be.true; }); + it('prepends element node to another element node', function() { + var node1 = elementNodeFromParams({tag: 'div'}), + node2 = elementNodeFromParams({tag: 'a'}), + node3 = elementNodeFromParams({tag: 'p'}); + node1.prepend(node2); + node1.prepend(node3); + expect(node1.contents()[0].sameNode(node3)).to.be.true; + expect(node1.contents()[1].sameNode(node2)).to.be.true; + }); + it('wraps element node with another element node', function() { var node = elementNodeFromXML('
'), wrapper = elementNodeFromXML(''); -- 2.20.1 From 07ddee75789e301b49b252405196f05de2367f74 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 10:29:16 +0200 Subject: [PATCH 10/16] smartxml: test fix - actually make sure we are appending, not prepending --- src/smartxml/smartxml.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 29c4244..b12f127 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -253,9 +253,12 @@ describe('smartxml', function() { it('appends element node to another element node', function() { var node1 = elementNodeFromParams({tag: 'div'}), - node2 = elementNodeFromParams({tag: 'a'}); + node2 = elementNodeFromParams({tag: 'a'}), + node3 = elementNodeFromParams({tag: 'p'}); node1.append(node2); + node1.append(node3); expect(node1.contents()[0].sameNode(node2)).to.be.true; + expect(node1.contents()[1].sameNode(node3)).to.be.true; }); it('prepends element node to another element node', function() { -- 2.20.1 From 5281c07b13ea63c96602a232e61332fb41ca0779 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 10:33:38 +0200 Subject: [PATCH 11/16] smartxml: wrapping sibling nodes --- src/smartxml/smartxml.js | 41 ++++++++++++++++++++++++++++- src/smartxml/smartxml.test.js | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 91dd8bb..28a88ee 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -26,7 +26,10 @@ $.extend(DocumentNode.prototype, { this._$ = $(nativeNode); }, - detach: function() { this._$.detach(); }, + detach: function() { + this._$.detach(); + return this; + }, sameNode: function(otherNode) { return otherNode && this.nativeNode === otherNode.nativeNode; @@ -313,6 +316,42 @@ $.extend(Document.prototype, Backbone.Events, { return this.root.toXML(); }, + wrapNodes: function(params) { + if(!(params.element1.parent().sameNode(params.element2.parent()))) { + throw new Error('Wrapping non-sibling nodes not supported.'); + } + + var parent = params.element1.parent(), + parentContents = parent.contents(), + wrapper = this.createElementNode({ + tagName: params._with.tagName, + attrs: params._with.attrs}), + idx1 = parent.indexOf(params.element1), + idx2 = parent.indexOf(params.element2); + + 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()); + } + + insertingTarget[insertingMethod](wrapper); + return wrapper; + }, + _wrapText: function(params) { params = _.extend({textNodeIdx: 0}, params); if(typeof params.textNodeIdx === 'number') { diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index b12f127..0826458 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -317,6 +317,55 @@ describe('smartxml', function() { expect(wrapperContents[1].contents()[0].getText()).to.equal('small'); }); }); + + describe('Wrapping Nodes', function() { + it('wraps multiple sibling nodes', function() { + var section = elementNodeFromXML('
Alice
has
a cat
'), + aliceText = section.contents()[0], + firstDiv = section.contents()[1], + lastDiv = section.contents()[section.contents().length -1]; + + var returned = section.document.wrapNodes({ + element1: aliceText, + element2: lastDiv, + _with: {tagName: 'header'} + }); + + var sectionContents = section.contents(), + header = sectionContents[0], + headerContents = header.contents(); + + expect(sectionContents).to.have.length(1); + expect(header.sameNode(returned)).to.equal(true, 'wrapper returned'); + expect(header.parent().sameNode(section)).to.be.true; + expect(headerContents).to.have.length(3); + expect(headerContents[0].sameNode(aliceText)).to.equal(true, 'first node wrapped'); + expect(headerContents[1].sameNode(firstDiv)).to.equal(true, 'second node wrapped'); + expect(headerContents[2].sameNode(lastDiv)).to.equal(true, 'third node wrapped'); + }); + + it('wraps multiple sibling Elements - middle case', function() { + var section = elementNodeFromXML('
'), + div2 = section.contents()[1], + div3 = section.contents()[2]; + + section.document.wrapNodes({ + element1: div2, + element2: div3, + _with: {tagName: 'header'} + }); + + var sectionContents = section.contents(), + header = sectionContents[1], + headerChildren = header.contents(); + + expect(sectionContents).to.have.length(3); + expect(headerChildren).to.have.length(2); + expect(headerChildren[0].sameNode(div2)).to.equal(true, 'first node wrapped'); + expect(headerChildren[1].sameNode(div3)).to.equal(true, 'second node wrapped'); + }); + }); + }); describe('Serializing document to WLXML', function() { -- 2.20.1 From 180cd85dd5dcdcb29eb1a4626e3c6412558caddb Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 12:35:59 +0200 Subject: [PATCH 12/16] smartxml - emitting nodeAdded event on node inserting operations --- src/smartxml/smartxml.js | 50 ++++++++++++++++---- src/smartxml/smartxml.test.js | 86 +++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 10 deletions(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 28a88ee..6b02779 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -40,15 +40,21 @@ $.extend(DocumentNode.prototype, { }, after: function(node) { - node = node instanceof ElementNode ? node : this.document.createElementNode(node); - this._$.after(node.nativeNode); - return node; + var insertion = this.getNodeInsertion(node); + this._$.after(insertion.ofNode.nativeNode); + if(insertion.insertsNew) { + this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); + } + return insertion.ofNode; }, before: function(node) { - node = node instanceof ElementNode ? node : this.document.createElementNode(node); - this._$.before(node.nativeNode); - return node; + var insertion = this.getNodeInsertion(node); + this._$.before(insertion.ofNode.nativeNode); + if(insertion.insertsNew) { + this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); + } + return insertion.ofNode; }, wrapWith: function(node) { @@ -65,6 +71,18 @@ $.extend(DocumentNode.prototype, { var event = new events.ChangeEvent(type, $.extend({node: this}, metaData || {})); this.document.trigger('change', event); }, + + getNodeInsertion: function(node) { + var insertion = {}; + if(node instanceof DocumentNode) { + insertion.ofNode = node; + insertion.insertsNew = !this.document.containsNode(node); + } else { + insertion.ofNode = this.document.createElementNode(node); + insertion.insertsNew = true; + } + return insertion; + } }); var ElementNode = function(nativeNode, document) { @@ -153,13 +171,21 @@ $.extend(ElementNode.prototype, { }, append: function(node) { - node = node instanceof DocumentNode ? node : this.document.createElementNode(node); - this._$.append(node.nativeNode); + var insertion = this.getNodeInsertion(node); + this._$.append(insertion.ofNode.nativeNode); + if(insertion.insertsNew) { + this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); + } + return insertion.ofNode; }, prepend: function(node) { - node = node instanceof DocumentNode ? node : this.document.createElementNode(node); - this._$.prepend(node.nativeNode); + var insertion = this.getNodeInsertion(node); + this._$.prepend(insertion.ofNode.nativeNode); + if(insertion.insertsNew) { + this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); + } + return insertion.ofNode; }, unwrapContent: function() { @@ -316,6 +342,10 @@ $.extend(Document.prototype, Backbone.Events, { return this.root.toXML(); }, + containsNode: function(node) { + return node._$.parents().index(this.root._$) !== -1; + }, + wrapNodes: function(params) { if(!(params.element1.parent().sameNode(params.element2.parent()))) { throw new Error('Wrapping non-sibling nodes not supported.'); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 0826458..4f9a72a 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -368,6 +368,92 @@ describe('smartxml', function() { }); + describe('Events', function() { + it('emits nodeAdded event when appending new node', function() { + var node = elementNodeFromXML('
'), + spy = sinon.spy(); + node.document.on('change', spy); + + var appended = node.append({tagName:'div'}), + event = spy.args[0][0]; + expect(event.type).to.equal('nodeAdded'); + expect(event.meta.node.sameNode(appended)).to.be.true; + }); + + it('doesn\'t emit nodeAdded when appending aready existing node', function() { + var node = elementNodeFromXML('
'), + a = node.contents()[0], + b = node.contents()[1], + spy = sinon.spy(); + node.document.on('change', spy); + a.append(b); + expect(spy.callCount).to.equal(0); + }); + + it('emits nodeAdded event when prepending new node', function() { + var node = elementNodeFromXML('
'), + spy = sinon.spy(); + node.document.on('change', spy); + + var prepended = node.prepend({tagName:'div'}), + event = spy.args[0][0]; + expect(event.type).to.equal('nodeAdded'); + expect(event.meta.node.sameNode(prepended)).to.be.true; + }); + + it('doesn\'t emit nodeAdded when prepending aready existing node', function() { + var node = elementNodeFromXML('
'), + a = node.contents()[0], + b = node.contents()[1], + spy = sinon.spy(); + node.document.on('change', spy); + a.prepend(b); + expect(spy.callCount).to.equal(0); + }); + + it('emits nodeAdded event when inserting node after another', function() { + var node = elementNodeFromXML('
').contents()[0], + spy = sinon.spy(); + node.document.on('change', spy); + + var inserted = node.after({tagName:'div'}), + event = spy.args[0][0]; + expect(event.type).to.equal('nodeAdded'); + expect(event.meta.node.sameNode(inserted)).to.be.true; + }); + + it('doesn\'t emit nodeAdded when inserting aready existing node after another', function() { + var node = elementNodeFromXML('
'), + a = node.contents()[0], + b = node.contents()[1], + spy = sinon.spy(); + node.document.on('change', spy); + b.after(a); + expect(spy.callCount).to.equal(0); + }); + + it('emits nodeAdded event when inserting node before another', function() { + var node = elementNodeFromXML('
').contents()[0], + spy = sinon.spy(); + node.document.on('change', spy); + + var inserted = node.before({tagName:'div'}), + event = spy.args[0][0]; + expect(event.type).to.equal('nodeAdded'); + expect(event.meta.node.sameNode(inserted)).to.be.true; + }); + + it('doesn\'t emit nodeAdded when inserting aready existing node before another', function() { + var node = elementNodeFromXML('
'), + a = node.contents()[0], + b = node.contents()[1], + spy = sinon.spy(); + node.document.on('change', spy); + a.before(b); + expect(spy.callCount).to.equal(0); + }); + }); + describe('Serializing document to WLXML', function() { it('keeps document intact when no changes have been made', function() { var xmlIn = '
Alice
has
a cat!
', -- 2.20.1 From ea6df9066799de1d2bfda2132ad06c2d8f57582e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 15:12:16 +0200 Subject: [PATCH 13/16] smartxm: emitting nodeMoved when inserting existing node into different place --- src/smartxml/smartxml.js | 16 ++++---------- src/smartxml/smartxml.test.js | 41 +++++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 6b02779..7e0d9b3 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -42,18 +42,14 @@ $.extend(DocumentNode.prototype, { after: function(node) { var insertion = this.getNodeInsertion(node); this._$.after(insertion.ofNode.nativeNode); - if(insertion.insertsNew) { - this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); - } + this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}); return insertion.ofNode; }, before: function(node) { var insertion = this.getNodeInsertion(node); this._$.before(insertion.ofNode.nativeNode); - if(insertion.insertsNew) { - this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); - } + this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}); return insertion.ofNode; }, @@ -173,18 +169,14 @@ $.extend(ElementNode.prototype, { append: function(node) { var insertion = this.getNodeInsertion(node); this._$.append(insertion.ofNode.nativeNode); - if(insertion.insertsNew) { - this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); - } + this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}); return insertion.ofNode; }, prepend: function(node) { var insertion = this.getNodeInsertion(node); this._$.prepend(insertion.ofNode.nativeNode); - if(insertion.insertsNew) { - this.triggerChangeEvent('nodeAdded', {node: insertion.ofNode}); - } + this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}); return insertion.ofNode; }, diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 4f9a72a..4c9b860 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -380,14 +380,19 @@ describe('smartxml', function() { expect(event.meta.node.sameNode(appended)).to.be.true; }); - it('doesn\'t emit nodeAdded when appending aready existing node', function() { + it('emits nodeMoved when appending aready existing node', function() { var node = elementNodeFromXML('
'), a = node.contents()[0], b = node.contents()[1], spy = sinon.spy(); node.document.on('change', spy); - a.append(b); - expect(spy.callCount).to.equal(0); + + var appended = a.append(b), + event = spy.args[0][0]; + + expect(spy.callCount).to.equal(1); + expect(event.type).to.equal('nodeMoved'); + expect(event.meta.node.sameNode(appended)).to.be.true; }); it('emits nodeAdded event when prepending new node', function() { @@ -401,14 +406,18 @@ describe('smartxml', function() { expect(event.meta.node.sameNode(prepended)).to.be.true; }); - it('doesn\'t emit nodeAdded when prepending aready existing node', function() { + it('emits nodeMoved when prepending aready existing node', function() { var node = elementNodeFromXML('
'), a = node.contents()[0], b = node.contents()[1], spy = sinon.spy(); node.document.on('change', spy); - a.prepend(b); - expect(spy.callCount).to.equal(0); + + var prepended = a.prepend(b), + event = spy.args[0][0]; + expect(spy.callCount).to.equal(1); + expect(event.type).to.equal('nodeMoved'); + expect(event.meta.node.sameNode(prepended)).to.be.true; }); it('emits nodeAdded event when inserting node after another', function() { @@ -422,14 +431,18 @@ describe('smartxml', function() { expect(event.meta.node.sameNode(inserted)).to.be.true; }); - it('doesn\'t emit nodeAdded when inserting aready existing node after another', function() { + it('emits nodeMoved when inserting aready existing node after another', function() { var node = elementNodeFromXML('
'), a = node.contents()[0], b = node.contents()[1], spy = sinon.spy(); node.document.on('change', spy); - b.after(a); - expect(spy.callCount).to.equal(0); + var inserted = b.after(a), + event = spy.args[0][0]; + + expect(spy.callCount).to.equal(1); + expect(event.type).to.equal('nodeMoved'); + expect(event.meta.node.sameNode(inserted)).to.be.true; }); it('emits nodeAdded event when inserting node before another', function() { @@ -443,14 +456,18 @@ describe('smartxml', function() { expect(event.meta.node.sameNode(inserted)).to.be.true; }); - it('doesn\'t emit nodeAdded when inserting aready existing node before another', function() { + it('emits nodeAdded when inserting aready existing node before another', function() { var node = elementNodeFromXML('
'), a = node.contents()[0], b = node.contents()[1], spy = sinon.spy(); node.document.on('change', spy); - a.before(b); - expect(spy.callCount).to.equal(0); + var inserted = a.before(b), + event = spy.args[0][0]; + + expect(spy.callCount).to.equal(1); + expect(event.type).to.equal('nodeMoved'); + expect(event.meta.node.sameNode(inserted)).to.be.true; }); }); -- 2.20.1 From 02ea7eed98a6826504c4472a9309010277026a03 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 15:39:15 +0200 Subject: [PATCH 14/16] smartxml: fixing arguments name in wrapText --- src/smartxml/smartxml.js | 4 ++-- src/smartxml/smartxml.test.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 7e0d9b3..b469068 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -275,7 +275,7 @@ $.extend(TextNode.prototype, { textNodeIdx: this.parent().indexOf(this), offsetStart: Math.min(desc.start, desc.end), offsetEnd: Math.max(desc.start, desc.end), - _with: {tag: desc.tagName, attrs: desc.attrs} + _with: {tagName: desc.tagName, attrs: desc.attrs} }); } else { return DocumentNode.prototype.wrapWith.call(this, desc); @@ -396,7 +396,7 @@ $.extend(Document.prototype, Backbone.Events, { throw new Error('Wrapping text in non-sibling text nodes not supported.'); } - var wrapperElement = this.createElementNode({tagName: params._with.tag, attrs: params._with.attrs}); + var wrapperElement = this.createElementNode({tagName: params._with.tagName, attrs: params._with.attrs}); textNode1.after(wrapperElement); textNode1.detach(); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 4c9b860..e7aa8d4 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -295,7 +295,7 @@ describe('smartxml', function() { it('wraps text spanning multiple sibling TextNodes', function() { var section = elementNodeFromXML('
Alice has a small cat
'), wrapper = section.wrapText({ - _with: {tag: 'span', attrs: {'attr1': 'value1'}}, + _with: {tagName: 'span', attrs: {'attr1': 'value1'}}, offsetStart: 6, offsetEnd: 4, textNodeIdx: [0,2] @@ -307,6 +307,7 @@ describe('smartxml', function() { var wrapper2 = section.contents()[1]; expect(wrapper2.sameNode(wrapper)).to.be.true; + expect(wrapper.getTagName()).to.equal('span'); var wrapperContents = wrapper.contents(); expect(wrapperContents.length).to.equal(3); -- 2.20.1 From b5b63aaa73c53f6ba8091c8e8662ff478a1ecca7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 15:32:03 +0200 Subject: [PATCH 15/16] smartxml: DocumentNode.getIndex function for syntactic sugar --- src/smartxml/smartxml.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index b469068..b4036d3 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -78,6 +78,10 @@ $.extend(DocumentNode.prototype, { insertion.insertsNew = true; } return insertion; + }, + + getIndex: function() { + return this.parent().indexOf(this); } }); -- 2.20.1 From 24f79c15db362b3646ce15d0dc6d9c703040e892 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 25 Oct 2013 15:32:40 +0200 Subject: [PATCH 16/16] smartxml: fixing text node creation --- src/smartxml/smartxml.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index b4036d3..cbb2ba8 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -319,7 +319,13 @@ $.extend(Document.prototype, Backbone.Events, { from = node[0]; } } - return new this.ElementNodeFactory(from, this); + 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); }, createTextNode: function(nativeNode) { -- 2.20.1