From d6ae05ea6fd7b0ff365877f2b009fac3346ebd14 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 27 May 2016 15:21:42 +0200 Subject: [PATCH] Make parser understand how to manage text nodes --- src/code_manager/model/HtmlGenerator.js | 2 +- src/dom_components/config/config.js | 3 ++ src/dom_components/main.js | 2 +- src/dom_components/model/Component.js | 5 +++ src/dom_components/model/Components.js | 7 +++- src/parser/config/config.js | 7 ++++ src/parser/main.js | 7 ++++ src/parser/model/ParserHtml.js | 55 ++++++++++++++++++++----- test/specs/parser/model/ParserHtml.js | 43 +++++++++++++++---- 9 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 src/parser/config/config.js diff --git a/src/code_manager/model/HtmlGenerator.js b/src/code_manager/model/HtmlGenerator.js index 9c35c5a4b..079b63f5d 100644 --- a/src/code_manager/model/HtmlGenerator.js +++ b/src/code_manager/model/HtmlGenerator.js @@ -12,12 +12,12 @@ define(['backbone'], coll.each(function(m){ var tag = m.get('tagName'), // Tag name - sTag = 0, // Single tag attr = '', // Attributes string attrId = '', strCls = '', cln = m.get('components'), // Children attrs = m.get('attributes'), + sTag = m.get('void'), classes = m.get('classes'); _.each(attrs,function(value, prop){ if(prop == 'onmousedown') diff --git a/src/dom_components/config/config.js b/src/dom_components/config/config.js index 56554bab8..5b30d2d74 100644 --- a/src/dom_components/config/config.js +++ b/src/dom_components/config/config.js @@ -26,5 +26,8 @@ define(function () { // Open assets manager on create of image component oAssetsOnCreate : true, + + // List of void elements + voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'], }; }); \ No newline at end of file diff --git a/src/dom_components/main.js b/src/dom_components/main.js index 1c5e15068..f7bc76f25 100644 --- a/src/dom_components/main.js +++ b/src/dom_components/main.js @@ -60,7 +60,7 @@ define(function(require) { c[name] = defaults[name]; } - var component = new Component(c.wrapper, { sm: c.em }); + var component = new Component(c.wrapper, { sm: c.em, config: c }); component.set({ attributes: {id: 'wrapper'} diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index 2c0671c1a..e154bf6c6 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -13,6 +13,7 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'], badgable: true, stylable: true, copyable: true, + void: false, state: '', status: '', previousModel: '', @@ -22,6 +23,10 @@ define(['backbone','./Components', 'ClassManager/model/ClassTags'], }, initialize: function(o, opt) { + // Check void elements + if(opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0) + this.set('void', true); + this.sm = opt ? opt.sm || {} : {}; this.config = o || {}; this.defaultC = this.config.components || []; diff --git a/src/dom_components/model/Components.js b/src/dom_components/model/Components.js index c7b7d8358..921cb08d7 100644 --- a/src/dom_components/model/Components.js +++ b/src/dom_components/model/Components.js @@ -7,6 +7,8 @@ define([ 'backbone', 'require'], this.on('add', this.onAdd); + this.config = opt && opt.config ? opt.config : null; + // Inject editor if(opt && opt.sm) this.editor = opt.sm; @@ -17,6 +19,9 @@ define([ 'backbone', 'require'], if(!options.sm && opt && opt.sm) options.sm = opt.sm; + if(opt && opt.config) + options.config = opt.config; + switch(attrs.type){ case 'text': @@ -53,7 +58,7 @@ define([ 'backbone', 'require'], onAdd: function(model, c, opts){ var style = model.get('style'); - if(!_.isEmpty(style)){ + if(!_.isEmpty(style) && this.editor){ var newClass = this.editor.get('ClassManager').addClass(model.cid); model.get('classes').add(newClass); var rule = this.editor.get('CssComposer').newRule(newClass); diff --git a/src/parser/config/config.js b/src/parser/config/config.js new file mode 100644 index 000000000..6f7697c02 --- /dev/null +++ b/src/parser/config/config.js @@ -0,0 +1,7 @@ +define(function () { + return { + + textTags: ['br', 'b', 'i', 'u'], + + }; +}); \ No newline at end of file diff --git a/src/parser/main.js b/src/parser/main.js index 611f69a52..61906550a 100644 --- a/src/parser/main.js +++ b/src/parser/main.js @@ -3,9 +3,16 @@ define(function(require) { var Parser = function(config) { var c = config || {}, + defaults = require('./config/config'), parserCss = require('./model/ParserCss'), parserHtml = require('./model/ParserHtml'); + // Set default options + for (var name in defaults) { + if (!(name in c)) + c[name] = defaults[name]; + } + var pHtml = new parserHtml(c); var pCss = new parserCss(c); diff --git a/src/parser/model/ParserHtml.js b/src/parser/model/ParserHtml.js index e33fec30f..b2472382f 100644 --- a/src/parser/model/ParserHtml.js +++ b/src/parser/model/ParserHtml.js @@ -3,6 +3,7 @@ define(function(require) { return function(config) { var TEXT_NODE = 'span'; + var c = config; return { @@ -63,6 +64,8 @@ define(function(require) { var model = {}; var attrs = node.attributes || []; var attrsLen = attrs.length; + var prevI = result.length - 1; + var prevSib = result[prevI]; model.tagName = node.tagName ? node.tagName.toLowerCase() : ''; if(attrsLen) @@ -74,11 +77,11 @@ define(function(require) { var nodeValue = attrs[j].nodeValue; //Isolate style, class and src attributes - if(nodeName === 'style') + if(nodeName == 'style') model.style = this.parseStyle(nodeValue); - else if(nodeName === 'class') + else if(nodeName == 'class') model.classes = this.parseClass(nodeValue); - else if(nodeName === 'src' && model.tagName === 'img'){ + else if(nodeName == 'src' && model.tagName == 'img'){ model.type = 'image'; model.src = nodeValue; }else @@ -90,18 +93,50 @@ define(function(require) { if(nodeChild){ // Avoid infinite text nodes nesting var firstChild = node.childNodes[0]; - if(nodeChild === 1 && firstChild.nodeType === 3 && model.tagName === TEXT_NODE){ + if(nodeChild === 1 && firstChild.nodeType === 3){ model.type = 'text'; model.content = firstChild.nodeValue; - }else - model.components = this.parseNode(node); + }else{ + var parsed = this.parseNode(node); + // From:
TEST
<-- span is text type + // TO:
TEST
<-- div become text type + // With 'nodeChild > 1' I know that nodes were merged + if(parsed.length == 1 && parsed[0].type == 'text' && nodeChild > 1){ + model.type = 'text'; + model.content = parsed[0].content; + }else + model.components = parsed; + } } + var prevIsText = prevSib && prevSib.type == 'text' && prevSib.tagName == TEXT_NODE; // Find text nodes - if(!model.tagName && node.nodeType === 3 && node.nodeValue.trim()){ - model.type = 'text'; - model.tagName = TEXT_NODE; - model.content = node.nodeValue; + if(!model.tagName && node.nodeType === 3){ + // Pass content to the previous model if it's a text node + if(prevIsText){ + prevSib.content += node.nodeValue; + continue; + } + // Make it text node only the content is not empty + if(node.nodeValue.trim()){ + model.type = 'text'; + model.tagName = TEXT_NODE; + model.content = node.nodeValue; + } + } + + // Check if it's a text node and if it could be moved to the prevous model + if(c.textTags.indexOf(model.tagName) >= 0){ + if(prevIsText){ + prevSib.content += node.outerHTML; + continue; + }else{ + model = { + type: 'text', + tagName: TEXT_NODE, + content: node.outerHTML, + }; + } } // If tagName is still empty do not push it diff --git a/test/specs/parser/model/ParserHtml.js b/test/specs/parser/model/ParserHtml.js index 7a6c6caaf..df4c4494f 100644 --- a/test/specs/parser/model/ParserHtml.js +++ b/test/specs/parser/model/ParserHtml.js @@ -9,7 +9,9 @@ define([path + 'model/ParserHtml',], var obj; beforeEach(function () { - obj = new ParserHtml(); + obj = new ParserHtml({ + textTags: ['br', 'b', 'i', 'u'], + }); }); afterEach(function () { @@ -95,13 +97,43 @@ define([path + 'model/ParserHtml',], it('Parse text nodes', function() { var str = '
test2
'; + var result = { + tagName: 'div', + attributes: { id: 'test1'}, + type: 'text', + content: 'test2 ', + }; + obj.parse(str).should.deep.equal(result); + }); + + it('Parse text with few text tags', function() { + var str = '

test2
a b b i u test
'; + var result = { + tagName: 'div', + attributes: { id: 'test1'}, + type: 'text', + content: '
test2
a b b i u test ', + }; + obj.parse(str).should.deep.equal(result); + }); + + it('Parse text with few text tags and nested node', function() { + var str = '
a b b ic
ABC
i u test
'; var result = { tagName: 'div', attributes: { id: 'test1'}, components: [{ tagName: 'span', type: 'text', - content: 'test2 ', + content: 'a b b ic ', + },{ + tagName: 'div', + type: 'text', + content: 'ABC', + },{ + tagName: 'span', + type: 'text', + content: 'i u test ', }], }; obj.parse(str).should.deep.equal(result); @@ -141,11 +173,8 @@ define([path + 'model/ParserHtml',], content: 'content1 ', },{ tagName: 'div', - components: [{ - tagName: 'span', - type: 'text', - content: 'nested', - }] + type: 'text', + content: 'nested', },{ tagName: 'span', type: 'text',