smarty/lib/tool.js

340 lines
8.6 KiB
JavaScript
Raw Normal View History

2017-03-17 18:07:35 +08:00
/**
2020-04-06 23:56:03 +08:00
* 模板引擎预处理
2017-03-17 18:07:35 +08:00
* @authors yutent (yutent@doui.cc)
* @date 2016-01-02 21:26:49
*
*/
2018-05-25 15:35:26 +08:00
'use strict'
2017-03-17 18:07:35 +08:00
2020-04-06 23:56:03 +08:00
const fs = require('iofs')
2020-04-07 00:49:07 +08:00
const path = require('path')
2017-03-17 18:07:35 +08:00
class Tool {
2020-04-07 00:49:07 +08:00
constructor(opt) {
2018-05-25 15:35:26 +08:00
this.opt = {
delimiter: ['<!--{', '}-->'], //模板界定符
labels: {
//支持的标签类型
extends: 'extends ([^\\{\\}\\(\\)]*?)', //引入其他文件
inc: 'include ([^\\{\\}\\(\\)]*?)', //引入其他文件
each: 'each ([^\\{\\}\\(\\)]*?)', //each循环开始
done: '/each', //each循环结束
blockL: 'block ([^\\{\\}\\(\\)]*?)', //each循环开始
blockR: '/block', //each循环结束
if: 'if ([^\\{\\}\\/]*?)', //if开始
elif: 'elseif ([^\\{\\}\\/]*?)', //elseif开始
else: 'else', //else开始
fi: '/if', //if结束
var: 'var ([\\s\\S]*?)', //定义变量
echo: '=([^\\{\\}]*?)', //普通变量
comment: '#([\\s\\S]*?)#' //引入其他文件
}
2017-03-17 18:07:35 +08:00
}
2020-04-07 00:49:07 +08:00
Object.assign(this.opt, opt)
this.__REG__ = new RegExp(this.opt.ext + '$')
2018-05-25 15:35:26 +08:00
//过滤器
this.filters = {
html: function(str = '') {
str += ''
return str.tohtml()
},
truncate: function(str, len = '', truncation = '...') {
str += ''
//防止模板里参数加了引号导致异常
len = len.replace(/['"]/g, '') - 0
if (str.length <= len || len < 1) return str
//去除参数里多余的引号
truncation = truncation.replace(/^['"]/, '').replace(/['"]$/, '')
return str.slice(0, len) + truncation
},
lower: function(str) {
str += ''
return str.toLowerCase()
},
upper: function(str) {
str += ''
return str.toUpperCase()
},
date: function(str, format = '') {
//去除参数里多余的引号
format = format.replace(/^['"]/, '').replace(/['"]$/, '')
2018-06-05 01:26:53 +08:00
if (isFinite(str)) {
str = +str
}
return new Date(str).format(format)
2018-05-25 15:35:26 +08:00
}
2017-03-17 18:07:35 +08:00
}
2018-05-25 15:35:26 +08:00
}
2017-03-17 18:07:35 +08:00
2020-04-07 00:49:07 +08:00
__readFile__(file, noParse) {
2020-04-06 23:33:47 +08:00
var buf = null
2018-05-25 15:35:26 +08:00
if (!fs.exists(file)) {
throw new Error(`Can not find template "${file}"`)
2017-03-17 18:07:35 +08:00
}
2020-04-06 23:33:47 +08:00
buf = fs.cat(file).toString()
if (noParse) {
return buf
}
return buf
2018-05-25 15:35:26 +08:00
.replace(/[\r\n\t]+/g, ' ') //去掉所有的换行/制表
.replace(/\\/g, '\\\\')
}
//生成正则
__exp__(str) {
return new RegExp(str, 'g')
}
//生成模板标签
__label__(id) {
2020-04-07 00:49:07 +08:00
var opt = this.opt
var tag = opt.labels[id]
2018-05-25 15:35:26 +08:00
return this.__exp__(opt.delimiter[0] + tag + opt.delimiter[1])
}
//解析普通字段
matchNormal(m) {
let begin = this.__exp__('^' + this.opt.delimiter[0] + '[=\\s]?')
let end = this.__exp__(this.opt.delimiter[1] + '$')
m = m
.replace(begin, '')
.replace(end, '')
.replace(/\|\|/g, '\t')
let matches = m.split('|')
let filter = matches.length == 1 ? '' : matches[1].trim()
let txt = matches[0].replace(/\t/g, '||').trim()
// 默认过滤HTML标签
txt = txt.htmlspecialchars()
if (filter) {
let args = filter.split(':')
filter = args.splice(0, 1, txt) + ''
if (filter === 'date' && args.length > 2) {
let tmp = args.splice(0, 1)
tmp.push(args.join(':'))
args = tmp
tmp = null
}
if (this.filters.hasOwnProperty(filter)) {
args = args.map((it, i) => {
if (i === 0) {
return it
}
return `'${it}'`
})
txt = `__filters__.${filter}(${args.join(', ')})`
}
2017-03-17 18:07:35 +08:00
}
2018-05-25 15:35:26 +08:00
return `\` + (${txt}); tpl += \``
}
2017-03-17 18:07:35 +08:00
2018-05-25 15:35:26 +08:00
//解析each循环
matchFor(m) {
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'each\\s+')
let end = this.__exp__(this.opt.delimiter[1] + '$')
2017-03-17 18:07:35 +08:00
2018-05-25 15:35:26 +08:00
m = m.replace(begin, '').replace(end, '')
2017-03-17 18:07:35 +08:00
2018-05-25 15:35:26 +08:00
m = m.trim()
2018-06-05 01:21:22 +08:00
if (!m || !/\sin\s/.test(m)) {
2018-05-25 15:35:26 +08:00
return new Error('Wrong each loop')
2017-03-17 18:07:35 +08:00
}
2018-05-25 15:35:26 +08:00
let each = 'for (let '
let ms = m.split(' in ')
let mi = ms[0].trim().split(' ')
let mf = ms[1].trim() //要遍历的对象
2017-03-17 18:07:35 +08:00
2018-05-25 15:35:26 +08:00
if (mi.length === 1) {
each += `d_idx in ${mf}) { let ${mi[0]} = ${mf}[d_idx]; tpl += \``
} else {
each += `${mi[0]} in ${mf}) { let ${mi[1]} = ${mf}[${mi[0]}]; tpl += \``
2017-03-17 18:07:35 +08:00
}
2018-05-25 15:35:26 +08:00
return `\`; ${each}`
}
//解析条件语句
matchIf(m) {
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'if\\s+')
let end = this.__exp__(this.opt.delimiter[1] + '$')
m = m.replace(begin, '').replace(end, '')
m = m.trim()
2018-06-05 01:21:22 +08:00
if (!m) {
return `\`; tpl += \``
}
2018-05-25 15:35:26 +08:00
return `\`; if (${m}){ tpl += \``
}
//解析条件语句
matchElseIf(m) {
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'elseif\\s+')
let end = this.__exp__(this.opt.delimiter[1] + '$')
m = m.replace(begin, '').replace(end, '')
m = m.trim()
2018-06-05 01:21:22 +08:00
if (!m) {
return `\`;} else { tpl += \``
}
2018-05-25 15:35:26 +08:00
return `\`; } else if (${m}){ tpl += \``
}
//解析变量定义
matchVar(m) {
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'var\\s+')
let end = this.__exp__(this.opt.delimiter[1] + '$')
m = m.replace(begin, '').replace(end, '')
m = m.trim()
2018-06-05 01:21:22 +08:00
if (m && /=/.test(m)) {
m = 'let ' + m
}
2018-05-25 15:35:26 +08:00
this.vars += ` ${m};`
return `\`; tpl += \``
}
//解析include
matchInclude(m) {
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'include\\s+')
let end = this.__exp__(this.opt.delimiter[1] + '$')
2020-04-07 00:49:07 +08:00
var tpl = ''
2018-05-25 15:35:26 +08:00
m = m
.replace(begin, '')
.replace(end, '')
.replace(/^['"]/, '')
.replace(/['"]$/, '')
2020-04-07 00:49:07 +08:00
.replace(this.__REG__, '') //去掉可能出现的自带的模板后缀
2018-05-25 15:35:26 +08:00
2020-04-07 00:49:07 +08:00
m += this.opt.ext //统一加上后缀
2018-05-25 15:35:26 +08:00
2020-04-07 00:49:07 +08:00
tpl = this.__readFile__(path.resolve(this.opt.path, m))
2018-05-25 15:35:26 +08:00
//递归解析include
tpl = tpl.replace(this.__label__('inc'), m1 => {
return this.matchInclude(m1)
})
return tpl
}
// 解析常规标签
parseNormal(str) {
return (
str
// 解析include
.replace(this.__label__('inc'), m => {
return this.matchInclude(m)
})
// 移除注释
.replace(this.__label__('comment'), m => {
return ''
})
// 解析each循环
.replace(this.__label__('each'), m => {
return this.matchFor(m)
})
// 解析循环结束标识
.replace(this.__label__('done'), '` } tpl += `')
// 解析 if/elseif 条件
.replace(this.__label__('if'), m => {
return this.matchIf(m)
})
.replace(this.__label__('elif'), m => {
return this.matchElseIf(m)
})
// 解析else
.replace(this.__label__('else'), '`; } else { tpl += `')
// 解析if条件结束标识
.replace(this.__label__('fi'), '`; } tpl += `')
// 解析临时变量的定义
.replace(this.__label__('var'), m => {
return this.matchVar(m)
})
// 解析普通变量/字段
.replace(this.__label__('echo'), m => {
return this.matchNormal(m)
})
)
}
// 解析extends标签
parseExtends(str) {
2020-04-07 00:49:07 +08:00
let matches = str.match(/^<!--{extends ([^\\{\\}\\(\\)]*?)\s*?}-->/)
2018-05-25 15:35:26 +08:00
if (!matches) {
str = str
.replace(this.__label__('blockL'), '')
.replace(this.__label__('blockR'), '')
} else {
let blocks = {}
// 去除所有的extends标签, 只允许有出现1次
str = str.replace(this.__label__('extends'), '').trim()
str.replace(
/<!--{block ([^\\{\\}\\(\\)]*?)}-->([\s\S]*?)<!--{\/block}-->/g,
(m, flag, val) => {
flag = flag.trim()
blocks[flag] = val.trim()
}
)
str = matches[1]
.replace(/^['"]/, '')
.replace(/['"]$/, '')
2020-04-07 00:49:07 +08:00
.replace(this.__REG__, '') //去掉可能出现的自带的模板后缀
2018-05-25 15:35:26 +08:00
2020-04-07 00:49:07 +08:00
str += this.opt.ext //统一加上后缀
2018-05-25 15:35:26 +08:00
2020-04-07 00:49:07 +08:00
str = this.__readFile__(path.resolve(this.opt.path, str)).replace(
this.__label__('blockL'),
(m, flag) => {
flag = flag.trim()
return blocks[flag] || ''
}
)
2017-03-17 18:07:35 +08:00
}
2018-05-25 15:35:26 +08:00
return str
}
//解析模板
parse(str, data) {
2020-04-07 00:49:07 +08:00
var vars = `"use strict"; let __filters__ = f; `
2018-05-25 15:35:26 +08:00
for (let i in data) {
let tmp = JSON.stringify(data[i]) || ''
2020-04-07 00:49:07 +08:00
vars += `let ${i} = ${tmp}; `
2017-03-17 18:07:35 +08:00
}
2018-05-25 15:35:26 +08:00
str = str
.trim()
.replace(/[\r\n\t]+/g, ' ') // 去掉所有的换行/制表
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
2017-03-17 18:07:35 +08:00
2018-05-25 15:35:26 +08:00
str = this.parseExtends(str)
str = this.parseNormal(str)
2017-03-17 18:07:35 +08:00
2020-04-07 00:49:07 +08:00
str = `${vars} let tpl=\`${str}\`; return tpl;`
2017-03-17 18:07:35 +08:00
2018-05-25 15:35:26 +08:00
return new Function('f', str)(this.filters)
}
2017-03-17 18:07:35 +08:00
}
2018-05-25 15:35:26 +08:00
module.exports = Tool