From 0ee14b1c718895bbc4034df7f6f35c0eabf06838 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 23 Jan 2014 09:37:47 +0100 Subject: [PATCH 1/1] smartxml: implementing merging of inserted text nodes if necessary This ensures that document stays in a normalized state, i.e. without adjacent text nodes. Up until now it was the client code responsibility not to denormalize the document. --- src/smartxml/core.js | 74 +++++++++++++++++++++++++++++------ src/smartxml/smartxml.test.js | 62 +++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 13 deletions(-) diff --git a/src/smartxml/core.js b/src/smartxml/core.js index 4d3c2c1..ae6d20e 100644 --- a/src/smartxml/core.js +++ b/src/smartxml/core.js @@ -8,16 +8,20 @@ var _ = require('libs/underscore'), var INSERTION = function(implementation) { - var toret = function(node) { + var toret = function(node, options) { var insertion = this.getNodeInsertion(node), nodeWasContained = this.document.containsNode(insertion.ofNode), - nodeParent; + nodeParent, + returned; + options = options || {}; if(!(this.document.containsNode(this))) { nodeParent = insertion.ofNode.parent(); } - implementation.call(this, insertion.ofNode.nativeNode); - this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent, nodeWasContained); - return insertion.ofNode; + returned = implementation.call(this, insertion.ofNode); + if(!options.silent && returned.sameNode(insertion.ofNode)) { + this.triggerChangeEvent(insertion.insertsNew ? 'nodeAdded' : 'nodeMoved', {node: insertion.ofNode}, nodeParent, nodeWasContained); + } + return returned; }; return toret; }; @@ -40,12 +44,24 @@ var documentNodeTransformations = { return toret; }, - after: INSERTION(function(nativeNode) { - return this._$.after(nativeNode); + after: INSERTION(function(node) { + var next = this.next(); + if(next && next.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) { + next.setText(node.getText() + next.getText()); + return next; + } + this._$.after(node.nativeNode); + return node; }), - before: INSERTION(function(nativeNode) { - return this._$.before(nativeNode); + before: INSERTION(function(node) { + var prev = this.prev(); + if(prev && prev.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) { + prev.setText(prev.getText() + node.getText()); + return prev; + } + this._$.before(node.nativeNode); + return node; }), wrapWith: function(node) { @@ -116,12 +132,26 @@ var elementNodeTransformations = { } }, - append: INSERTION(function(nativeNode) { - this._$.append(nativeNode); + append: INSERTION(function(node) { + var last = _.last(this.contents()); + if(last && last.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) { + last.setText(last.getText() + node.getText()); + return last; + } else { + this._$.append(node.nativeNode); + return node; + } }), - prepend: INSERTION(function(nativeNode) { - this._$.prepend(nativeNode); + prepend: INSERTION(function(node) { + var first = this.contents()[0]; + if(first && first.nodeType === Node.TEXT_NODE && node.nodeType === Node.TEXT_NODE) { + first.setText(node.getText() + first.getText()); + return first; + } else { + this._$.prepend(node.nativeNode); + return node; + } }), insertAtIndex: function(nativeNode, index) { @@ -199,6 +229,24 @@ var textNodeTransformations = { } }, + before: INSERTION(function(node) { + if(node.nodeType === Node.TEXT_NODE) { + this.prependText(node.getText()); + return this; + } else { + return this.__super__.before(node, {silent:true}); + } + }), + + after: INSERTION(function(node) { + if(node.nodeType === Node.TEXT_NODE) { + this.appendText(node.getText()); + return this; + } else { + return this.__super__.after(node, {silent:true}); + } + }), + appendText: function(text) { this.nativeNode.data = this.nativeNode.data + text; this.triggerTextChangeEvent(); diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index e6f0baa..542237a 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -458,6 +458,68 @@ describe('smartxml', function() { expect(node1.contents()[1].sameNode(node2)).to.be.true; }); + describe('adding text nodes', function() { + it('merges text nodes on append', function() { + var doc = getDocumentFromXML('text1'), + returned; + returned = doc.root.append({text: 'text2'}); + expect(doc.root.contents().length).to.equal(1); + expect(returned.sameNode(doc.root.contents()[0])).to.equal(true, 'modified node returned'); + expect(doc.root.contents()[0].getText()).to.equal('text1text2'); + }); + + it('merges text nodes on prepend', function() { + var doc = getDocumentFromXML('text1'), + returned; + returned = doc.root.prepend({text: 'text2'}); + expect(doc.root.contents().length).to.equal(1); + expect(returned.sameNode(doc.root.contents()[0])).to.equal(true, 'modified node returned'); + expect(doc.root.contents()[0].getText()).to.equal('text2text1'); + }); + + it('merges text nodes on before text node', function() { + var doc = getDocumentFromXML('text1'), + textNode = doc.root.contents()[0], + returned; + returned = textNode.before({text: 'text2'}); + expect(doc.root.contents().length).to.equal(1); + expect(returned.sameNode(doc.root.contents()[0])).to.equal(true, 'modified node returned'); + expect(doc.root.contents()[0].getText()).to.equal('text2text1'); + }); + + it('merges text nodes on after text node', function() { + var doc = getDocumentFromXML('text1'), + textNode = doc.root.contents()[0], + returned; + returned = textNode.after({text: 'text2'}); + expect(doc.root.contents().length).to.equal(1); + expect(returned.sameNode(doc.root.contents()[0])).to.equal(true, 'modified node returned'); + expect(doc.root.contents()[0].getText()).to.equal('text1text2'); + }); + + it('merges text nodes on before element node', function() { + var doc = getDocumentFromXML('text1
'), + textNode = doc.root.contents()[0], + div = doc.root.contents()[1], + returned; + returned = div.before({text: 'text2'}); + expect(doc.root.contents().length).to.equal(2); + expect(returned.sameNode(doc.root.contents()[0])).to.equal(true, 'modified node returned'); + expect(textNode.getText()).to.equal('text1text2'); + }); + + it('merges text nodes on after element node', function() { + var doc = getDocumentFromXML('
text1
'), + textNode = doc.root.contents()[1], + div = doc.root.contents()[0], + returned; + returned = div.after({text: 'text2'}); + expect(doc.root.contents().length).to.equal(2); + expect(returned.sameNode(doc.root.contents()[1])).to.equal(true, 'modified node returned'); + expect(textNode.getText()).to.equal('text2text1'); + }); + }); + it('wraps element node with another element node', function() { var node = elementNodeFromXML('
'), wrapper = elementNodeFromXML(''); -- 2.20.1