From 2ceaf44955f0f54fa0336978586bb8d9f4d7890b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 2 Dec 2013 14:19:34 +0100 Subject: [PATCH 1/1] wip: lists + uri extensions --- src/smartxml/smartxml.js | 14 +- src/wlxml/extensions/list/list.js | 167 ++++++++++ src/wlxml/extensions/list/list.test.js | 417 +++++++++++++++++++++++++ src/wlxml/extensions/uri.js | 10 + src/wlxml/wlxml.js | 41 ++- 5 files changed, 644 insertions(+), 5 deletions(-) create mode 100644 src/wlxml/extensions/list/list.js create mode 100644 src/wlxml/extensions/list/list.test.js create mode 100644 src/wlxml/extensions/uri.js diff --git a/src/smartxml/smartxml.js b/src/smartxml/smartxml.js index 712c0ad..fc9715b 100644 --- a/src/smartxml/smartxml.js +++ b/src/smartxml/smartxml.js @@ -242,12 +242,18 @@ $.extend(ElementNode.prototype, { return this.nativeNode.tagName.toLowerCase(); }, - contents: function() { + contents: function(selector) { var toret = [], document = this.document; - this._$.contents().each(function() { - toret.push(document.createDocumentNode(this)); - }); + if(selector) { + this._$.children(selector).each(function() { + toret.push(document.createDocumentNode(this)); + }); + } else { + this._$.contents().each(function() { + toret.push(document.createDocumentNode(this)); + }); + } return toret; }, diff --git a/src/wlxml/extensions/list/list.js b/src/wlxml/extensions/list/list.js new file mode 100644 index 0000000..dd010a6 --- /dev/null +++ b/src/wlxml/extensions/list/list.js @@ -0,0 +1,167 @@ +define(function(require) { + +'use strict'; + + +var wlxml = require('wlxml/wlxml'), + extension = {documentTransformations: [], classMethods: {}}; + + +extension.classMethods['list'] = { + itemIndex: function(listItem) { + var toret = -1; + this.contents('.item').some(function(item, index) { + if(item.sameNode(listItem)) { + toret = index; + return true; // break + } + }); + return toret; + }, + getItem: function(index) { + return this.contents('.item')[index]; + } +} + +extension.documentTransformations.push({ + name: 'createList', + impl: function(params) { + var parent = params.node1.parent(), + parentContents = parent.contents(), + nodeIndexes = [params.node1.getIndex(), params.node2.getIndex()].sort(), + nodesToWrap = [], + listNode = params.node1.document.createDocumentNode({tagName: 'div', attrs: {'class': 'list.items'}}), + node, i; + + for(i = nodeIndexes[0]; i <= nodeIndexes[1]; i++) { + node = parentContents[i]; + if(node.nodeType === Node.TEXT_NODE) { + node = node.wrapWith({tagName: 'div', attrs: {'class': 'item'}}); //t + } else { + node.setClass('item'); //t + } + nodesToWrap.push(node); + } + + var toInsert; + if(parent.is('list') && parent.object.itemIndex(nodesToWrap[0]) > 0) { // object api + // var prevItem = parent.object.getItem(parent.object.itemIndex(nodesToWrap[0])-1); // object api + // prevItem.append(listNode); //t + toInsert = listNode.wrapWith({tagName: 'div', attrs: {'class': 'item'}}); + } else { + //nodesToWrap[0].before(listNode); //t + toInsert = listNode; + } + + params.node1.before(toInsert); + + nodesToWrap.forEach(function(node) { + listNode.append(node); //t + }); + }, + getChangeRoot: function() { + return this.args.node1.parent(); + }, + isAllowed: function() { + return this.args.node1.parent().sameNode(this.args.node2.parent()); + } +}); + +extension.documentTransformations.push({ + name: 'extractItems', + impl: function(params) { + params = _.extend({}, {merge: true}, params); + var list = params.item1.parent(), + indexes = [params.item1.getIndex(), params.item2.getIndex()].sort(), + precedingItems = [], + extractedItems = [], + succeedingItems = [], + items = list.contents(), // lub list.object.items() + listIsNested = list.parent().is('item'), + i; + + items.forEach(function(item, idx) { + if(idx < indexes[0]) { + precedingItems.push(item); + } + else if(idx >= indexes[0] && idx <= indexes[1]) { + extractedItems.push(item); + } + else { + succeedingItems.push(item); + } + }); + + var reference = listIsNested ? list.parent() : list; + if(succeedingItems.length === 0) { + var reference_orig = reference; + extractedItems.forEach(function(item) { + reference.after(item); //t + reference = item; + if(!listIsNested) { + item.setClass(null); //t + } + }); + if(precedingItems.length === 0) + reference_orig.detach(); //t + } else if(precedingItems.length === 0) { + extractedItems.forEach(function(item) { + reference.before(item); //t + if(!listIsNested) { + item.setClass(null); //t + } + }); + } else { + extractedItems.forEach(function(item) { + reference.after(item); //t + if(!listIsNested) + item.setClass(null); //t + reference = item; + }); + var secondList = params.item1.document.createDocumentNode({tagName: 'div', attrs: {'class':'list'}}), + toAdd = secondList; + + if(listIsNested) { + toAdd = secondList.wrapWith({tagName: 'div', attrs: {'class':'item'}}); + } + succeedingItems.forEach(function(item) { + secondList.append(item); + }); + + reference.after(toAdd); + } + if(!params.merge && listIsNested) { + debugger; + return this.transform('extractItems', {item1: extractedItems[0], item2: extractedItems[extractedItems.length-1]}); + } + return true; + }, + isAllowed: function() { + var parent = this.args.nodel1.parent(); + return parent.is('list') && parent.sameNode(this.args.node2.parent()); + } +}); + +wlxml.registerExtension(extension); + +// wlxml.registerClassTransformation('list', { +// name: 'insertItem', +// impl: function(item) { +// if(!item.is('item')) { +// throw new Error ('...'); +// } +// this.append(item); +// } +// }); + +// wlxml.registerClassMethod('list', { +// name: 'items', +// impl: function() { +// var node = this; +// return this.contents(); +// } +// }); + +//a atrybuty? registerClassAttrs? E... lepiej registerClassExtension({methods, attibutes}) + +}); \ No newline at end of file diff --git a/src/wlxml/extensions/list/list.test.js b/src/wlxml/extensions/list/list.test.js new file mode 100644 index 0000000..f15d75b --- /dev/null +++ b/src/wlxml/extensions/list/list.test.js @@ -0,0 +1,417 @@ +define(function(require) { + +'use strict'; + +var chai = require('libs/chai'), + wlxml = require('wlxml/wlxml'), + expect = chai.expect, + $ = require('libs/jquery'), + lists = require('wlxml/extensions/list/list'); + + +var getDocumentFromXML = function(xml, options) { + return wlxml.WLXMLDocumentFromXML(xml, options || {}); +}; + +var removeEmptyTextNodes = function(xml) { + xml = $($.trim(xml)); + xml.find(':not(iframe)') + .addBack() + .contents() + .filter(function() {return this.nodeType === Node.TEXT_NODE;}) + .each(function() { + if(!this.data.length) { + $(this).remove(); + } + }); + return $('').append(xml).html(); +}; + + +describe.only('Lists extension', function() { + + describe('creating lists', function() { + it('allows creation of a list from existing sibling DocumentElements', function() { + var doc = getDocumentFromXML('
Alice
has
a
cat
'), + section = doc.root, + div1 = section.contents()[1], + textA = section.contents()[2]; + + doc.transform('createList', {node1: div1, node2: textA}); + + expect(section.contents().length).to.equal(3, 'section has three child nodes'); + + var child1 = section.contents()[0], + list = section.contents()[1], + child3 = section.contents()[2]; + + expect(child1.getText()).to.equal('Alice'); + expect(list.is('list')).to.equal(true, 'second child is a list'); + expect(list.contents().length).to.equal(2, 'list contains two elements'); + list.contents().forEach(function(child) { + expect(child.getClass()).to.equal('item', 'list childs have wlxml class of item'); + }); + expect(child3.contents()[0].getText()).to.equal('cat'); + }); + + it('allows creating nested list from existing sibling list items', function() { + var doc = getDocumentFromXML('\ +
\ +
\ +
A
\ +
B
\ +
C
\ +
D
\ +
\ +
'); + + var outerList = doc.root.contents('.list')[0], + itemB = outerList.contents('.item')[1], + itemC = outerList.contents('.item')[2]; + + + doc.transform('createList', {node1: itemB, node2: itemC}); + + var outerListItems = outerList.contents('.item'), + innerList = outerListItems[1].contents()[0]; + + var innerListItems = innerList.contents('.item'); + + expect(outerListItems.length).to.equal(3, 'outer list has three items'); + expect(outerListItems[0].contents()[0].getText()).to.equal('A', 'first outer item ok'); + expect(outerListItems[1].getClass()).to.equal('item', 'inner list is wrapped by item element'); + + expect(innerList.is('list')).to.equal(true, 'inner list created'); + expect(innerListItems.length).to.equal(2, 'inner list has two items'); + expect(innerListItems[0].contents()[0].getText()).to.equal('B', 'first inner item ok'); + expect(innerListItems[1].contents()[0].getText()).to.equal('C', 'second inner item ok'); + + expect(outerListItems[2].contents()[0].getText()).to.equal('D', 'last outer item ok'); + + }); + }); + + describe('extracting list items', function() { + it('creates two lists with extracted items in the middle if extracting from the middle of the list', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
3
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[1], + item2 = list.contents()[2]; + + doc.transform('extractItems', {item1: item1, item2: item2}); + + var section = doc.root, + list1 = section.contents()[0], + oldItem1 = section.contents()[1], + oldItem2 = section.contents()[2], + list2 = section.contents()[3]; + + expect(section.contents().length).to.equal(4, 'section contains two old items and two lists'); + + expect(list1.is('list')).to.equal(true, 'first section child is a list'); + expect(list1.contents().length).to.equal(1, 'first list has one child'); + expect(list1.contents()[0].contents()[0].getText()).to.equal('0', 'first item of the first list is a first item of the original list'); + + expect(oldItem1.contents()[0].getText()).to.equal('1', 'first item got extracted'); + expect(oldItem1.getClass() === '').to.equal(true, 'first extracted element has no wlxml class'); + + expect(oldItem2.contents()[0].getText()).to.equal('2', 'second item got extracted'); + expect(oldItem2.getClass() === '').to.equal(true, 'second extracted element has no wlxml class'); + + expect(list2.is('list')).to.equal(true, 'last section child is a list'); + expect(list2.contents().length).to.equal(1, 'second list has one child'); + expect(list2.contents()[0].contents()[0].getText()).to.equal('3', 'first item of the second list is a last item of the original list'); + }); + + it('puts extracted items above the list if starting item is the first one', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2]; + + doc.transform('extractItems', {item1: item1, item2: item2}); + + var section = doc.root, + oldItem1 = section.contents()[0], + oldItem2 = section.contents()[1], + newList = section.contents()[2]; + + expect(section.contents().length).to.equal(3, 'section has three children'); + expect(oldItem1.contents()[0].getText()).to.equal('0', 'first item extracted'); + expect(oldItem2.contents()[0].getText()).to.equal('1', 'second item extracted'); + expect(newList.is('list')).to.equal(true, 'list lies below extracted item'); + expect(newList.contents().length).to.equal(1, 'list has now one child'); + }); + + it('puts extracted items below the list if ending item is the last one', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
1
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2]; + + doc.transform('extractItems', {item1: item2, item2: item3}); + + var section = doc.root, + oldItem1 = section.contents()[1], + oldItem2 = section.contents()[2], + newList = section.contents()[0]; + + expect(section.contents().length).to.equal(3, 'section has three children'); + expect(oldItem1.contents()[0].getText()).to.equal('1', 'first item extracted'); + expect(oldItem2.contents()[0].getText()).to.equal('2', 'second item extracted'); + expect(newList.is('list')).to.equal(true, 'list lies above extracted item'); + expect(newList.contents().length).to.equal(1, 'list has now one child'); + }); + + it('removes list if all its items are extracted', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
some item
\ +
some item 2
\ +
\ +
')), + list = doc.root.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1]; + + doc.transform('extractItems', {item1: item1, item2: item2}); + + var section = doc.root, + oldItem1 = section.contents()[0], + oldItem2 = section.contents()[1]; + + expect(section.contents().length).to.equal(2, 'section contains two children'); + expect(oldItem1.contents()[0].getText()).to.equal('some item'); + expect(oldItem2.contents()[0].getText()).to.equal('some item 2'); + }); + + it('creates two lists with extracted items in the middle if extracting from the middle of the list - nested case' , function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem = nestedList.contents()[1]; + + doc.transform('extractItems', {item1: nestedListItem, item2: nestedListItem}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], // + item3 = list.contents()[2], + item4 = list.contents()[3], // + item5 = list.contents()[4], + nestedList1 = item2.contents()[0], + nestedList2 = item4.contents()[0]; + + expect(list.contents().length).to.equal(5, 'top list has five items'); + + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + + expect(item2.getClass()).to.equal('item', 'first nested list is still wrapped in item element'); + expect(nestedList1.contents().length).to.equal(1, 'first nested list is left with one child'); + expect(nestedList1.contents()[0].contents()[0].getText()).to.equal('1.1', 'first nested list item left alone'); + + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + + expect(item4.getClass()).to.equal('item', 'second nested list is still wrapped in item element'); + expect(nestedList2.contents().length).to.equal(1, 'second nested list is left with one child'); + expect(nestedList2.contents()[0].contents()[0].getText()).to.equal('1.3', 'second nested list item left alone'); + + expect(item5.contents()[0].getText()).to.equal('2', 'last item ok'); + }); + + it('puts extracted items below the list if ending item is the last one - nested case' , function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem1 = nestedList.contents()[1], + nestedListItem2 = nestedList.contents()[2]; + + doc.transform('extractItems', {item1: nestedListItem1, item2: nestedListItem2}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2], + item4 = list.contents()[3], + item5 = list.contents()[4]; + nestedList = item2.contents()[0]; + + expect(list.contents().length).to.equal(5, 'top list has five items'); + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + expect(item2.getClass()).to.equal('item', 'nested list is still wrapped in item element'); + expect(nestedList.contents().length).to.equal(1, 'nested list is left with one child'); + expect(nestedList.contents()[0].contents()[0].getText()).to.equal('1.1', 'nested list item left alone'); + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + expect(item4.contents()[0].getText()).to.equal('1.3', 'fourth item ok'); + expect(item5.contents()[0].getText()).to.equal('2', 'fifth item ok'); + }); + + it('puts extracted items above the list if starting item is the first one - nested case' , function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
1.3
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem1 = nestedList.contents()[0], + nestedListItem2 = nestedList.contents()[1]; + + doc.transform('extractItems', {item1: nestedListItem1, item2: nestedListItem2}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2], + item4 = list.contents()[3], + item5 = list.contents()[4]; + nestedList = item4.contents()[0]; + + expect(list.contents().length).to.equal(5, 'top list has five items'); + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + expect(item2.contents()[0].getText()).to.equal('1.1', 'second item ok'); + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + + expect(item4.getClass()).to.equal('item', 'nested list is still wrapped in item element'); + expect(nestedList.contents().length).to.equal(1, 'nested list is left with one child'); + expect(nestedList.contents()[0].contents()[0].getText()).to.equal('1.3', 'nested list item left alone'); + expect(item5.contents()[0].getText()).to.equal('2', 'fifth item ok'); + }); + + it('removes list if all its items are extracted - nested case', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
\ +
\ +
2
\ +
\ +
')), + list = doc.root.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem1 = nestedList.contents()[0], + nestedListItem2 = nestedList.contents()[1]; + + doc.transform('extractItems', {item1: nestedListItem1, item2: nestedListItem2}); + + var section = doc.root, + list = section.contents()[0], + item1 = list.contents()[0], + item2 = list.contents()[1], + item3 = list.contents()[2], + item4 = list.contents()[3]; + + expect(list.contents().length).to.equal(4, 'top list has four items'); + expect(item1.contents()[0].getText()).to.equal('0', 'first item ok'); + expect(item2.contents()[0].getText()).to.equal('1.1', 'second item ok'); + expect(item3.contents()[0].getText()).to.equal('1.2', 'third item ok'); + expect(item4.contents()[0].getText()).to.equal('2', 'fourth item ok'); + }); + + it('extracts items out of outer most list when merge flag is set to false', function() { + var doc = getDocumentFromXML(removeEmptyTextNodes('\ +
\ +
\ +
0
\ +
\ +
\ +
1.1
\ +
1.2
\ +
\ +
\ +
2
\ +
\ +
')), + section = doc.root, + list = section.contents()[0], + nestedList = list.contents()[1].contents()[0], + nestedListItem = nestedList.contents()[0]; + + var test = doc.transform('extractItems', {item1: nestedListItem, item2: nestedListItem, merge: false}); + + expect(test).to.equal(true, 'extraction status ok'); + + var sectionContents = section.contents(), + extractedItem = sectionContents[1]; + + expect(sectionContents.length).to.equal(3, 'section has three children'); + expect(sectionContents[0].is('list')).to.equal(true, 'first child is a list'); + + expect(extractedItem.getTagName()).to.equal('div', 'extracted item is a wlxml div'); + expect(extractedItem.getClass()).to.equal('', 'extracted item has no wlxml class'); + expect(extractedItem.contents()[0].getText()).to.equal('1.1', 'extracted item ok'); + expect(sectionContents[2].is('list')).to.equal(true, 'second child is a list'); + }); + }); + +}); + +}); \ No newline at end of file diff --git a/src/wlxml/extensions/uri.js b/src/wlxml/extensions/uri.js new file mode 100644 index 0000000..ec8f939 --- /dev/null +++ b/src/wlxml/extensions/uri.js @@ -0,0 +1,10 @@ +define(function() { + +'use strict'; + +return { + className: 'uri', + attributes: {uri: {type: 'string'}} +}; + +}); \ No newline at end of file diff --git a/src/wlxml/wlxml.js b/src/wlxml/wlxml.js index e6ea26b..d2dbd30 100644 --- a/src/wlxml/wlxml.js +++ b/src/wlxml/wlxml.js @@ -21,9 +21,19 @@ AttributesList.prototype.keys = function() { return _.keys(this); }; +var installObject = function(instance, klass) { + var methods = classMethods[klass]; + if(methods) { + instance.object = Object.create(methods); + _.keys(methods).forEach(function(key) { + instance.object[key] = _.bind(instance.object[key], instance); + }); + } +} var WLXMLElementNode = function(nativeNode, document) { smartxml.ElementNode.call(this, nativeNode, document); + installObject(this, this.getClass()); }; WLXMLElementNode.prototype = Object.create(smartxml.ElementNode.prototype); @@ -32,7 +42,14 @@ $.extend(WLXMLElementNode.prototype, smartxml.ElementNode.prototype, { return this.getAttr('class') || ''; }, setClass: function(klass) { - return this.setAttr('class', klass); + var methods, object; + if(klass !== this.klass) { + installObject(this, klass); + return this.setAttr('class', klass); + } + }, + is: function(klass) { + return this.getClass().substr(0, klass.length) === klass; }, getMetaAttributes: function() { var toret = new AttributesList(), @@ -228,6 +245,8 @@ var wlxmlClasses = { } }; +var classMethods = {}; + return { WLXMLDocumentFromXML: function(xml, options) { options = _.extend({wlxmlClasses: wlxmlClasses}, options); @@ -236,7 +255,27 @@ return { WLXMLElementNodeFromXML: function(xml) { return this.WLXMLDocumentFromXML(xml).root; + }, + + registerExtension: function(extension) { + extension.documentTransformations.forEach(function(method) { + WLXMLDocument.prototype.transformations.register(transformations.createContextTransformation(method)); + }); + + _.pairs(extension.classMethods).forEach(function(pair) { + var className = pair[0], + methods = pair[1]; + _.pairs(methods).forEach(function(pair) { + var methodName = pair[0], + method = pair[1]; + classMethods[className] = classMethods[className] || {}; + classMethods[className][methodName] = method; + }); + + }); + } + }; }); \ No newline at end of file -- 2.20.1