构建ES6解析器和字符串生成器

时间:2016-07-28 00:36:42

标签: javascript oop parsing recursion ecmascript-6

我正在构建一个个人项目的解析器,一旦达到我的初始目标,我希望开源。我在动态构建DOM的递归调用方面遇到了一些麻烦。我根本不想使用任何框架来做这件事。

基本上,我的代码生成器用Javascript数组构建一个DOM字符串来表示DOM,以及用来模拟元素本身的对象。

  var example = [{elm: 'div',
  class: 'fadeIn',
  inner: 'Hello!'}]

会产生

  <div class="fadeIn">Hello!</div>

  var module = [{elm: 'div',   
  class: 'background-yellow',
  inner: example 
  }]

会产生

<div class="background-yellow">
  <div class="fadeIn">Hello!</div>
</div>

下面的代码相对较好地生成了第一个示例,尽管我已经记录了几个要完成它的事情(例如自闭标签)。我正在努力的部分是嵌套对象,它们代表嵌套节点。我的目标是递归地做这个,但是可以随意使用迭代方法 - 如果你能帮忙,我该判断谁:)

// Start with a single node
var testDom = [{
    elm: 'button',
    inner: 'click me!',
    class: 'test-elm',
    onclick: 'javascript:alert("woot!");',
}]

var nestedTest = [{
    elm: 'div',
    inner: testDom
}]

// Define
class parser {

    // This is the parser for JSML
    constructor(stdIn) {
        this.output = '';
        this.parse(stdIn);
        return this.output;
    }

    generateAttributeKeyValueString(key, value) {
        return `${key}='${value}'`;
    }

    generateDom(vNode, nestedOpenTag, nestedCloseTag) {
        var self = this,
            elmStart = `<${vNode.elm}`,
            elmEnd = `</${vNode.elm}>`,
            elmAttrs = '',
            elmContent;
        function parseInput(vNode, key) {
            // if (!vNode.hasOwnProperty(key)) return;
            var value = vNode[key];
            // custom types conditons depending on the key and value of the nodes contents
            var isActualInnerValueChildContents = (key === 'inner' && typeof value !== 'object');
            var isChildrenContentArr = (key === 'inner' && Array.isArray(value));
            var isAttributeKeyValuePair = (key !== 'elm' && key !== 'inner');

            if (isActualInnerValueChildContents) elmContent = value;
            else if (isAttributeKeyValuePair) elmAttrs += self.generateAttributeKeyValueString(key, value); // Otherwise its an attribute and value pair and should be added to the node string
            else if (isChildrenContentArr) {
                // Array of nested child nodes.
                elmStart += ` ${elmAttrs}>`;
                self.generateDom(value, elmStart, elmEnd)
            }
        }   

        for (var key in vNode) parseInput(vNode, key);

        elmStart += ` ${elmAttrs}>`; // Close off the html elements start tag now that all possible attributes have been written



        if (nestedOpenTag && nestedCloseTag) this.output = nestedOpenTag + this.output + nestedCloseTag;
        else this.output = elmStart + elmContent + elmEnd;
    }

    parse(input) {
        var self = this;
        input.forEach((vNode) => {
            self.generateDom(vNode);
        });
    }

}

parser.execute = function(vDom) {
    document.getElementsByTagName('html')[0].innerHTML += vDom.output;
    return vDom;
}

// Call
console.log(parser.execute(new parser(testDom)).output);

这是一个link,用于显示在单个节点上运行的代码。

提前感谢任何可以提供帮助的人。

1 个答案:

答案 0 :(得分:1)

我找到了几件事:

解决主要问题

如果您获得的inner值只是纯文本,则将元素的内容(elmContent)设置为该文本

当你得到一个inner值是另一个元素时,我理解的是你想获得该元素的解析版本,并将元素的内容设置为结果文本。

要获取另一个元素的解析版本,您可以定期解析它并执行parser.execute(new parser(<element>)).output

所以在generateDom中,当elmContent是另一个元素时,只需将parser.execute(new parser(value)).output设置为value,就像在value时将其设置为value一样是常规文本。而不是这样做:

if (isChildrenContentArr) {
    // Array of nested child nodes.
    elmStart += ` ${elmAttrs}>`;
    self.generateDom(value, elmStart, elmEnd)
}

这样做:

if (isChildrenContentArr) {
    // Array of nested child nodes.
    elmContent = parser.execute(new parser(value)).output
}

您甚至不需要考虑递归。请记住,您要将元素内容设置为value,已解析

永远不会使用generateDOM的最后两个参数,因此请将其删除:

generateDom(vNode) { ...

解析多个DOM元素的内容

应用上述修复将使您的某些元素有效,inner内容只是一个子元素,例如

var nestedTest = [{
    elm: 'div',
    inner: testDom
}]

但在其他情况下,它是一个数组,就像在

中一样
var app = [{
    elm: 'body',
    inner: [
        header,
        content,
        footer
    ]
}]

我注意到的另一件事是所有这些元素都在数组中。我认为你假设阵列变平了,所以像

那样
[ [element1], [element2], [element3] ]

变成

[element1, element2, element3]

然而,这不会发生在javascript中,你最终会得到一个包含其他数组的数组。因此,当您应用上面的代码并且value是一个元素数组,而不仅仅是一个元素时,它将无法工作。

首先,你可能应该删除元素周围的所有数组,所以像

这样的元素
var app = [{
    elm: 'body',
    inner: [
        header,
        content,
        footer
    ]
}]

变成

var app = {
    elm: 'body',
    inner: [
        header,
        content,
        footer
    ]
}

(注意定义周围缺少[...]

要让解析器解析这些,只需将parse中的parser函数更改为:

parse(input) {
    var self = this;
    self.generateDom(input);
}

然后,处理value是一个元素的情况,以及它分别是多个元素的情况;在[{1}}中分配value时,请添加parseInput执行:

isSingleChildContent

如果var value = vNode[key], // custom types conditons depending on the key and value of the nodes contents isActualInnerValueChildContents = (key === 'inner' && typeof value === 'string'), isChildrenContentArr = (key === 'inner' && Array.isArray(value)), isSingleChildContent = (key === 'inner' && !isChildrenContentArr && (typeof value === 'object')), isAttributeKeyValuePair = (key !== 'elm' && key !== 'inner'); 是一个字符串,而不只是任何非对象值,请注意isActualInnerValueChildContents如何成立。如果value是一个数组,则isChildrenContentArr为真,如前所述。如果value是一个对象而不是一个数组,那么新的测试isSingleChildContent是正确的,这将是一个单独的元素(删除了围绕所有元素变量的value)。

现在,如果[...] inner是单个元素,则value将解析该元素(elmContent)。但如果它是多个值,则必须枚举每个值并将其结果添加到parser.execute(new parser(value)).output,如下所示:

elmContent

以下是代码段中的最终代码。这适用于你的所有元素(我只测试了一对):

//Array of multiple child nodes
elmContent = "";

value.forEach((subValue) => {
    elmContent += parser.execute(new parser(subValue)).output;
});
/** JSML - Javascript markup language

	Benifits over traditional HTML:
		1) Remove 1 of 3 languages you must know in order to work on the front end of websites (HTML), drastically reducing the complexity of web development
		2) HTML is not flexible, JSML is as flexible as can be
		3) Much tinier syntax compared to HTML, each DOM node is represented by a simple JSON object the DOM is an array no closing tags needed either
		4) Dynamically manipulate elements with no extra frameworks needed - just use plain JS
		5) Framework and platform independant, works with 100% client side and server side libraries out there (it doesnt change anything other than your development process).
		6) Completely manage DOM elements by name instead of selector (very powerful)
		
		Major milestones remaining for v1
		1) Recursive algorithm (right now only generates a single layer of non-nested nodes)
		2) Handling for - seperated data-attributes (camelCase to hyphen-case)
		3) Consistancy between sever and client event assignment
		4) Handling for self closing tags
**/

// Input (language syntax example for static and dynamic)

// Static vNode with dynamic contents
var navigation = {
	elm: 'navigation',
	inner: menuItems(),
	class: 'navigation'
}

// Example of managing the element without explicitly describing the selector more than one time

function sampleMethodBinding(){
	alert();
}

// Dynamically created vNodes
function menuItems() {
	var items = []
	var data = [{
		elm: 'a',
		inner: 'Click here!'
	}, {
		elm: 'a',
		inner: 'here too!'
	}]

	data.forEach(function(itemData, idx) {items.push({
		elm: itemData.elm,
		href: '/page' + idx + '.html',
		inner: 'Nav item #' + idx,
	})});

	return items;
}

var header = {
	elm: 'header',
	inner: navigation,
	class: 'header'
}

var content = {
	elm: 'body',
	inner: 'This is the content area!!!',
	class: 'content'
}

var footer = {
	elm: 'footer',
	inner: navigation,
	class: 'footer'
}

// This is the virtual DOM parent
var app = {
	elm: 'body',
	inner: [
		header,
		content,
		footer
	]
}

// Start with a single node
var testDom = {
	elm: 'button',
	inner: 'click me!',
	class: 'test-elm',
	onclick: 'javascript:alert("woot!");',
}

var nestedTest = {
	elm: 'div',
	inner: testDom
}

// Define
class parser {

	// This is the parser for JSML
	constructor(stdIn) {
		this.output = '';
		this.parse(stdIn);
		return this.output;
	}

	generateAttributeKeyValueString(key, value) {
		return `${key}='${value}'`;
	}

	generateDom(vNode) {
		var self = this,
			elmStart = `<${vNode.elm}`,
			elmEnd = `</${vNode.elm}>`,
			elmAttrs = '',
			elmContent;
		function parseInput(vNode, key) {
				if (!vNode.hasOwnProperty(key)) return;
				var value = vNode[key],
				// custom types conditons depending on the key and value of the nodes contents
				isActualInnerValueChildContents = (key === 'inner' && typeof value === 'string'),
				isChildrenContentArr = (key === 'inner' && Array.isArray(value)),
				isSingleChildContent = (key === 'inner' && !isChildrenContentArr && (typeof value === 'object')),
			    isAttributeKeyValuePair = (key !== 'elm' && key !== 'inner');

			if (isActualInnerValueChildContents) elmContent = value;
			else if (isAttributeKeyValuePair) elmAttrs += self.generateAttributeKeyValueString(key, value); // Otherwise its an attribute and value pair and should be added to the node string
			else if (isChildrenContentArr) {
				//Array of multiple child nodes
				elmContent = "";
				
				value.forEach(function(subValue) {
					elmContent += parser.execute(new parser(subValue)).output;
				});
			} else if (isSingleChildContent) {
				// Just one child node
				elmContent = parser.execute(new parser(value)).output;
			}
			
		}	

		for (var key in vNode) parseInput(vNode, key);

		elmStart += ` ${elmAttrs}>`; // Close off the html elements start tag now that all possible attributes have been written
		
		this.output = elmStart + elmContent + elmEnd;
	}

	parse(input) {
		var self = this;
		self.generateDom(input);
	}

}

parser.execute = function(vDom) {
	document.getElementsByTagName('html')[0].innerHTML += vDom.output;
	return vDom;
}

// Call
document.innerHtml = parser.execute(new parser(app)).output;

相关问题