From df1aaa5ae8434788bb67662782fe7e5324ac9786 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Fri, 24 Jan 2014 10:17:28 +0100 Subject: [PATCH] smartxml: Deleting arbitrary text --- src/smartxml/core.js | 46 +++++++++++++ src/smartxml/smartxml.js | 6 +- src/smartxml/smartxml.test.js | 118 ++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/src/smartxml/core.js b/src/smartxml/core.js index 65b7852..44be8d6 100644 --- a/src/smartxml/core.js +++ b/src/smartxml/core.js @@ -431,6 +431,52 @@ var documentTransformations = { this._defineDocumentProperties(insertion.ofNode._$); insertion.ofNode.triggerChangeEvent('nodeAdded'); return insertion.ofNode; + }, + deleteText: function(params) { + var ptr, prev, next, toDetach, middle, text; + + if(params.from.node.sameNode(params.to.node)) { + ptr = params.from.node; + text = ptr.getText(); + ptr.setText(text.substr(0, params.from.offset) + text.substr(params.to.offset)); + return; + } + + ptr = params.from.node; + ptr.setText(ptr.getText().substr(0, params.from.offset)); + next = ptr.next(); + while(next || ptr.parent()) { + if(next) { + if(next.sameNode(params.to.node) || (next.nodeType === Node.ELEMENT_NODE && next.containsNode(params.to.node))) { + middle = next; + break; + } else { + toDetach = next; + next = next.next(); + toDetach.detach(); + } + } else { + ptr = ptr.parent(); + next = ptr.next(); + } + } + + ptr = params.to.node; + ptr.setText(ptr.getText().substr(params.to.offset)); + prev = ptr.prev(); + while(prev || ptr.parent()) { + if(ptr.sameNode(middle)) { + break; + } + if(prev) { + toDetach = prev; + prev = prev.prev(); + toDetach.detach(); + } else { + ptr = ptr.parent(); + prev = ptr.prev(); + } + } } }; diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 8bf531b..d87ec54 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -203,6 +203,10 @@ $.extend(ElementNode.prototype, { return toret; }, + containsNode: function(node) { + return node && (node.nativeNode === this.nativeNode || node._$.parents().index(this._$) !== -1); + }, + toXML: function() { var wrapper = $('
'); wrapper.append(this._getXMLDOMToDump()); @@ -354,7 +358,7 @@ $.extend(Document.prototype, Backbone.Events, { }, containsNode: function(node) { - return this.root && (node.nativeNode === this.root.nativeNode || node._$.parents().index(this.root._$) !== -1); + return this.root && this.root.containsNode(node); }, getSiblingParents: function(params) { diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js index 542237a..140174e 100644 --- a/src/smartxml/smartxml.test.js +++ b/src/smartxml/smartxml.test.js @@ -644,6 +644,124 @@ describe('smartxml', function() { }); + var getTextNodes = function(text, doc) { + /* globals Node */ + var toret = []; + var search = function(node) { + node.contents().forEach(function(node) { + if(node.nodeType === Node.TEXT_NODE) { + if(node.getText() === text) { + toret.push(node); + } + } else { + search(node); + } + }); + }; + search(doc.root); + return toret; + }; + + var getTextNode = function(text, doc) { + var nodes = getTextNodes(text, doc), + error; + if(nodes.length === 0) { + error = 'Text not found'; + } else if(nodes.length > 1) { + error = 'Text not unique'; + } else if(nodes[0].getText() !== text) { + error = 'I was trying to cheat your test :('; + } + if(error) { + throw new Error(error); + } + return nodes[0]; + }; + + describe('Removing arbitrary text', function() { + it('removes within single text element', function() { + var doc = getDocumentFromXML('
Alice
'), + text = getTextNode('Alice', doc); + doc.deleteText({ + from: { + node: text, + offset: 1 + }, + to: { + node: text, + offset: 4 + } + }); + expect(doc.root.contents().length).to.equal(1); + expect(doc.root.contents()[0].getText()).to.equal('Ae'); + }); + it('removes across elements - 1', function() { + var doc = getDocumentFromXML('
aaabbb
'); + + doc.deleteText({ + from: { + node: getTextNode('aaa', doc), + offset: 2 + }, + to: { + node: getTextNode('bbb', doc), + offset: 2 + } + }); + + var contents = doc.root.contents(); + expect(contents.length).to.equal(2); + expect(contents[0].contents()[0].getText()).to.equal('aa'); + expect(contents[1].contents()[0].getText()).to.equal('b'); + }); + it('removes across elements - 2', function() { + var doc = getDocumentFromXML('cccxxx'); + doc.deleteText({ + from: { + node: getTextNode('ccc', doc), + offset: 2 + }, + to: { + node: getTextNode('xxx', doc), + offset: 2 + } + }); + + var contents = doc.root.contents(); + expect(contents.length).to.equal(2); + expect(contents[0].getTagName()).to.equal('b'); + expect(contents[1].getText()).to.equal('x'); + + var bContents = contents[0].contents(); + expect(bContents.length).to.equal(1); + expect(bContents[0].getTagName()).to.equal('c'); + expect(bContents[0].contents().length).to.equal(1); + expect(bContents[0].contents()[0].getText()).to.equal('cc'); + }); + it('removes nodes in between', function() { + var doc = getDocumentFromXML('
aaa!xxx!bbb
'); + doc.deleteText({ + from: { + node: getTextNode('aaa', doc), + offset: 2 + }, + to: { + node: getTextNode('bbb', doc), + offset: 2 + } + }); + + var contents = doc.root.contents(); + expect(contents.length).to.equal(2, 'two nodes survived'); + expect(contents[0].getTagName()).to.equal('a'); + expect(contents[1].getTagName()).to.equal('b'); + expect(contents[0].contents().length).to.equal(1); + expect(contents[0].contents()[0].getText()).to.equal('aa'); + expect(contents[1].contents().length).to.equal(1); + expect(contents[1].contents()[0].getText()).to.equal('b'); + }); + }); + describe('Splitting text', function() { it('splits TextNode\'s parent into two ElementNodes', function() { -- 2.20.1