1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptのテンプレートエンジンを供養します

Posted at

外部ライブラリが使えない状況でJSのテンプレートエンジンが有ったら良いなと思って書いたら案件そのものが先送りになったので供養します。勢いで書いたので充分なテストが出来ていませんが、多分、LOOPやIFの入れ子もイケると思います。

<script id="loop" type="text/html">
<div>
    <#LOOP name="list">
    <p>
       <#VAL name="val"  format="number">&nbsp;
       <#VAL name="date" format="date:MM/DD YYYY">
       <#IF expr="__AA_LOOP_IDX > 2">&nbsp;<#VAL name="__AA_LOOP_IDX"><#/IF>
   </p>
    <#/LOOP>
</div>
</script>

<script id="if" type="text/html">
<div><#IF name="case">exactly<#ELSE>boo!<#/IF></div>
</script>

<script id="else" type="text/html">
<div><#IF name="case">boo!<#ELSE>exactly<#/IF></div>
</script>

<h2>loop</h2>
<div id="loopResult"></div>


<h2>if</h2>
<div id="ifResult"></div>

<h2>else</h2>
<div id="elseResult"></div>

<script type="text/javascript" src="assets/js/Template.js"></script>
<script type="text/javascript">
(function($){
    $('loopResult').innerHTML = AA.Template.compile($('loop').innerHTML).run({list: [
        {'val':1001, 'date':'2015/01/01'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1004, 'date':'2015/01/04'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1002, 'date':'2015/01/02'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'},
        {'val':1003, 'date':'2015/01/03'}
    ]});
    $('ifResult').innerHTML   = AA.Template.compile($('if').innerHTML).run({"case": true});
    $('elseResult').innerHTML = AA.Template.compile($('else').innerHTML).run({"case": false});
})(function(id){return document.getElementById(id);});
</script>

Template.jsの中身

var AA = window.AA || {};
AA.Template = {};
AA.Template.compile = function (text) {
    return new Tag(parse(text).stack);
    function parse (text, start, endTag) {
        start = start || 0;
        var stack = [];
        for (var i = start, l = text.length, buf = '', c = ''; i < l; i++) {
            c = text.substr(i, 1);
            if (text.substr(i, 3)=== '<#/') {
                if (buf !== '') stack.push(new TextNode(buf));
                for (i += 3, buf = '', c = ''; i < l; i++) {
                    c = text.substr(i, 1);
                    if (c === '>') break;
                    buf += c;
                }
                if (!endTag)        throw new SyntaxError('invalidTag: no expected,actual:' + buf);
                if (buf !== endTag) throw new SyntaxError('invalidTag: expected:' + endTag + ', actual:' + buf);
                return {"stack": stack, pos: i};
            } else if (text.substr(i, 2) === '<#') {
                if (buf !== '')  stack.push(new TextNode(buf));
                for (i += 2, buf = '<#'; i < l; i++) {
                    c = text.substr(i, 1);
                    if (c === '"' || c === "'") {
                        for (buf += quote = c, i += 1; i < l; i++) {
                            c = text.substr(i, 1);
                            if (c === '\\')  {
                                if (substr(i + 1, 1) === "n") {
                                    buf += "\n";
                                } else if (substr(i + 1, 1) === "\\") {
                                    buf += "\\";
                                } else {
                                    buf += substr(i + 1, 1);
                                }
                                i++;
                            } else if (quote === c) {
                                buf += c;
                                break;
                            } else {
                                buf += c;
                            }
                        }
                    } else if (c === ">") {
                        buf += c;
                        var tag = buildTag(buf);
                        buf = '';
                        if (tag.getEndTag()) {
                            var result = parse(text, i + 1, tag.getEndTag());
                            tag.stack = result.stack;
                            i = result.pos;
                        }
                        stack.push(tag);
                        break;
                    } else {
                        buf += c;
                    }
                }
            } else {
                buf += c;
            }
        }
        if (buf) {
            stack.push(new TextNode(buf));
        }
        return {"stack": stack, pos: text.length};
    }

    function buildTag (text) {
        var i = 0, name = '', attributes = {};
        for (i = 0, buf = '', c = '', l = text.length; i < l; i++) {
            c = text.substr(i, 1);
            if (c === ' ' || c === "\n" || c === '>') break;
            buf += c;
        }
        name = buf;
        for (buf = '', c = '', l = text.length; i < l; i++) {
            while (i < l && text.substr(i++, 1) === ' ');
            var label = '';
            for (i--; i < l; i++)  {
                c = text.substr(i, 1);
                if (c === ' ' || c === '=' || c === '>') break;
                buf += c;
            }
            label = buf;
            var value = '', buf = '', quote = '';
            while (i < l && text.substr(i++, 1) === ' ');
            for (; i < l; i++)  {
                c = text.substr(i, 1);
                if (buf === '' && (c === '"' || c === "'")) {
                    quote = c;
                    for (i += 1; i < l; i++) {
                        c = text.substr(i, 1);
                        if (c === "\\") {
                            switch (text.substr(i + 1, 1)) {
                                case "n":  buf += "\n"; i+=2; break;
                                case "\\": buf += "\\"; i+=2; break;
                                default:   buf += text.substr(i + 1, 1); i+=2; break;
                            }
                        } else if (c === quote) {
                            break;
                        } else {
                            buf += c;
                        }
                    }
                } else if (c === ' ' || c === '>') {
                    break;
                } else {
                    buf += c;
                }
            }
            attributes[label] = buf;
            buf = '';
        }

        if (name === '<#IF') {
            return new IFTag (text, attributes);
        } else if (name === '<#LOOP') {
            return new LoopTag(text, attributes);
        } else if (name === '<#ELSE') {
            return new ElseTag(text, attributes);
        } else if (name === '<#VAL') {
            return new ValueTag(text, attributes);
        } else {
            return new TextNode(text);
        }
    }

    function Tag (stack) {
        this.stack = stack;
        this.getEndTag = function () {
            return null;
        }

        this.run = function (parameter) {
            if (Object.prototype.toString.call(parameter) === '[object Array]') {
                throw new TypeError('parameter is array');
            }
            var result = '';
            for (var i = 0, l = this.stack.length; i < l; i++)  {
                result += this.stack[i].run(parameter);
            }
            return result;
        }
        return this;
    }

    function IFTag (text, attributes) {
        this.originalText = text;
        this.attributes = attributes;
        this.stack = []
        this.getEndTag = function () {
            return 'IF';
        }

        this.run = function (parameter) {
            var current = this.evaluate(parameter, this.attributes);
            var isSkip = (function(bool) {
                return bool ?
                    function (a) {return (a instanceof ElseTag);}:
                    function (b) {return !(b instanceof ElseTag);};
            })(current);
            var result = '';
            for (var i = 0, l = this.stack.length; i < l; i++)  {
                if (isSkip(this.stack[i])) continue;
                result += this.stack[i].run(parameter);
            }
            return result;
        }

        this.evaluate = function (parameter, attributes) {
            var result = false;
            if (attributes['expr']) {
                if (!attributes['expr'].match(/^ *([a-zA-Z_-]+) *(===|==|>=|<=|<|>) *(.+?) *$/)) {
                    throw new SyntaxError(
                        'expected pattern :"^ *([a-zA-Z_-]+) *(===|==|>=|<=|<|>) *(.+?) *$", actual expr:' +
                        attributes['expr']
                    );
                }
                var comperator = {
                    "left": RegExp.$1,
                    "operator": RegExp.$2,
                    "right": RegExp.$3
                };
                comperator.left  = parameter[comperator.left]  ? parameter[comperator.left]  : this.resolveQuote(comperator.left);
                comperator.right = parameter[comperator.right] ? parameter[comperator.right] : this.resolveQuote(comperator.right);
                result = this.compare(comperator);
            } else if (attributes['name']) {
                result = parameter[attributes['name']];
            }
            return result;
        }

        this.compare = function (obj) {
            if (obj.operator === '===' && obj.left === obj.right) return true;
            if (obj.operator === '=='  && obj.left == obj.right)  return true;
            if (obj.operator === '<='  && (obj.left - 0) <= (obj.right - 0)) return true;
            if (obj.operator === '>='  && (obj.left - 0) >= (obj.right - 0)) return true;
            if (obj.operator === '<'   && (obj.left - 0) <  (obj.right - 0)) return true;
            if (obj.operator === '>'   && (obj.left - 0) >  (obj.right - 0)) return true;
            return false;
        }

        // TODO: too hack
        this.resolveQuote = function (str) {
            return str.replace(/^["']|["']$/g).replace(/\\n/g,"\n").replace(/\\/g,'');
        }
        return this;
    }

    function ElseTag (text, attributes) {
        this.originalText = text;
        this.attributes = attributes;
        this.stack = []

        this.getEndTag = function () {
            return 'IF';
        }

        this.run = function (parameter) {
            var result = '';
            for (var i = 0, l = this.stack.length; i < l; i++)  {
                result += this.stack[i].run(parameter);
            }
            return result;
        }
        return this;
    }

    function LoopTag (text, attributes) {
        this.originalText = text;
        this.attributes = attributes;
        this.stack = []
        this.getEndTag = function () {
            return 'LOOP';
        }

        this.run = function (parameter) {
            var result = '';
            var list = parameter[this.attributes['name']];
            if (!list || Object.prototype.toString.call(list) !== '[object Array]')
                throw new TypeError('invalid arguments.. wants array');

            for (var i = 0, l = list.length; i < l; i++) {
                var current = list[i];
                if (Object.prototype.toString.call(current) === '[object Array]') {
                    throw new TypeError('parameter is array... at loop(' + i + ')' );
                }
                current['__AA_LOOP_IDX'] = i;
                for (var j = 0, l2 = this.stack.length; j < l2; j++) {
                    result += this.stack[j].run(current);
                }
            }
            return result;
        }
        return this;
    }

    function ValueTag (text, attributes) {
        this.originalText = text;
        this.attributes = attributes;
        this.stack = [];
        this.getEndTag = function () {
            return null;
        }

        this.run = function (parameter) {
            var result = parameter[this.attributes['name']];
            if (result === undefined)
                throw new TypeError('invalid argument:' + this.attributes['name']);
            if (this.attributes['format']) {
                result = this.format (result, this.attributes['format']);
            }
            if (this.attributes['escape'] !== "false") {
                result = this.escape (result);
            }
            return result;
        }

        this.escape =  function (val) {
            var div = document.createElement('div');
            div.appendChild(document.createTextNode(val));
            return div.innerHTML;
        }

        this.format = function (val, format) {
            if (format === 'number') {
                return String(val).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
            } else if (format.match(/^date:?(.*)$/)) {
                var obj = new Date(val);
                if (RegExp.$1)  {
                    return String(RegExp.$1).
                        replace(/YYYY/g, String(obj.getYear() + 1900)).
                        replace(/YY/g, String(obj.getYear() + 1900).replace(/^\d{2}/,'')).
                        replace(/MM/g, String(obj.getMonth() + 1)).
                        replace(/DD/g, String(obj.getDate())).
                        replace(/HH/g, String(obj.getHours())).
                        replace(/II/g, String(obj.getMinutes())).
                        replace(/SS/g, String(obj.getSeconds()));
                } else {
                    return obj.toLocaDateString();
                }
            } else {
                throw new SyntaxError ('invalid format: expects: (number|date:(dateFormat)), actual: ' + format);
            }
        }
        return this;
    }

    function TextNode (text) {
        this.originalText = text;
        this.getEndTag = function () {
            return null;
        }
        this.run = function (parameter) {
            return this.originalText;
        }
        return this;
    }
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?