// Copyright (C) 2009 Andy Chu
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// $Id: json-template.js 271 2009-05-31 19:35:43Z andy@chubot.org $

//
// JavaScript implementation of json-template.
//


var log=log||function(){};var repr=repr||function(){};var jsontemplate=function(){var META_ESCAPE={'{':'\\{','}':'\\}','{{':'\\{\\{','}}':'\\}\\}','[':'\\[',']':'\\]'};function _MakeTokenRegex(meta_left,meta_right){return new RegExp('('+
META_ESCAPE[meta_left]+'.+?'+
META_ESCAPE[meta_right]+'\n?)','g');}
function HtmlEscape(s){return s.replace(/&/g,'&amp;').replace(/>/g,'&gt;').replace(/</g,'&lt;');}
function HtmlTagEscape(s){return s.replace(/&/g,'&amp;').replace(/>/g,'&gt;').replace(/</g,'&lt;').replace(/"/g,'&quot;');}
function ToString(s){return s.toString();}
var DEFAULT_FORMATTERS={'html':HtmlEscape,'htmltag':HtmlTagEscape,'html-attr-value':HtmlTagEscape,'str':ToString,'raw':function(x){return x;}};function _ScopedContext(context,undefined_str){var stack=[{context:context,index:-1}];return{PushSection:function(name){log('PushSection '+name);if(name===undefined||name===null){return null;}
var new_context=stack[stack.length-1].context[name]||null;stack.push({context:new_context,index:-1});return new_context;},Pop:function(){stack.pop();},next:function(){var stacktop=stack[stack.length-1];if(stacktop.index==-1){stacktop={context:null,index:0};stack.push(stacktop);}
var context_array=stack[stack.length-2].context;if(stacktop.index==context_array.length){stack.pop();log('next: null');return null;}
log('next: '+stacktop.index);stacktop.context=context_array[stacktop.index++];log('next: true');return true;},CursorValue:function(){return stack[stack.length-1].context;},_Undefined:function(name){if(undefined_str===undefined){throw{name:'UndefinedVariable',message:name+' is not defined'};}else{return undefined_str;}},_LookUpStack:function(name){var i=stack.length-1;while(true){var context=stack[i].context;log('context '+repr(context));if(typeof context!=='object'){i--;}else{var value=context[name];if(value===undefined||value===null){i--;}else{return value;}}
if(i<=-1){return this._Undefined(name);}}},Lookup:function(name){var parts=name.split('.');var value=this._LookUpStack(parts[0]);if(parts.length>1){for(var i=1;i<parts.length;i++){value=value[parts[i]];if(value===undefined){return this._Undefined(parts[i]);}}}
return value;}};}
function _Section(section_name){var current_clause=[];var statements={'default':current_clause};return{section_name:section_name,Statements:function(clause){clause=clause||'default';return statements[clause]||[];},NewClause:function(clause_name){var new_clause=[];statements[clause_name]=new_clause;current_clause=new_clause;},Append:function(statement){current_clause.push(statement);}};}
function _Execute(statements,context,callback){var i;for(i=0;i<statements.length;i++){statement=statements[i];if(typeof(statement)=='string'){callback(statement);}else{var func=statement[0];var args=statement[1];func(args,context,callback);}}}
function _DoSubstitute(statement,context,callback){log('Substituting: '+statement.name);var value;if(statement.name=='@'){value=context.CursorValue();}else{value=context.Lookup(statement.name);}
for(i=0;i<statement.formatters.length;i++){value=statement.formatters[i](value);}
callback(value);}
function _DoSection(args,context,callback){var block=args;var value=context.PushSection(block.section_name);var do_section=false;if(value){do_section=true;}
if(value&&value.length===0){do_section=false;}
if(do_section){_Execute(block.Statements(),context,callback);context.Pop();}else{context.Pop();_Execute(block.Statements('or'),context,callback);}}
function _DoRepeatedSection(args,context,callback){var block=args;var pushed;if(block.section_name=='@'){items=context.CursorValue();pushed=false;}else{items=context.PushSection(block.section_name);pushed=true;}
if(items&&items.length>0){var last_index=items.length-1;var statements=block.Statements();var alt_statements=block.Statements('alternate');for(var i=0;context.next()!==null;i++){log('_DoRepeatedSection i: '+i);_Execute(statements,context,callback);if(i!=last_index){log('ALTERNATE');_Execute(alt_statements,context,callback);}}}else{log('OR: '+block.Statements('or'));_Execute(block.Statements('or'),context,callback);}
if(pushed){context.Pop();}}
var _SECTION_RE=/(repeated)?\s*(section)\s+(\S+)?/;function _Compile(template_str,options){var more_formatters=options.more_formatters||function(x){return null;};var default_formatter;if(options.default_formatter===undefined){default_formatter='str';}else{default_formatter=options.default_formatter;}
function GetFormatter(format_str){var formatter=more_formatters(format_str)||DEFAULT_FORMATTERS[format_str];if(formatter===undefined){throw{name:'BadFormatter',message:format_str+' is not a valid formatter'};}
return formatter;}
var format_char=options.format_char||'|';if(format_char!=':'&&format_char!='|'){throw{name:'ConfigurationError',message:'Only format characters : and | are accepted'};}
var meta=options.meta||'{}';var n=meta.length;if(n%2==1){throw{name:'ConfigurationError',message:meta+' has an odd number of metacharacters'};}
var meta_left=meta.substring(0,n/2);var meta_right=meta.substring(n/2,n);var token_re=_MakeTokenRegex(meta_left,meta_right);var current_block=_Section();var stack=[current_block];var strip_num=meta_left.length;var token_match;var last_index=0;while(true){token_match=token_re.exec(template_str);log('match:',token_match);if(token_match===null){break;}else{var token=token_match[0];}
log('last_index: '+last_index);log('token_match.index: '+token_match.index);if(token_match.index>last_index){var tok=template_str.slice(last_index,token_match.index);current_block.Append(tok);log('tok: "'+tok+'"');}
last_index=token_re.lastIndex;log('token0: "'+token+'"');var had_newline=false;if(token.slice(-1)=='\n'){token=token.slice(null,-1);had_newline=true;}
token=token.slice(strip_num,-strip_num);if(token.charAt(0)=='#'){continue;}
if(token.charAt(0)=='.'){token=token.substring(1,token.length);var literal={'meta-left':meta_left,'meta-right':meta_right,'space':' ','tab':'\t','newline':'\n'}[token];if(literal!==undefined){current_block.Append(literal);continue;}
var section_match=token.match(_SECTION_RE);if(section_match){var repeated=section_match[1];var section_name=section_match[3];var func=repeated?_DoRepeatedSection:_DoSection;log('repeated '+repeated+' section_name '+section_name);var new_block=_Section(section_name);current_block.Append([func,new_block]);stack.push(new_block);current_block=new_block;continue;}
if(token=='alternates with'){current_block.NewClause('alternate');continue;}
if(token=='or'){current_block.NewClause('or');continue;}
if(token=='end'){stack.pop();if(stack.length>0){current_block=stack[stack.length-1];}else{throw{name:'TemplateSyntaxError',message:'Got too many {end} statements'};}
continue;}}
var parts=token.split(format_char);var formatters;var name;if(parts.length==1){if(default_formatter===null){throw{name:'MissingFormatter',message:'This template requires explicit formatters.'};}
formatters=[GetFormatter(default_formatter)];name=token;}else{formatters=[];for(var j=1;j<parts.length;j++){formatters.push(GetFormatter(parts[j]));}
name=parts[0];}
current_block.Append([_DoSubstitute,{name:name,formatters:formatters}]);if(had_newline){current_block.Append('\n');}}
current_block.Append(template_str.slice(last_index));if(stack.length!==1){throw{name:'TemplateSyntaxError',message:'Got too few {end} statements'};}
return current_block;}
function Template(template_str,options){if(!(this instanceof Template)){return new Template(template_str,options);}
this._options=options||{};this._program=_Compile(template_str,this._options);}
Template.prototype.render=function(data_dict,callback){var context=_ScopedContext(data_dict,this._options.undefined_str);_Execute(this._program.Statements(),context,callback);};Template.prototype.expand=function(data_dict){var tokens=[];this.render(data_dict,function(x){tokens.push(x);});return tokens.join('');};return{Template:Template,HtmlEscape:HtmlEscape};}();