// renderTp.js 嵌套循环# Parse2Array 与 RenderTp 递归调用
import LookUp from './lookUp'
import Parse2Array from './parse2Array'
/**
* 让tokens变为dom字符串
*/
export default function (tokens, data) {
// 结果字符串
let convertedStr = ''
tokens.forEach(token => {
// 看类型
if (token[0] === 'text') { // 文本直接将内容直接 拼接
convertedStr += token[1]
} else if (token[0] === 'name') {
// 如果是 name 类型,则要使用lookup 防止 a.b.c有点符号号行是
convertedStr += LookUp(token[1], data)
} else if (token[0] === '#') {
// 增加#的对象/数组 嵌套模式
// token 和 数据
convertedStr += Parse2Array(token, data)
}
})
return convertedStr
}
// parse2Array.js 解决“.”的问题 层级嵌套
// 嵌套循环# RenderTp与 Parse2Array 递归调用
import LookUp from './lookUp'
import RenderTp from './renderTp'
/**
* 处理数组,结合rederTps互相调用实现递归
* 递归的次数,取决于数据中数组内容的次数
* token,一个简单的模板['#','student', []] 内部tokens
* { name: "高乐", hobbies: ["抽烟", "喝酒"] },
* { name: "于谦", hobbies: ["抽烟", "喝酒", "烫头"] },
* { name: "GDG", hobbies: ["抽烟", "喝酒", "剃头"] },
* 那么parseArray()函数就要递归调用renderTp函数 data数组的次数
*/
export default function (token, data) {
// console.log(token)
// console.log(data)
// 结果数组
let result = ''
// 得到数据数组中要使用的部分
const val = LookUp(token[1], data)
// val是 # 数据里数组
val.forEach(v => {
console.log(token[2], v)
/**
* “.”的tokens无法解析 需要单独处理
* result += RenderTp(token[2], v)
* 单独处理的方式,单独增加“.”则无法处理对象中的其他属性,
* 所以需要 拓展运算符,展开原内容,再原内容基础上增加“.”对应内容
* */
result += RenderTp(token[2], {
...v,
'.' : v
})
})
return result
}
// lookUp方法未更新
/**
* 功能是可以在dataObj对象中,寻找连续点符号的keyName属性
* 比如,dataObj是
* {
* a:{
* b:{
* c: 100
* }
* }
* }
* 那么lookup(dataObj,'a.b.c)
*/
export default function (keyName, dataObj) {
// 查看keyname中是否有".",符号 但不能是"."本身
if (keyName.indexOf(".") != -1 && keyName != '.' ) {
let keys = keyName.split(".");
// 设置临时变量
let temp = dataObj;
let i = 0;
while (i < keys.length) {
// 每找一层都要重新存储
temp = temp[keys[i]];
i++;
}
return temp;
}
// 没有点
return dataObj[keyName]
}
// 数据和模板
<div id="container"></div>
const tmpStr = `<div>
<ol>
{{#students}}
<li class="student">
学生[{{cname}}]的爱好<ol>
{{#hobbies}}
<li>
{{.}}
</li>
{{/hobbies}}
</ol>
</li>
{{/students}}
</ol>
</div>`;
const data = {
students: [
{ cname: "高乐", hobbies: ["抽烟", "喝酒"] },
{ cname: "于谦", hobbies: ["抽烟", "喝酒", "烫头"] },
{ cname: "GDG", hobbies: ["抽烟", "剃头"] },
],
};
const domStr = TpEngine.render(tmpStr, data);
container.innerHTML = domStr;
// Scanner.js 扫描类未调整
// 创建扫描类
export default class Scanner {
constructor(tpStr) {
console.log('render函数里 调用Scanner')
this.tpStr = tpStr
// 建立指针
this.pos = 0
// 尾部字符串,默认为整个字符串
this.tail = tpStr
}
scan(tag) {
if (this.tail.indexOf(tag) === 0) {
// tag 有多长,指针就往后移动几位
this.pos += tag.length
// tail1也要调整
this.tail = this.tpStr.substring(this.pos)
}
}
// 让指针扫描吗,直到内容结束,并返回之前查到的文字
scanUtil(stopTag) {
// 记录指针原始位置
const pos_backup = this.pos
// 当尾巴的开头不是 stopTag的时候,就说明没有扫描到stopTag
// 写 && 很有必要,防止越界
while (this.tail.indexOf(stopTag) !== 0 && !this.eos()) {
this.pos++
//
// console.log('pos--------------', this.pos)
// 重新计算尾巴,从指针开始,到最后一位(第一个参数是启始,第二个是个数,如果省略了该参数)
this.tail = this.tpStr.substr(this.pos)
}
// 返回位于 String 对象中指定位置的子字符串(两个指定下标之间的字符,如果省略该参数,那么返回的子串会一直到字符串的结尾)
return this.tpStr.substring(pos_backup, this.pos)
}
// 指针防止越界
eos() {
return this.pos >= this.tpStr.length
}
}
// parseTp2Tokens.js 未调整
import Scanner from './scaner'
import NextTokens from './nextTokens'
import RenderTp from './renderTp'
export default function (tpStr, data) {
const tokens = []
// 创建扫描器
const ScannerInit = new Scanner(tpStr)
let words;
// 扫描器
while (!ScannerInit.eos()) {
// 收集开始出现前的文字
words = ScannerInit.scanUtil('{{')
if (words !== '') {
// 尝试写一下去掉空格,智能判断是普通文字的空格,还是标签中的空格
// 标签中的空格不能去掉,比如 <div class="box"><></div> 不能去掉class前面的空格
let isInJJH = false
// 空白字符串
var _words = ''
words.split('').forEach(word => {
// 判断是否在标签里
if (word === '<') {
isInJJH = true
} else if (word === '>') {
isInJJH = false
}
if (!/\s/.test(word)) {
_words += word
} else {
// 如果这项是空格,只有当它在标签内的时候,才拼接上
if (isInJJH) {
_words += word
}
}
})
tokens.push(['text', _words])
}
// 跳过大括号
ScannerInit.scan('{{')
words = ScannerInit.scanUtil('}}')
// 收集标记之间的
if (words !== '') {
// 存储
if (words[0] === '#') {
// 从下标为1 跳过#号开始存
tokens.push(['#', words.substring(1)])
} else if (words[0] === '/') {
tokens.push(['/', words.substring(1)])
} else {
tokens.push(['name', words])
}
}
// 跳过大括号
ScannerInit.scan('}}')
}
// 整理tokens
const nextTokens = NextTokens(tokens)
// 折叠token
return RenderTp(nextTokens, data)
}
// nextTokens.js 折叠tokens未改变
/**
* 折叠tokens
* 栈底数组第一位,栈顶-数组尾
*/
export default function (tokens) {
// console.log(tokens)
// 结果数组
let nestedTokens = []
// 栈数字组,存放小token,栈顶(靠近端口的,最新进入的)tokens数组中当前操作的这个tokens小数组
let sections = []
// 收集器,初始指向结果数组,引用类型值,所以指向同一个数组
// 收集器指向会变化,当遇见#的时候,收集器会指向这个token的下表为2的新数组
let collector = nestedTokens
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i]
switch (token[0]) {
case '#':
// console.log(token, '------------')
// 收集器里放入token,由于是引用类型,所以同时可以push到nestedTokens最终结果中
collector.push(token)
// 入栈
sections.push(token)
// console.log('collector', collector,' nestedTokens', nestedTokens, '-----#---0----')
// 收集器更换, 给token添加下标为2的数组,并更换收集器指向,改变了collector的引用指向,切断了collector,与最终结果的关系
// 下面等同于 token[2] = []; collector = token[2]
collector = token[2] = []
// console.log('collector', collector,' nestedTokens', nestedTokens, '-----#----1---')
// console.log(nestedTokens)
break;
case '/':
// console.log(sections, '-------------')
// 出栈,pop()会返回弹出的项目
sections.pop()
// 改变收集器为栈结构队尾(队尾为栈顶),那项的下标为2的数组
collector = sections.length > 0 ? sections[sections.length -1][2] : nestedTokens
break;
default:
// console.log('collector', collector,' nestedTokens', nestedTokens, '-----default-------')
// 如果 没进入栈,则直接结果里存,如果如栈了 就要往栈里 也就是 栈的 【2】数组里存
collector.push(token)
}
}
return nestedTokens
}