Is Shenma an "interpreter mode"?
Let’s open the “GOF” first and take a look at the Definition:
Given a language, define a representation of its grammar, and define an interpreter that uses this representation to interpret sentences in the language.
Before the beginning, I still need to popularize several concepts:
Abstract syntax tree:
The interpreter pattern does not explain how to create an abstract syntax tree. It does not involve grammatical analysis. An abstract syntax tree can be completed by a table-driven syntax analysis program, or created by handwritten (usually recursive descending) syntax analysis program, or provided directly by client.
Parser:
It refers to a program that describes the expressions required by the client call and forms an abstract syntax tree after parsing.
Interpreter:
Refers to a program that explains the abstract syntax tree and executes the corresponding functions of each node.
An important prerequisite to use the interpreter pattern is to define a set of grammatical rules, also known as grammar. Regardless of whether the rules of this grammar are simple or complex, these rules must be included, because the interpreter mode is to analyze and perform corresponding functions accordingly.
Let’s take a look at the structure diagram and description of the interpreter mode:
AbstractExpression: Defines the interface of the interpreter and agrees to the interpreter's interpretation operation.
TerminalExpression: TerminalExpression, used to implement operations related to terminator in syntax rules, no longer contains other interpreters. If the combination pattern is used to build an abstract syntax tree, it is equivalent to a leaf object in the combination pattern, and there can be multiple terminator interpreters.
NonterminalExpression: A non-terminal interpreter, used to implement non-terminal-related operations in syntax rules. Usually, an interpreter corresponds to a syntax rule and can contain other interpreters. If the composition pattern is used to build an abstract syntax tree, it is equivalent to a combination object in the composition pattern. There can be multiple non-terminal interpreters.
Context: Context, usually contains data required by each interpreter or public functions.
Client: A client refers to a client that uses an interpreter. Usually, expressions made according to the language's syntax are converted into an abstract syntax tree described by the interpreter object, and then an explanation operation is called.
Here we use an XML example to understand the interpreter pattern:
First, we need to design a simple grammar for the expression. For general purpose, use root to represent the root element, abc, etc. to represent the element. A simple xml is as follows:
The code copy is as follows:
<?xml version="1.0" encoding="UTF-8">
<root id="rootId">
<a>
<b>
<c name="testC">12345</c>
<d id="1">d1</d>
<d id="2">d2</d>
<d id="3">d3</d>
<d id="4">d4</d>
</b>
</a>
</root>
The grammar of the convention expression is as follows:
1. Get the value of a single element: Start from the root element and all the way to the element you want to get the value. The middle of the element is separated by "/" and no "/" is added before the root element. For example, the expression "root/a/b/c" means to obtain the values of element c under the root element, element a, element b, and element c.
2. Get the value of the attribute of a single element: Of course there are multiple attributes. The attribute to obtain the value must be the attribute of the last element of the expression. Add "." after the last element and then add the name of the attribute. For example, the expression "root/a/b/c.name" means obtaining the value of the name attribute of the root element, element a, element b, element c.
3. Get the value of the same element name, of course, there are multiple elements. The element to obtain the value must be the last element of the expression, and add "$" after the last element. For example, the expression "root/a/b/d$" represents the collection of values of multiple d elements under the root element, under the a element, and under the b element.
4. Get the value of the attribute with the same element name, of course, there are multiple: the element to obtain the attribute value must be the last element of the expression, and add "$" after the last element. For example, the expression "root/a/b/d$.id$" represents the collection of values of multiple d elements id attributes under the root element, under the a element, and under the b element.
The above xml, corresponding to the abstract syntax tree, and the possible structure is shown in the figure:
Let's take a look at the specific code below:
1. Define the context:
The code copy is as follows:
/**
* Context, used to contain some global information required by the interpreter
* @param {String} filePathName [Path and name of the xml that needs to be read]
*/
function Context(filePathName) {
// The previous processed element
this.preEle = null;
// xml Document object
this.document = XmlUtil.getRoot(filePathName);
}
Context.prototype = {
// Reinitialize the context
reInit: function () {
this.preEle = null;
},
/**
* Methods for public use of each Expression
* Get the current element according to the name of the parent element and the current element
* @param {Element} pEle [parent element]
* @param {String} eleName [current element name]
* @return {Element|null} [current element found]
*/
getNowEle: function (pEle, eleName) {
var tempNodeList = pEle.childNodes;
var nowEle;
for (var i = 0, len = tempNodeList.length; i < len; i++) {
if ((nowEle = tempNodeList[i]).nodeType === 1)
if (nowEle.nodeName === eleName)
return nowEle;
}
return null;
},
getPreEle: function () {
return this.preEle;
},
setPreEle: function (preEle) {
this.preEle = preEle;
},
getDocument: function () {
return this.document;
}
};
In the context, I used a tool object XmlUtil to get xmlDom. Below I am using DOMPaser of DOM3. Some browsers may not support it. Please use the base browser:
The code copy is as follows:
// Tool Object
// parse xml to get the corresponding Document object
var XmlUtil = {
getRoot: function (filePathName) {
var parser = new DOMParser();
var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
return xmldom;
}
};
Here is the code for the interpreter:
The code copy is as follows:
/**
* Elements are used as interpreters corresponding to non-terminals to interpret and execute intermediate elements
* @param {String} eleName [name of element]
*/
function ElementExpression(eleName) {
this.eles = [];
this.eleName = eleName;
}
ElementExpression.prototype = {
addEle: function (eleName) {
this.eles.push(eleName);
return true;
},
removeEle: function (ele) {
for (var i = 0, len = this.eles.length; i < len; i++) {
if (ele === this.eles[i])
this.eles.splice(i--, 1);
}
return true;
},
interpret: function (context) {
// First take out the current element in the context as the parent element
// Find the xml element corresponding to the current element name and set it back to the context
var pEle = context.getPreEle();
if (!pEle) {
// Indicates that the root element is now obtained
context.setPreEle(context.getDocument().documentElement);
} else {
// Get the current element based on the name of the parent element and the element to be searched
var nowEle = context.getNowEle(pEle, this.eleName);
// Put the currently retrieved element into the context
context.setPreEle(nowEle);
}
var ss;
// Loop to call the interpret method of the child element
for (var i = 0, len = this.eles.length; i < len; i++) {
ss = this.eles[i].interpret(context);
}
// Return the explanation result of the last interpreter. Generally, the last interpreter is the terminator interpreter.
return ss;
}
};
/**
* Elements are used as the interpreter corresponding to the terminator
* @param {String} name [name of element]
*/
function ElementTerminalExpression(name) {
this.eleName = name;
}
ElementTerminalExpression.prototype = {
interpret: function (context) {
var pEle = context.getPreEle();
var ele = null;
if (!pEle) {
ele = context.getDocument().documentElement;
} else {
ele = context.getNowEle(pEle, this.eleName);
context.setPreEle(ele);
}
// Get the value of the element
return ele.firstChild.nodeValue;
}
};
/**
* The attribute is used as the interpreter corresponding to the terminator
* @param {String} propName [name of attribute]
*/
function PropertyTerminalExpression(propName) {
this.propName = propName;
}
PropertyTerminalExpression.prototype = {
interpret: function (context) {
// Get the value of the last element attribute directly
return context.getPreEle().getAttribute(this.propName);
}
};
Let's first look at how to use the interpreter to get the value of a single element:
The code copy is as follows:
void function () {
var c = new Context();
// Want to get the value of multiple d elements, that is, the value of the following expression: "root/a/b/c"
// First, you need to build an abstract syntax tree for the interpreter
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var cEle = new ElementTerminalExpression('c');
// Combination
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(cEle);
console.log('c's value is = ' + root.interpret(c));
}();
Output: The value of c is = 12345
Then we use the above code to get the value of the attribute of a single element:
The code copy is as follows:
void function () {
var c = new Context();
// Want to get the id attribute of the d element, that is, the value of the following expression: "a/b/c.name"
// This time c is not over, and you need to modify c to ElementExpression
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var cEle = new ElementExpression('c');
var prop = new PropertyTerminalExpression('name');
// Combination
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(cEle);
cEle.addEle(prop);
console.log('c's property name value is = ' + root.interpret(c));
// If you want to use the same context and parse continuously, you need to reinitialize the context object
// For example, you need to re-get the value of the attribute name again in succession, of course you can recombine the elements
// Re-parse, as long as the same context is used, the context object needs to be re-initialized
c.reInit();
console.log('Reget c's property name value is = ' + root.interpret(c));
}();
Output: The attribute name value of c is = testC Retrieve the attribute name value of c is = testC
explain:
1. Interpreter mode function:
The interpreter pattern uses interpreter objects to represent and process corresponding syntax rules. Generally, an interpreter handles a syntax rule. Theoretically, as long as the syntax-compliant expressions can be represented by the interpreter object and can form an abstract syntax tree, the interpreter pattern can be used to handle it.
2. Syntax rules and interpreters
There is a correspondence between syntax rules and interpreters. Generally, an interpreter handles a syntax rule, but the opposite is not true. A syntax rule can have multiple interpretations and processing, that is, a syntax rule can correspond to multiple interpreters.
3. Context commonality
Context plays a very important role in interpreter mode. Because the context is passed to all interpreters. Therefore, the state of the interpreter can be stored and accessed in the context. For example, the previous interpreter can store some data in the context, and the latter interpreter can obtain these values.
In addition, some data outside the interpreter can be passed through the context, but the interpreter needs it, and some global, public data.
There is also a function in the context, which is that it can provide the common functions of all interpreter objects, similar to object combinations, rather than using inheritance to obtain common functions, which can be called in each interpreter object.
4. Who will build an abstract syntax tree
In the previous example, it is very troublesome to manually build the abstract syntax tree on the client side, but in the interpreter mode, this part of the function is not involved, and it is only responsible for interpreting and processing the constructed abstract syntax tree. We will introduce that we can provide a parser to convert expressions into abstract syntax trees.
There is another problem, that is, a syntax rule can correspond to multiple interpreter objects, that is, the same element can be converted into multiple interpreter objects, which means that the same expression can form an unnecessary abstract syntax tree, which also makes it difficult to build an abstract syntax tree and the workload is very large.
5. Who is responsible for explaining the operation
As long as the abstract syntax tree is defined, the interpreter must be responsible for interpreting and executing. Although there are different syntax rules, the interpreter is not responsible for choosing which interpreter object to use to interpret the execution syntax rules. The function of selecting the interpreter is completed when building an abstract syntax tree.
6. The order of call of interpreter mode
1) Create a context object
2) Create multiple interpreter objects and combine abstract syntax trees
3) Invoke the interpreter object's interpretation operation
3.1) Store and access the state of the interpreter through the context.
For non-terminator interpreter objects, recursively call the subinterpreter object it contains.
The essence of the interpreter pattern: *separate implementation, interpret execution*
The interpreter module uses an interpreter object to process a syntax rule to separate complex functions; then selects the functions that need to be executed, and combines these functions into an abstract syntax tree that needs to be interpreted and executed; then interprets execution according to the abstract syntax tree to implement corresponding functions.
On the surface, the interpreter mode focuses on the processing of custom syntax that we don't usually use; but in essence, the idea of the interpreter mode is then separation, encapsulation, simplification, and is the same as many modes.
For example, the interpreter mode can be used to simulate the function of the state mode. If the syntax to be processed by the interpreter mode is simplified to only one state mark, the interpreter is regarded as a processing object for the state. For the same syntax representing the state, there can be many unused interpreters, that is, there are many objects with different processing states. When creating an abstract syntax tree, it is simplified to create the corresponding interpreter based on the state mark, and there is no need to build a tree.
Similarly, the interpreter mode can simulate the function of implementing the policy mode, the function of the decorator mode, etc., especially the process of simulating the function of the decorator mode, and the process of building an abstract syntax tree will naturally correspond to the process of combining the decorator.
The interpreter mode is usually not fast (mostly very slow), and error debugging is difficult (Part 1: Although debugging is difficult, it actually reduces the possibility of errors), but its advantages are obvious. It can effectively control the complexity of interfaces between modules. For functions that have low execution frequency but high code frequency enough, and are very diverse, interpreters are very suitable for modes. In addition, the interpreter has another less noticeable advantage, which is that it can be conveniently cross-language and cross-platform.
Advantages and disadvantages of interpreter mode:
advantage:
1. Easy to implement syntax
In interpreter mode, a syntax rule is interpreted with an interpreter object to interpret execution. For the implementation of the interpreter, the function becomes relatively simple. You only need to consider the implementation of this syntax rule, and you don’t have to worry about anything else. 2. Easy to expand new syntax
It is precisely because of the way an interpreter object is responsible for a syntax rule that extending new syntax is very easy. The new syntax has been expanded, and you only need to create the corresponding interpreter object and use this new interpreter object when creating an abstract syntax tree.
shortcoming:
Not suitable for complex syntax
If the syntax is particularly complex, the work of building the abstract syntax tree required by the interpreter pattern is very difficult, and it is possible to build multiple abstract syntax trees. Therefore, the interpreter pattern is not suitable for complex syntax. It might be better to use a syntax analyzer or compiler generator.
When to use it?
When there is a language that needs to be interpreted and executed, and sentences in that language can be represented as an abstract syntax tree, you can consider using the interpreter pattern.
When using the interpreter mode, there are two other features that need to be considered. One is that the syntax should be relatively simple. The syntax that is too responsible is not suitable for using the interpreter mode. The other is that the efficiency requirements are not very high and the efficiency requirements are very high, and it is not suitable for use.
The previous introduction was how to obtain the value of a single element and the value of a single element attribute. Let’s take a look at how to obtain the values of multiple elements, as well as the values of the names of each other in multiple elements, and the previous tests are all artificially combined abstract syntax trees. We also implement the following simple parser to convert expressions that conform to the syntax defined above into abstract syntax trees of the interpreter implemented above: I directly posted the code:
The code copy is as follows:
// Read the values of multiple elements or attributes
(function () {
/**
* Context, used to contain some global information required by the interpreter
* @param {String} filePathName [Path and name of the xml that needs to be read]
*/
function Context(filePathName) {
// Multiple elements that were processed in the previous
this.preEles = [];
// xml Document object
this.document = XmlUtil.getRoot(filePathName);
}
Context.prototype = {
// Reinitialize the context
reInit: function () {
this.preEles = [];
},
/**
* Methods for public use of each Expression
* Get the current element according to the name of the parent element and the current element
* @param {Element} pEle [parent element]
* @param {String} eleName [current element name]
* @return {Element|null} [current element found]
*/
getNowEles: function (pEle, eleName) {
var elements = [];
var tempNodeList = pEle.childNodes;
var nowEle;
for (var i = 0, len = tempNodeList.length; i < len; i++) {
if ((nowEle = tempNodeList[i]).nodeType === 1) {
if (nowEle.nodeName === eleName) {
elements.push(nowEle);
}
}
}
return elements;
},
getPreEles: function () {
return this.preEles;
},
setPreEles: function (nowEles) {
this.preEles = nowEles;
},
getDocument: function () {
return this.document;
}
};
// Tool Object
// parse xml to get the corresponding Document object
var XmlUtil = {
getRoot: function (filePathName) {
var parser = new DOMParser();
var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
return xmldom;
}
};
/**
* Elements are used as interpreters corresponding to non-terminals to interpret and execute intermediate elements
* @param {String} eleName [name of element]
*/
function ElementExpression(eleName) {
this.eles = [];
this.eleName = eleName;
}
ElementExpression.prototype = {
addEle: function (eleName) {
this.eles.push(eleName);
return true;
},
removeEle: function (ele) {
for (var i = 0, len = this.eles.length; i < len; i++) {
if (ele === this.eles[i]) {
this.eles.splice(i--, 1);
}
}
return true;
},
interpret: function (context) {
// First take out the current element in the context as the parent element
// Find the xml element corresponding to the current element name and set it back to the context
var pEles = context.getPreEles();
var ele = null;
var nowEles = [];
if (!pEles.length) {
// Indicates that the root element is now obtained
ele = context.getDocument().documentElement;
pEles.push(ele);
context.setPreEles(pEles);
} else {
var tempEle;
for (var i = 0, len = pEles.length; i < len; i++) {
tempEle = pEles[i];
nowEles = nowEles.concat(context.getNowEles(tempEle, this.eleName));
// Stop if you find one
if (nowEles.length) break;
}
context.setPreEles([nowEles[0]]);
}
var ss;
// Loop to call the interpret method of the child element
for (var i = 0, len = this.eles.length; i < len; i++) {
ss = this.eles[i].interpret(context);
}
return ss;
}
};
/**
* Elements are used as the interpreter corresponding to the terminator
* @param {String} name [name of element]
*/
function ElementTerminalExpression(name) {
this.eleName = name;
}
ElementTerminalExpression.prototype = {
interpret: function (context) {
var pEles = context.getPreEles();
var ele = null;
if (!pEles.length) {
ele = context.getDocument().documentElement;
} else {
ele = context.getNowEles(pEles[0], this.eleName)[0];
}
// Get the value of the element
return ele.firstChild.nodeValue;
}
};
/**
* The attribute is used as the interpreter corresponding to the terminator
* @param {String} propName [name of attribute]
*/
function PropertyTerminalExpression(propName) {
this.propName = propName;
}
PropertyTerminalExpression.prototype = {
interpret: function (context) {
// Get the value of the last element attribute directly
return context.getPreEles()[0].getAttribute(this.propName);
}
};
/**
* Multiple attributes are used as the interpreter corresponding to the terminator
* @param {String} propName [name of attribute]
*/
function PropertysTerminalExpression(propName) {
this.propName = propName;
}
PropertysTerminalExpression.prototype = {
interpret: function (context) {
var eles = context.getPreEles();
var ss = [];
for (var i = 0, len = eles.length; i < len; i++) {
ss.push(eles[i].getAttribute(this.propName));
}
return ss;
}
};
/**
* Interpretation processing object with multiple elements as terminators
* @param {[type]} name [description]
*/
function ElementsTerminalExpression(name) {
this.eleName = name;
}
ElementsTerminalExpression.prototype = {
interpret: function (context) {
var pEles = context.getPreEles();
var nowEles = [];
for (var i = 0, len = pEles.length; i < len; i++) {
nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
}
var ss = [];
for (i = 0, len = nowEles.length; i < len; i++) {
ss.push(nowEles[i].firstChild.nodeValue);
}
return ss;
}
};
/**
* Multiple elements are interpreted as non-terminals
*/
function ElementsExpression(name) {
this.eleName = name;
this.eles = [];
}
ElementsExpression.prototype = {
interpret: function (context) {
var pEles = context.getPreEles();
var nowEles = [];
for (var i = 0, len = pEles.length; i < len; i++) {
nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
}
context.setPreEles(nowEles);
var ss;
for (i = 0, len = this.eles.length; i < len; i++) {
ss = this.eles[i].interpret(context);
}
return ss;
},
addEle: function (ele) {
this.eles.push(ele);
return true;
},
removeEle: function (ele) {
for (var i = 0, len = this.eles.length; i < len; i++) {
if (ele === this.eles[i]) {
this.eles.splice(i--, 1);
}
}
return true;
}
};
void function () {
// "root/a/b/d$"
var c = new Context('Interpreter.xml');
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var dEle = new ElementsTerminalExpression('d');
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(dEle);
var ss = root.interpret(c);
for (var i = 0, len = ss.length; i < len; i++) {
console.log('d value is = ' + ss[i]);
}
}();
void function () {
// a/b/d$.id$
var c = new Context('Interpreter.xml');
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var dEle = new ElementsExpression('d');
var prop = new PropertysTerminalExpression('id');
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(dEle);
dEle.addEle(prop);
var ss = root.interpret(c);
for (var i = 0, len = ss.length; i < len; i++) {
console.log('d property id value is = ' + ss[i]);
}
}();
// parser
/**
* Implementation ideas of parser
* 1. Decompose the expressions passed by the client, decompose them into elements one by one, and use a corresponding analytical model to encapsulate some information about this element.
* 2. Convert it into the corresponding parser object based on the information of each element.
* 3. Combine these parser objects in order to get an abstract syntax tree.
*
* Why don’t you merge 1 and 2, and directly decompose an element and convert it into the corresponding parser object?
* 1. Functional separation, do not make the functions of a method too complicated.
* 2. For future modification and extension, the syntax is now simple, so there is little to consider when converting into a parser object, and it is not difficult to convert directly, but if the syntax is complicated, the direct conversion will be very messy.
*/
/**
* Used to encapsulate the corresponding attributes of each parsed element
*/
function ParserModel() {
// Whether a single value
this.singleValue;
// Whether it is an attribute, either an attribute or an element
this.propertyValue;
// Whether to terminate
this.end;
}
ParserModel.prototype = {
isEnd: function () {
return this.end;
},
setEnd: function (end) {
this.end = end;
},
isSingleValue: function () {
return this.singleValue;
},
setSingleValue: function (oneValue) {
this.singleValue = oneValue;
},
isPropertyValue: function () {
return this.propertyValue;
},
setPropertyValue: function (propertyValue) {
this.propertyValue = propertyValue;
}
};
var Parser = function () {
var BACKLASH = '/';
var DOT = '.';
var DOLLAR = '$';
// Record the names of elements that need to be parsed according to the order of decomposition
var listEle = null;
// Start the first step --------------------------------------------------------------------------------------------------------------------------
/**
* Pass in a string expression, and then parse it and combine it into an abstract syntax tree
* @param {String} expr [Describe the string expression to take the value]
* @return {Object} [corresponding abstract syntax tree]
*/
function parseMapPath(expr) {
// First divide the string according to "/"
var tokenizer = expr.split(BACKLASH);
// Table of decomposed values
var mapPath = {};
var onePath, eleName, propName;
var dotIndex = -1;
for (var i = 0, len = tokenizer.length; i < len; i++) {
onePath = tokenizer[i];
if (tokenizer[i + 1]) {
// There is another value, which means that this is not the last element
// According to the current syntax, the attribute must be at the end, so it is not the attribute.
setParsePath(false, onePath, false, mapPath);
} else {
// It's the end
dotIndex = onePath.indexOf(DOT);
if (dotIndex >= 0) {
// It means that you want to get the value of the attribute, so divide it according to "."
// The first one is the element name, and the second one is the attribute name
eleName = onePath.substring(0, dotIndex);
propName = onePath.substring(dotIndex + 1);
// The element in front of the property is naturally not the last one, nor is it the property
setParsePath(false, eleName, false, mapPath);
// Set attributes. According to the current syntax definition, the attribute can only be the last one.
setParsePath(true, propName, true, mapPath);
} else {
// Instructions are taken as the value of the element, and the value of the last element
setParsePath(true, onePath, false, mapPath);
}
break;
}
}
return mapPath;
}
/**
* Set the element name to be parsed according to the decomposed location and name
* @param {Boolean} end [Is it last]
* @param {String} ele [element name]
* @param {Boolean} propertyValue [whether to take the property]
* @param {Object} mapPath [Set the name of the element that needs to be parsed, and the table of the parsing model corresponding to the element]
*/
function setParsePath(end, ele, propertyValue, mapPath) {
var pm = new ParserModel();
pm.setEnd(end);
// If the symbol "$" is not a value
pm.setSingleValue(!(ele.indexOf(DOLLAR) >= 0));
pm.setPropertyValue(propertyValue);
// Remove "$"
ele = ele.replace(DOLLAR, '');
mapPath[ele] = pm;
listEle.push(ele);
}
// Start the second step --------------------------------------------------------------------------------------------------------------------------
/**
* Convert the decomposed element name into the corresponding interpreter object according to the corresponding analytical model.
* @param {Object} mapPath [The decomposed element name to be parsed, and the parsing model corresponding to the element]
* @return {Array} [Convert each element into an array of corresponding interpreter objects]
*/
function mapPath2Interpreter(mapPath) {
var list = [];
var pm, key;
var obj = null;
// It is necessary to convert it into interpreter objects in the order of decomposition
for (var i = 0, len = listEle.length; i < len; i++) {
key = listEle[i];
pm = mapPath[key];
// Not the last one
if (!pm.isEnd()) {
if (pm.isSingleValue())
// is a value, conversion
obj = new ElementExpression(key);
else
// is multiple values, conversion
obj = new ElementsExpression(key);
} else {
// It's the last one
// is the attribute value
if (pm.isPropertyValue()) {
if (pm.isSingleValue())
obj = new PropertyTerminalExpression(key);
else
obj = new PropertysTerminalExpression(key);
// Take the value of the element
} else {
if (pm.isSingleValue())
obj = new ElementTerminalExpression(key);
else
obj = new ElementsTerminalExpression(key);
}
}
list.push(obj);
}
return list;
}
// Start the third step ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/**
* Build an abstract syntax tree
* @param {[type]} list [Convert each element into an array of corresponding interpreter objects]
* @return {[type]} [description]
*/
function buildTree(list) {
// The first object, also the returned object, is the root of the abstract syntax tree
var returnReadXMLExpr = null;
// Define the previous object
var preReadXmlExpr = null;
var readXml, ele, eles;
for (var i = 0, len = list.length; i < len; i++) {
readXml = list[i];
// Description is the first element
if (preReadXmlExpr === null) {
preReadXmlExpr = readXml;
returnReadXMLExpr = readXml;
// Add the element to the previous object and set the object to oldRe
// As the parent node of the next object
} else {
if (preReadXmlExpr instanceof ElementExpression) {
ele = preReadXmlExpr;
ele.addEle(readXml);
preReadXmlExpr = readXml;
} else if (preReadXmlExpr instanceof ElementsExpression) {
eles = preReadXmlExpr;
eles.addEle(readXml);
preReadXmlExpr = readXml;
}
}
}
return returnReadXMLExpr;
}
return {
// Public method
parse: function (expr) {
listEle = [];
var mapPath = parseMapPath(expr);
var list = mapPath2Interpreter(mapPath);
return buildTree(list);
}
};
}();
void function () {
// Prepare the context
var c = new Context('Interpreter.xml');
// Get the abstract syntax tree by parsing it
var readXmlExpr = Parser.parse('root/a/b/d$.id$');
// Request parsing and get the return value
var ss = readXmlExpr.interpret(c);
console.log('------------parsing--------------');
for (var i = 0, len = ss.length; i < len; i++) {
console.log('d property id value is = ' + ss[i]);
}
console.log('---------------parsed--------------');
// If you want to use the same context and parse continuously, you need to reinitialize the context object
c.reInit();
var readxmlExpr2 = Parser.parse('root/a/b/d$');
var ss2 = readxmlExpr2.interpret(c);
console.log('------------parsing--------------');
for (i = 0, len = ss2.length; i < len; i++) {
console.log('d value is = ' + ss2[i]);
}
console.log('---------------parsed--------------');
c.reInit();
var readxmlExpr3 = Parser.parse('root/a/b/c');
var ss3 = readxmlExpr3.interpret(c);
console.log('------------parsing--------------');
console.log('c's name attribute value is = ' + ss3);
console.log('---------------parsed--------------');
c.reInit();
var readxmlExpr4 = Parser.parse('root/a/b/c.name');
var ss4 = readxmlExpr4.interpret(c);
console.log('------------parseing--------------');
console.log('c's name attribute value is = ' + ss4);
console.log('---------------parsed--------------');
}();
// This implements some functions similar to XPath
// That's right, it's similar to some functions of jQuery selector
}());
Output: The value of d is = d1
The value of d is = d2
The value of d is = d3
The value of d is = d4
The value of the attribute id of d is = 1
The value of attribute id of d is = 2
The value of the attribute id of d is = 3
The value of the attribute id of d is = 4
------------parsing--------------
The value of the attribute id of d is = 1
The value of attribute id of d is = 2
The value of the attribute id of d is = 3
The value of the attribute id of d is = 4
---------------parsed--------------
------------parsing--------------
The value of d is = d1
The value of d is = d2
The value of d is = d3
The value of d is = d4
---------------parsed--------------
------------parsing--------------
The name attribute value of c is = 12345
---------------parsed--------------
------------parseing--------------
The value of c's name attribute is = testC
---------------parsed--------------