JSON Template Engine //Repositório JavaScript

Descrição

Engine de template simples, extensível e eficaz. É capaz de interpretar tags de template aninhadas e montar sua respectiva estrutura usando JSON. O modo como foi implementado permite uma melhor organização e torna o preenchimento dos templates mais fácil.
Criado: 2006.02.04

Código (Download)

//+ Carlos R. L. Rodrigues
//@ http://jsfromhell.com/classes/template [rev. #2]

Template = function(s, oo, oc, co, cc){
    var o = this; o.oo = oo || "<js:", o.oc = oc || ">", o.co = co || "</js:", o.cc = cc || ">";
    if(o.oo == o.co || o.oo == o.oc || o.oo == o.cc || o.co == o.oc || o.co == o.cc) throw Error("Tag definition not allowed");
    o.tags = [], o.root = o.node("_root", s, null), o.parse(s);
}
Template.prototype.node = function(n, v, p){
    var _ = this;
    return {
        _default: v, _value: [v], _parent: p, _children: [], _name: n,
        _render: function(_o){
            var o = this._children, s = (v = this._value)[v.length - 1], p = [], i = -1,
            t = _.oo, c = _.co, tc = _.oc, cc = _.cc, y, v, a, f, j, to;
            while((y = o[++i]) && (v = s.slice(0, j = s.indexOf(to = t + (a = y._name) + tc)), f = s.indexOf(a = c + a + cc, j))){
                while((j = s.indexOf(to, j + 1)) + 1 && j < f) f = s.indexOf(a, f + 1);
                if(f + 1 && (s = v + y._value.join("") + s.slice(a.length + f)), y._children[0]) p.push(i, o), o = y._children, i = -1;
                else if(!o[i + 1]) while(p.length && !(o = p.pop())[(i = p.pop()) + 1]);
            }
            return _o ? s : (i = (v = this._value).length, v[i] = v[--i], v[i] = s);
        },
        _set: function(s){return this._value[this._value.length - 1] = s;},
        _get: function(){
            var r = /([(){}|*+?.,^$\[\]\\])/g, f = function(s){return s.replace(r, "\\\$1")};
            return this._value.join("").replace(new RegExp((f(_.oo) + ".*?" + f(_.oc) + "|" + f(_.co) + ".*?" + f(_.cc)), "gm"), "");
        },
        _reset: function(c){
            if(c)
                for(var a, o = this, t = _.tags, i = 0, l = t.length; i < l; i++)
                    ((a = t[i]._parent) == o || a == this) && ((o = t[i])._value = [o._default]);
            return this._value = [this._default];
        },
        _output: function(){return this._render(1);}
    };
}
Template.prototype.parse = function(s){
    var _ = this, p = 0, r = _.root, l = [[-1, s.length, r]], y = _.tags, i = _.oo,
    e = _.co, $ = _.oc, d = _.cc, h = $.length, g, a, f, j, c, t, v;
    while((p = s.indexOf(i, p)) > -1){
        if(p == s.indexOf(e, p) && ++p) continue;
        a = (a = p + i.length) + (t = s.slice(a, j = s.indexOf($, p))).length + h, f = s.indexOf(g = e + t + d, p);
        while((j = s.indexOf(i + t + d, j + 1)) + 1 && j < f) f = s.indexOf(g, f + 1);
        if(t.charAt() == "_") throw Error("Tag name not allowed [" + t + "]");
        if(f < 0) throw Error("End of tag \"" + i + t + $ + "\" expected");
        for(v = s.slice(a, f), j = l.length; j--;)
            if((c = l[j])[2][t] && p > c[0] && f < c[1]) throw Error("Ambiguous tag name \"" + t + "\"");
            else if(p > c[0] && f < c[1] && l.push([p++, f, (v = c[2][t] = _.node(t, v, c[2]))]) &&
                (!(a = c[2]._parent) ? c[2] : a[c[2]._name])._children.push(v) && y.push(v)) break;
    }
}

Exemplo (Exemplo)

<script type="text/javascript">
//<![CDATA[

s =
"<html>\n" +
    "<head>[header]\n" +
        '[js]<script src="[src][/src]" type="text/javascript"><\/script>\n[/js]' +
    "[/header]</head>\n" +
    "<body>\n" +
    "[content]Default content[/content]\n" +
    "</body>\n" +
"</html>";

var t = new Template(s, "[", "]", "[/", "]");

var js = t.root.header.js, content = t.root.content;

js.src._set("file.js");
js._render();

js.src._set("file2.js");

content._set("I Changed the content");
alert(t.root._output());

content._reset();
alert(t.root._output());

js._reset(true);
alert(t.root._output());

js._set("");
content._set("");
alert(t.root._output());

//]]>
</script>

Ajuda

Funcionamento

Esta classe é semelhante a um interpretador de xml, ela procura por tags de template no texto e as deixa facilmente acessíveis através da classe, adicionando propriedades e métodos especiais em cada nó.

Por default são reconhecidas como tag de template as tags que seguem esse padrão: <js:NomeDaTag>conteúdo qualquer</js:NomeDaTag>. Mas isso é configurável, exemplo: [NomeDaTag]conteúdo[/NomeDaTag].

Exemplo de template:
<js:a>
	<js:b>conteúdo</js:b>
</js:a>

Após carregar o template acima, você poderia acessar o texto "conteúdo" usando instanciaDeTemplate.root.a.b._get()

Há duas restrições para a nomeação das tags:

  • O nome da tag não pode começar com "_".
    Errado: [_minhaTag][/_minhaTag]
  • Não pode haver tags com o mesmo nome no mesmo nível de indentação.
    Errado: [a][/a][a][/a].
    Correto: [a][/a][b][/b].

    Errado: [a][b][/b][b][/b][/a].
    Correto: [a][b][/b][c][/c][/a].

    Correto: [a][b][/b][a][b][/b] (o segundo [b] está em outro nível de indentação).

Para mais detalhes sobre o funcionamento, olhe o exemplo.

Construtor

Template(content: String, [beginOpen: String = "<js:"], [beginClose: String = ">"], [endOpen: String = "</js:"], [endClose: String = ">"])
Gera uma instância de Template.
Restrições:
  • beginOpen não pode ser igual ao endOpen
  • beginOpen não pode ser igual ao beginClose
  • beginOpen não pode ser igual ao endClose
  • endOpen não pode ser igual ao beginClose
  • endOpen não pode ser igual ao endClose
content
texto a ser interpretado pela classe
beginOpen
define o prefixo da tag de abertura
beginClose
define o sufixo da tag de abertura
endOpen
define o prefixo da tag de fechamento
endClose
define o sufixo da tag de fechamento

Propriedades

Template.root: Node
É o nó inicial, deve ser usado para obter acesso aos demais nós via "JSON".
Template.tags: Array
Array contendo referência para todos os nós.

Métodos do Node

Node._get(void): String
Retorna o valor do nó. Se houverem tags de template no meio desse texto, estas serão removidas, mantendo apenas o texto padrão contido dentro delas.
Node._set(value: String): String
Atribui um novo valor para o nó e, retorna o próprio texto atribuido. Se o nó tiver filhos, estes serão ignorados (não realmente removidos) no output.
value
texto a ser atribuido
Node._reset([deep: Boolean = false]): void
Desfaz todas as alterações feitas no nó pelos métodos "_render" ou "_set".
deep
se true, os nós filhos também serão resetados
Node._render(void): String
A função retorna o valor atual do nó e implementa uma espécie de "fila de conteúdo". O valor atual do nó é empilhado (você tem acesso a ele através da propriedade "_value"), e o nó passa a operar sobre uma cópia dele.
Obs: O desenfileiramento ocorrerá automaticamente no output.
Node._output(void): String
Retorna o texto do nó e de seus nós filhos. É semelhante ao "_get", porém diferentemente do "_get", que só retorna o texto do nó, ele também pega o texto dos nós filhos.

Propriedades do Node

Node._default: String
Mantém o valor inicial do nó.
Node._parent: Node
Referência para o nó pai.
Node._name: String
Nome da tag.
Node._children: Array
Array contendo os nós filhos.
Node._value: Array
Mantém o texto do nó em forma de uma estrutura de fila.

Ranque (Votos: 43)

3.07