Browse Source

Start CSS parser

pull/36/head
Artur Arseniev 10 years ago
parent
commit
57da5b9bbe
  1. 92
      src/parser/model/ParserCss.js
  2. 53
      src/parser/model/ParserHtml.js
  3. 7
      test/specs/parser/main.js
  4. 102
      test/specs/parser/model/ParserCss.js
  5. 75
      test/specs/parser/model/ParserHtml.js

92
src/parser/model/ParserCss.js

@ -4,8 +4,98 @@ define(function(require) {
return {
/**
* Parse selector string to array.
* Only concatenated classes are valid as CSS rules inside editor.
* It's ok with the last part of the string as state (:hover, :active)
* @param {string} str Selectors string
* @return {Array<Array>}
* @example
* var res = ParserCss.parseSelector('.test1, .test1.test2, .test2.test3');
* console.log(res);
* // [['test1'], ['test1', 'test2'], ['test2', 'test3']]
*/
parseSelector: function(str){
var result = [];
var sels = str.split(',');
for (var i = 0, len = sels.length; i < len; i++) {
var sel = sels[i].trim();
// Will accept only concatenated classes and last
// class might be with state (eg. :hover), nothing else.
if (/^(\.{1}[\w\-]+)+(:{1}[\w\-]+)?$/ig.test(sel)) {
var cls = sel.split('.').filter(Boolean);
result.push(cls);
}
}
return result;
},
/**
* Fetch data from node
* @param {StyleSheet|CSSMediaRule} el
* @return {Array<Object>}
*/
parseNode: function(el){
var result = [];
var nodes = el.cssRules;
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var sels = node.selectorText; // CSSMediaRule has conditionText (screen and (min-width: 480px))
//if(node.cssRules)
// it's a CSSMediaRule, need to go deeper
if(!sels)
continue;
sels = this.parseSelector(sels);
// Create style object from the big one
var stl = node.style;
var style = {};
for(var j = 0, len2 = stl.length; j < len2; j++){
style[stl[j]] = stl[stl[j]];
}
// For each group of selectors
for (var k = 0, len3 = sels.length; k < len3; k++) {
var selArr = sels[k];
var model = {};
model.selectors = selArr;
model.style = style;
result.push(model);
}
}
return result;
},
/**
* Parse CSS string to a desired model object
* @param {string} str HTML string
* @return {Object|Array<Object>}
*/
parse: function(str){
return {parsed: 'CSS '+str};
var el = document.createElement('style');
/*
el.innerHTML = ".cssClass {border: 2px solid black; background-color: blue;} " +
".red, .red2 {color:red; padding:5px} .test1.red {color:black} .red:hover{color: blue} " +
"@media screen and (min-width: 480px){ .red{color:white} }";
*/
el.innerHTML = str;
// There is no .sheet without adding it to the <head>
document.head.appendChild(el);
var sheet = el.sheet;
document.head.removeChild(el);
var result = this.parseNode(sheet);
if(result.length == 1)
result = result[0];
return result;
},
};

53
src/parser/model/ParserHtml.js

@ -48,23 +48,20 @@ define(function(require) {
},
/**
* Parse HTML string to a desired model object
* @param {string} str HTML string
* @return {Object}
* Fetch data from node
* @param {HTMLElement} el DOM
* @return {Array<Object>}
*/
parse: function(str){
var el = document.createElement('div');
el.innerHTML = str;
var nodes = el.childNodes;
parseNode: function(el){
var result = [];
var nodes = el.childNodes;
// Iterate all nodes
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var model = {};
var attrs = node.attributes;
var attrs = node.attributes || [];
var attrsLen = attrs.length;
model.tagName = node.tagName.toLowerCase();
model.tagName = node.tagName ? node.tagName.toLowerCase() : '';
if(attrsLen)
model.attributes = {};
@ -74,18 +71,50 @@ define(function(require) {
var nodeName = attrs[j].nodeName;
var nodeValue = attrs[j].nodeValue;
//Isolate style and class attributes
//Isolate style, class and src attributes
if(nodeName === 'style')
model.style = this.parseStyle(nodeValue);
else if(nodeName === 'class')
model.classes = this.parseClass(nodeValue);
else
else if(nodeName === 'src' && model.tagName === 'img'){
model.type = 'image';
model.src = nodeValue;
}else
model.attributes[nodeName] = nodeValue;
}
// Check for nested elements
if(node.childNodes.length)
model.components = this.parseNode(node);
// Find text nodes
if(!model.tagName && node.nodeType === 3 && node.nodeValue.trim()){
model.type = 'text';
model.tagName = 'span';
model.content = node.nodeValue;
}
// If tagName is still empty do not push it
if(!model.tagName)
continue;
result.push(model);
}
return result;
},
/**
* Parse HTML string to a desired model object
* @param {string} str HTML string
* @return {Object}
*/
parse: function(str){
var el = document.createElement('div');
el.innerHTML = str;
var nodes = el.childNodes;
var result = this.parseNode(el);
if(result.length == 1)
result = result[0];

7
test/specs/parser/main.js

@ -2,16 +2,19 @@ var modulePath = './../../../test/specs/parser';
define([
'Parser',
modulePath + '/model/ParserHtml'
modulePath + '/model/ParserHtml',
modulePath + '/model/ParserCss'
],
function(
Parser,
ParserHtml
ParserHtml,
ParserCss
) {
describe('Parser', function() {
ParserHtml.run();
ParserCss.run();
});
});

102
test/specs/parser/model/ParserCss.js

@ -0,0 +1,102 @@
var path = 'Parser/';
define([path + 'model/ParserCss',],
function(ParserCss) {
return {
run : function(){
describe('ParserCss', function() {
var obj;
beforeEach(function () {
obj = new ParserCss();
});
afterEach(function () {
delete obj;
});
it('Parse selector', function() {
var str = '.test';
var result = [['test']];
obj.parseSelector(str).should.deep.equal(result);
});
it('Parse selectors', function() {
var str = '.test1, .test1.test2, .test2.test3';
var result = [['test1'], ['test1', 'test2'], ['test2', 'test3']];
obj.parseSelector(str).should.deep.equal(result);
});
it('Ignore not valid selectors', function() {
var str = '.test1.test2, .test2 .test3, div > .test4, #test.test5, .test6';
var result = [['test1', 'test2'], ['test6']];
obj.parseSelector(str).should.deep.equal(result);
});
it('Parse selectors with state', function() {
var str = '.test1. test2, .test2>test3, .test4.test5:hover';
var result = [['test4', 'test5:hover']];
obj.parseSelector(str).should.deep.equal(result);
});
it('Parse simple rule', function() {
var str = ' .test1 {color:red; width: 50px }';
var result = {
selectors: ['test1'],
style: {
color: 'red',
width: '50px',
}
};
obj.parse(str).should.deep.equal(result);
});
it('Parse rule with more selectors', function() {
var str = ' .test1.test2 {color:red; test: value}';
var result = {
selectors: ['test1', 'test2'],
style: { color: 'red'}
};
obj.parse(str).should.deep.equal(result);
});
it('Parse same rule with more selectors', function() {
var str = ' .test1.test2, .test3{ color:red }';
var result = [{
selectors: ['test1', 'test2'],
style: { color: 'red'}
},{
selectors: ['test3'],
style: { color: 'red'}
}];
obj.parse(str).should.deep.equal(result);
});
it('Parse more rules', function() {
var str = ' .test1.test2, .test3{ color:red } .test4, .test5.test6{ width:10px }';
var result = [{
selectors: ['test1', 'test2'],
style: { color: 'red'}
},{
selectors: ['test3'],
style: { color: 'red'}
},{
selectors: ['test4'],
style: { width: '10px'}
},{
selectors: ['test5', 'test6'],
style: { width: '10px'}
}];
obj.parse(str).should.deep.equal(result);
});
it.skip('Parse rule with state', function() {
});
});
}
};
});

75
test/specs/parser/model/ParserHtml.js

@ -82,16 +82,83 @@ define([path + 'model/ParserHtml',],
obj.parse(str).should.deep.equal(result);
});
it.skip('Parse nested nodes', function() {
it('Parse images nodes', function() {
var str = '<img id="test1" src="./index.html"/>';
var result = {
tagName: 'img',
type: 'image',
src: './index.html',
attributes: { id: 'test1'},
};
obj.parse(str).should.deep.equal(result);
});
it.skip('Parse images nodes', function() {
it('Parse text nodes', function() {
var str = '<div id="test1">test2 </div>';
var result = {
tagName: 'div',
attributes: { id: 'test1'},
components: [{
tagName: 'span',
type: 'text',
content: 'test2 ',
}],
};
obj.parse(str).should.deep.equal(result);
});
it('Parse nested nodes', function() {
var str = '<article id="test1"> <div></div> <footer id="test2"></footer> Text mid <div id="last"></div></article>';
var result = {
tagName: 'article',
attributes: {id: 'test1'},
components: [
{
tagName: 'div'
},{
tagName: 'footer',
attributes: { id: 'test2'},
},{
tagName: 'span',
type: 'text',
content: ' Text mid ',
},{
tagName: 'div',
attributes: { id: 'last'},
},
]
};
obj.parse(str).should.deep.equal(result);
});
it.skip('Parse text nodes', function() {
it('Parse nested text nodes', function() {
var str = '<div>content1 <div>nested</div> content2</div>';
var result = {
tagName: 'div',
components: [{
tagName: 'span',
type: 'text',
content: 'content1 ',
},{
tagName: 'div',
components: [{
tagName: 'span',
type: 'text',
content: 'nested',
}]
},{
tagName: 'span',
type: 'text',
content: ' content2',
}],
};
obj.parse(str).should.deep.equal(result);
});
it('Parse multiple nodes', function() {
var str = '<div></div><div></div>';
var result = [{ tagName: 'div'},{ tagName: 'div'}];
obj.parse(str).should.deep.equal(result);
});
});

Loading…
Cancel
Save