console
const MATCHTYPE={
TAG:'Tag',
ID:'id',
CLASS:'CLASS'
}
const RELATIONTYPE={
SUBSELECTOR:'SubSelector',//NO COMBINATOR
DESCENDANT:'Descendant', // "Space" combinator
CHILD: 'Child', // > combinator
DIRECTADJACENT:'DirectAdjacent', // + combinator
INDIRECTADJACENT:'IndirectAdjacent' // ~ combinator
}
// body span{
// font-size:30px;
// }
// div > div{
// width: 300px;
// }
// div.calss1 + span >div{
// height:300px;
// }
// div,span,img{
// margin:0;
// }
window.cssStr = `
div{
margin:0;
}
.dp1{
height: 300px;
margin:20px;
}
.dp1 > span{
color:#fff;
}
.sp1 + #s1{
display: inline-block;
}
.div1 div.div2 {
background: #fff;
margin:10px;
}
.div1 div.div2 .div3 ~ span{
font-size: 20px;
}
`;
var html = `
<html>
<head></head>
<body>
<div class="dp1">
111
<span class="sp1">111</span>
<span id="s1">222</span>
</div>
<section/>
<div class="div1">
<div class='div2'>
<div class='div3'>
<div>333</div>
</div>
<div></div>
<span></span>
</div>
</div>
</body>
</html>
`;
function weight(inlineStylePoint, idPoint, classPsuedoAttriPoint, elementsPoints) {
this.inlineStylePoint = inlineStylePoint.toString(16)+'';
this.idPoint = idPoint.toString(16)+'';
this.classPsuedoAttriPoint = classPsuedoAttriPoint.toString(16)+'';
this.elementsPoints = elementsPoints.toString(16)+'';
this.points = parseInt('0x'+this.inlineStylePoint + this.idPoint +this.classPsuedoAttriPoint+ this.elementsPoints,16);
}
weight.prototype.large = function (weight) {
return this.points > weight.points;
}
weight.prototype.small = function (weight){
return this.points < weight.points;
}
weight.prototype.equal = function (weight){
return this.points == weight.points;
}
function _parseCssSelectorToWeight(innerStyles){
let first = 0 ;//inline
let second = 0;//id
let third = 0;//class
let fourth = 0;//element
for(let i = 0 ;innerStyles && i<innerStyles.length;i++){
let innerStyleRule = innerStyles[i];
let matchTypes = innerStyleRule.matchType;
for(let j = 0 ;matchTypes && j<matchTypes.length; j++){
switch (matchTypes[j]){
case MATCHTYPE.ID:
second++;
break;
case MATCHTYPE.CLASS:
third++;
break;
case MATCHTYPE.TAG:
fourth++;
break;
default :
first++;
break;
}
}
}
return new weight(first,second,third,fourth);
}
function applyTheCssStyle(dom) {
//relationship
}
function processTheCombinators() {
}
//~
//div div > div div + span
function generatorTheArraySelectorList(selectorText) {
let selectorRex = /([^>~\+\s]+)([\s>~\+]+)?/ig;
let result;
let selectorList = [];
while (result = selectorRex.exec(selectorText)) {
let originCombinator = result[2];
let holeMatch = result[0];
let selector = result[1];
let combintor = originCombinator && originCombinator.trim() ? originCombinator.trim() : (originCombinator && !originCombinator.trim() ? " " : undefined);
if (selector) {
selectorList.push(selector);
}
if (combintor) {
selectorList.push(combintor);
}
// console.log('regxsess: ',result)
}
return selectorList;
}
function innerStyleRule(values,matchTypes,relation){
this.values = values;
this.matchType =[];
//
for(let i = values.length-1 ;i>=0;i--) {
switch (matchTypes && matchTypes[i]) {
case '.':
this.matchType.push(MATCHTYPE.CLASS);
break;
case "#":
this.matchType.push(MATCHTYPE.ID);
break;
default :
this.matchType.push(MATCHTYPE.TAG);
break
}
}
switch (relation) {
case ' ':
this.relation = RELATIONTYPE.DESCENDANT;
break;
case '+':
this.relation = RELATIONTYPE.DIRECTADJACENT;
break;
case '~':
this.relation = RELATIONTYPE.INDIRECTADJACENT;
break;
case '>':
this.relation = RELATIONTYPE.CHILD;
break;
default :
this.relation = RELATIONTYPE.SUBSELECTOR;
break;
}
}
function isCombinator(str){
if(!str)return false;
return !!str.match(/[\s\+>~]/g)
}
function innerSelectorRule(selectorArray){
//combinator 只存在于中间
let innerSelectorRules = [];
//匹配任何类名,单词
let matchValue = /([\w_-]+)/ig;
//匹配选择器 类选择器 和 id选择器
let typeRegex = /([#\.]+)/ig;
for(let i = selectorArray.length-1 ; i>=0;i--){
let selector = selectorArray[i];
let value = selector.match(matchValue);
let type = selector.match(typeRegex);
//process css in claass
value && innerSelectorRules.push(new innerStyleRule(value ,type,selectorArray[i-1]))
}
return innerSelectorRules;
}
let styleSheets1 = [];
//转换驼峰
function switchToCam(str, delimiter) {
let regStr = '-+([^-])';
if (delimiter) {
regStr = regStr.replace(/-/g, delimiter);
}
let camRegex = new RegExp(regStr, 'i');
let matched = str.match(camRegex);
if (matched) {
return str.replace(matched[0], matched[1].toUpperCase());
}
else {
return str;
}
}
CSSStyleRule.prototype._resetCursor=function(){
this.cursor= 0;
};
CSSStyleRule.prototype._isCursorEnd=function(){
return this.cursor == this.innerStyles.length-1;
};
let IDSTYLERULEMAP=[];
let CLASSSTYLEMAP=[];
let TAGSTYLEMAP=[];
let UNKNOWSTYLEMAP=[];
let CSSRULEHASHMAP={
ID:IDSTYLERULEMAP,
CLASS:CLASSSTYLEMAP,
TAG:TAGSTYLEMAP,
UNKNOW:UNKNOWSTYLEMAP
}
///CSS DEFINITION
function CSSStyleRule(cssText, selectorText, cssBody) {
this.cssText = cssText;
this.selectorText = selectorText;
this.selectorArray = generatorTheArraySelectorList(selectorText)
this.innerStyles =innerSelectorRule(this.selectorArray);
this.cursor = 0;
this.cssBody = cssBody;
this.style = {};
let rules = this.cssBody.split(';');
if (!rules)return;
//last one is empty.toString(16)
if (!rules[rules.length - 1]) {
rules.pop();
}
for (let i = 0; i < rules.length; i++) {
let rulePair = rules[i].trim();
let ruleKeyValue = rulePair.split(':');
let ruleKey = ruleKeyValue[0];
let ruleValue = ruleKeyValue[1];
this.style[switchToCam(ruleKey)] = ruleValue;
}
this.weight = _parseCssSelectorToWeight(this.innerStyles);
//最右
let rightRule = this.innerStyles[0];
if(rightRule.matchType.indexOf(MATCHTYPE.ID)>=0){
CSSRULEHASHMAP.ID.push(this);
return;
}
if(rightRule.matchType.indexOf(MATCHTYPE.CLASS)>=0){
CSSRULEHASHMAP.CLASS.push(this)
return;
}
if(rightRule.matchType.indexOf(MATCHTYPE.TAG)>=0){
//TAG是一定要得,需要过滤的是没有class 和 id的情况
CSSRULEHASHMAP.TAG.push(this);
return;
}
CSSRULEHASHMAP.UNKNOW.push(this);
}
function cssRuleGenerator(cssSnippet) {
let cssRegex = /([^\{\}]+?){([^\{\}]+)}/ig;
let result;
let stylesheet = [];
while (result = cssRegex.exec(cssSnippet)) {
// console.log('regex: ', result);
let holeMatch = result[0];
let selector = result[1] ;
let cssRuleBody = result[2];
let selectorList = selector;
selectorList = selectorList.trim();
let groups = selectorList.split(',');
for (let i = 0; i < groups.length; i++) {
let seletorTex = groups[i].trim();
stylesheet.push(new CSSStyleRule(holeMatch.trim(), seletorTex.trim(), cssRuleBody.trim()));
}
}
return stylesheet
}
//console.log(switchToCam('font_size', "_"));
//!IMPORTANT!!!!!!!!!!!
//!IMPORTANT!!!!!!!!!!!
//!IMPORTANT!!!!!!!!!!!
//!IMPORTANT!!!!!!!!!!!
//!IMPORTANT!!!!!!!!!!!
var styleSheets = [];
cssRuleGenerator(styleSheets, cssStr);
// console.log(styleSheets)
let IDHASHMAP = {};
function htmlNode(name, parent, value, attributes) {
this.name = name;
this.parent = parent;
this.value = value;
this.attributes = attributes;
}
function attributeNode(key, value) {
this.key = key;
this.value = value;
}
function LiGetElementsById(id) {
return IDHASHMAP[id];
}
htmlNode.prototype.LiGetElementsById = LiGetElementsById;
htmlNode.prototype.innerHTML = '';
htmlNode.prototype.getAttribute = function(key){
for(let i= 0 ;(i<this.attributes.length)&&this.attributes;i++){
let attriNode = this.attributes[i];
if(attriNode.key == key){
return attriNode;
}
}
};
htmlNode.prototype.hasId = function(idName){
let attriNode = this.getAttribute('id');
if(!attriNode)return;
return attriNode.value == idName;
};
htmlNode.prototype.hasClassName = function(className){
let attriNode = this.getAttribute('class');
if(!attriNode)return;
let classValue = attriNode.value;
let classList = classValue.split(/[\s]+/g);
for(let j = 0 ;(j<classList.length)&&classList;j++){
let classname = classList[j];
if(classname == className)return true;
}
};
let htmlstack = [];
function getTop(htmlstack) {
if (htmlstack.length) {
return htmlstack[htmlstack.length - 1]
}
else {
return {};
}
}
function popStack(htmlstack) {
htmlstack.pop();
}
function constructTheTree(nodeStack) {
//give every node a chilren property
let root;
for (var i = 0; i < nodeStack.length; i++) {
let node = nodeStack[i];
if (node.parent.name) {
if (!node.parent.children) {
node.parent.children = [];
node.parent.children.push(node)
}
else {
node.parent.children.push(node);
}
}
else {
root = node
}
}
root.parent = null;
return root;
}
function findAttributeKeyPair(str) {
let attributeRegex = /(([\w]+)=(('|")[\s\w-]+('|")))/g;
let result;
let pairs = [];
let attributeCollection = [];
while (result = attributeRegex.exec(str)) {
pairs.push(result[1]);
}
for (let i = 0; i < pairs.length; i++) {
let pair = pairs[i];
let keyValue = pair.split('=');
let key = keyValue[0];
let value = keyValue[1].replace(/['"]+/g, '');
attributeCollection.push(new attributeNode(key, value))
}
return attributeCollection;
}
function findTheHtmlTag(str) {
return /^<?([a-zA-Z]+)\s*/g.exec(str)[1];
}
function getAttribute(str) {
let atts = this.attributes;
let length = atts && atts.length;
for (let i = 0; i < length && length; i++) {
let attri = atts[i];
let key = attri.key;
let value = attri.value;
if (key == str) return value;
}
return null
}
function parseQueryById(dom) {
let idValue = getAttribute.call(dom, 'id');
if (idValue) {
IDHASHMAP[idValue] = dom;
}
}
function getInnerHtml(regexResult, node) {
let holeHtml = regexResult.input;
let tag = node.name;
let leftHtml = holeHtml.slice(regexResult.index + regexResult[0].length, holeHtml.length);
let re = new RegExp("<\\/(" + tag + ")>", "i"); // re为/<\/(div)>/i 没有/g标志,可以用match,效果和exec一样
let regMatched = leftHtml.match(re);
return leftHtml.substring(0, regMatched.index)
}
function _isMatchNodeRight(cssRule,node){
return _isMatchNodeIndex(cssRule,node,0)
}
function _isMatchNodeIndex(cssRule,node,index){
if(!node)return;
if(!node.name)return;
let styles = cssRule.innerStyles;
let innerStyle = styles && styles[index];
let matchFlagCount = 0;
if(!innerStyle)return;
let matchTypes = innerStyle.matchType;
if(!matchTypes)return;
let values = innerStyle.values;
for(let i =0 ;i<matchTypes.length ;i++){
if(matchTypes[i] == MATCHTYPE.TAG && node.name && node.name == values[i]){
matchFlagCount++;
}
if(matchTypes[i] == MATCHTYPE.CLASS && node.hasClassName && node.hasClassName(values[i])){
matchFlagCount++;
}
if(matchTypes[i] == MATCHTYPE.ID && node.hasId && node.hasId(values[i])){
matchFlagCount++;
}
}
return matchFlagCount==matchTypes.length;
}
function iteratorTheCssStyle(cssStyleRule, node) {
let RuleCombinator = cssStyleRule.innerStyles[cssStyleRule.cursor].relation;
switch (RuleCombinator) {
case RELATIONTYPE.DESCENDANT:
//console.log('descent')
if (!_isMatchNodeIndex(cssStyleRule, node, cssStyleRule.cursor))return;
cssStyleRule.cursor++;
let parent = node.parent;
if (!parent) return;
while (!_isMatchNodeIndex(cssStyleRule, parent, cssStyleRule.cursor)) {
parent = parent.parent;
//找到第一个匹配的
if (!parent) return;
}
if (cssStyleRule._isCursorEnd()) {
return true
}
//父级匹配到了
return iteratorTheCssStyle(cssStyleRule, parent);
case RELATIONTYPE.SUBSELECTOR:
return _isMatchNodeIndex(cssStyleRule, node, cssStyleRule.cursor);
case RELATIONTYPE.CHILD:
//>
if (!_isMatchNodeIndex(cssStyleRule, node, cssStyleRule.cursor))return;
cssStyleRule.cursor++;
let parentC = node.parent;
if(!parentC)return;
if(!_isMatchNodeIndex(cssStyleRule, parentC, cssStyleRule.cursor))return;
if (cssStyleRule._isCursorEnd()) {
return true
}
return iteratorTheCssStyle(cssStyleRule,parentC);
case RELATIONTYPE.DIRECTADJACENT:
//+
if (!_isMatchNodeIndex(cssStyleRule, node, cssStyleRule.cursor))return;
cssStyleRule.cursor++;
//previousSibling todo 有可能为#text 要有previousSiblingElement
let previousSibling = node.preslibingElement;
if(!_isMatchNodeIndex(cssStyleRule, previousSibling, cssStyleRule.cursor))return;
if (cssStyleRule._isCursorEnd()) {
return true
}
return iteratorTheCssStyle(cssStyleRule,previousSibling);
case RELATIONTYPE.INDIRECTADJACENT:
//~ find all pre
if (!_isMatchNodeIndex(cssStyleRule, node, cssStyleRule.cursor))return;
cssStyleRule.cursor++;
let previousSibling_inDirect = node.preslibingElement;
if (!previousSibling_inDirect) return;
while (!_isMatchNodeIndex(cssStyleRule, previousSibling_inDirect, cssStyleRule.cursor)) {
previousSibling_inDirect = previousSibling_inDirect.preslibingElement;
//找到第一个匹配的
if (!previousSibling_inDirect) return;
}
if (cssStyleRule._isCursorEnd()) {
return true
}
//同级匹配到了
return iteratorTheCssStyle(cssStyleRule, previousSibling_inDirect);
}
}
function _overrideNodeStyle(node,style){
node.style = Object.assign(node.style , style)
}
function _counterOverrideNodeStyle(node,style){
node.style = Object.assign(style , node.style);
}
function _constructTheWantedStyleSheets(node){
let styleSheets = [];
if(node.getAttribute('id')){
styleSheets = styleSheets.concat(CSSRULEHASHMAP.ID);
}
if(node.getAttribute('class')){
styleSheets = styleSheets.concat(CSSRULEHASHMAP.CLASS);
}
styleSheets = styleSheets.concat(CSSRULEHASHMAP.TAG);
styleSheets = styleSheets.concat(CSSRULEHASHMAP.UNKNOW);
return styleSheets;
}
function quickSort(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivotItem = arr[pivotIndex];
var pivot = arr.splice(pivotIndex, 1)[0].weight.points;
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i].weight.points < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivotItem], quickSort(right));
}
function embedCsstoHtmlNode(node) {
let wantedStyles = _constructTheWantedStyleSheets(node);
//按照权重对sheet进行排序
let sortedWantedStyles = quickSort(wantedStyles);
//console.log(sortedWantedStyles)
//根据node有没有id,有没有class 去拿HASHMAP的值 TAG,UNKNOW一定要拿,ID,CLASS如果NODE没有,可以不拿
for (let i = 0; i < sortedWantedStyles.length; i++) {
sortedWantedStyles[i]._resetCursor();
if (iteratorTheCssStyle(sortedWantedStyles[i], node)) {
if(!node.style)node.style={};
_overrideNodeStyle(node,sortedWantedStyles[i].style);
}
}
}
//todo children 合并入下面
//firstChildren lastChildren
//outhtml
function generatorHtmlNode(html) {
//todo 把id加入hashMap
//todo class也可以加入hashMap呀,需要遍历吗?
//clear html
html = html.replace(/[\r\n]+/g, '');
html = html.trim();
let tagRegex = /<(\/)?([^\/<>]+)(\/)?>/g;
let htmlNodes = [];
let htmlCopy = html;
let result;
let root;
let cursor = 0;
let preslibingNode;
let preslibingElement;
while (result = tagRegex.exec(htmlCopy)) {
// console.log('regex:', result);
let holeMatch = result[0];
let isEnd = !!result[1];
let matchGroup = result[2];
let isEndOfOneTag = !!result[3];//是单闭合标签
let tagName = findTheHtmlTag(result[2]);
tagName = tagName.trim();
let planText = html.slice(cursor, result.index);
let parentNode = getTop(htmlstack);
let flag = (tagName == parentNode.name && isEnd);
if (planText.trim()) {
//文本不需要入栈
//parentNode 即为 父节点
let textNode = new htmlNode('#text', parentNode, planText);//htmlstack.push(textNode);
if (preslibingNode && parentNode.previousSibling != preslibingNode && preslibingNode != parentNode && parentNode.nextSibling != preslibingNode) {
//previousSibling
//构建双向链表
textNode.previousSibling = preslibingNode
preslibingNode.nextSibling = textNode;
}
preslibingNode = textNode;
//结果栈 保留所有结点
htmlNodes.push(textNode);
if(parentNode.name){
//不是root
if (!parentNode.children) {
parentNode.children = [];
parentNode.children.push(textNode)
}
else {
parentNode.children.push(textNode);
}
}
else{
root = textNode
}
}
if (!parentNode.name || !flag) {
//parentNode 即为 父节点
//非文本以及单闭合
let node = new htmlNode(tagName, parentNode, holeMatch, findAttributeKeyPair(matchGroup));
parseQueryById(node);
if (preslibingNode && parentNode.previousSibling != preslibingNode && preslibingNode != parentNode && parentNode.nextSibling != preslibingNode) {
//previousSibling
//构建双向链表
node.previousSibling = preslibingNode
preslibingNode.nextSibling = node;
}
if (preslibingElement && parentNode != preslibingElement) {
//previousSibling
//构建双向链表
node.preslibingElement = preslibingElement
preslibingElement.nextSiblingElement = node;
}
//临时栈
if (!isEndOfOneTag) {
//不是单闭合标签
node.innerHTML = getInnerHtml(result, node);
htmlstack.push(node);
}
else {
//example of <section/>
node.innerHTML = '';
}
preslibingElement = node
preslibingNode = node;
//结果栈 保留所有结点
htmlNodes.push(node);
//构造树
if(parentNode.name){
//不是root
if (!parentNode.children) {
parentNode.children = [];
parentNode.children.push(node)
}
else {
parentNode.children.push(node);
}
}
else{
root = node
}
embedCsstoHtmlNode(node)
}
else {
//slibingNode 不可能为空对象 因为为空对象就会进入上面那个循环了
preslibingNode = parentNode;
preslibingElement =parentNode
//遇到结束标识
popStack(htmlstack);
}
cursor = result.index + holeMatch.length;
}
root.parent = null;
return root;
}
let htmlstr = ''
function treeShow(root,level){
let space = '--';
let name = '';
for(let i =0;i<level;i++){
name += space
}
if(root.name!='#text') htmlstr+=('<div>'+name + root.value.replace(/</g,'<').replace(/>/g,'>')+ (root.style?JSON.stringify(root.style):'') +'</div>')
for(let i = 0;root.children&&i<root.children.length;i++){
treeShow(root.children[i],level+1);
}
return htmlstr
}
// var htmlstackc = generatorHtmlNode(html);
// var lidocument = constructTheTree(htmlstackc);
// var dom1 = lidocument.LiGetElementsById('fds');
// console.log('html tree ', lidocument);
// treeShow(lidocument,0);
// console.log('dom1: ', dom1);
document.addEventListener('DOMContentLoaded',function(e){
let htmlarea = document.getElementById('htmlarea');
let cssarea = document.getElementById('cssarea');
htmlarea.value = window.html;
cssarea.value = window.cssStr
})
document.getElementById('parse').addEventListener('click',(e)=>{
let htmlarea = document.getElementById('htmlarea');
let cssarea = document.getElementById('cssarea');
CSSRULEHASHMAP.ID=[];
CSSRULEHASHMAP.CLASS=[];
CSSRULEHASHMAP.TAG=[];
CSSRULEHASHMAP.UNKNOW=[];
let styleSheets = cssRuleGenerator(cssarea.value);
console.log(CSSRULEHASHMAP)
//cssarea.value
var lidocument = generatorHtmlNode(htmlarea.value);
// var dom1 = lidocument.LiGetElementsById('fds');
console.log('html tree111 ', lidocument);
let container = document.getElementById('container');
htmlstr= ''
container.innerHTML = ''
container.innerHTML = treeShow(lidocument,0);
// console.log('dom1: ', dom1);
});
<div>
<![CDATA[
]]>
</div>
<div>html</div>
<textarea name="" id="htmlarea" cols="100" rows="30"></textarea>
<div>css</div>
<textarea name="" id="cssarea" cols="100" rows="30"></textarea>
<button id="parse">解析</button>
<!--<script src="lib/template.js"></script>-->
<div id="container"></div>
#parse{
width: 300px;
height: 300px;
font-size: 30px;
margin-top: 30px;
}
textarea{
display: block;
}