var htmlUtil = require('html-util') , parseHtml = htmlUtil.parse , trimLeading = htmlUtil.trimLeading , unescapeEntities = htmlUtil.unescapeEntities , escapeHtml = htmlUtil.escapeHtml , escapeAttribute = htmlUtil.escapeAttribute , isVoid = htmlUtil.isVoid , conditionalComment = htmlUtil.conditionalComment , markup = require('./markup') , viewPath = require('./viewPath') , wrapRemainder = viewPath.wrapRemainder , ctxPath = viewPath.ctxPath , extractPlaceholder = viewPath.extractPlaceholder , dataValue = viewPath.dataValue , pathFnArgs = viewPath.pathFnArgs , arraySlice = Array.prototype.slice; module.exports = View; function empty() { return ''; } function notFound(name, ns) { if (ns) name = ns + ':' + name; throw new Error("Can't find view: " + name); } var defaultCtx = { $depth: 0 , $aliases: {} , $paths: [] , $indices: [] }; var defaultGetFns = { equal: function(a, b) { return a === b; } , not: function(value) { return !value; } }; var defaultSetFns = { equal: function(value, a) { return value ? [a] : []; } , not: function(value) { return [!value]; } }; function View(libraries, appExports) { this._libraries = libraries || {}; this._appExports = appExports; this._nonvoidComponents = {}; this.clear(); this.getFns = Object.create(defaultGetFns); this.setFns = Object.create(defaultSetFns); } View.prototype = { clear: function() { this._views = Object.create(this.defaultViews); this._made = {}; this._renders = {}; this._inline = ''; this._idCount = 0; } // All automatically created ids start with a dollar sign , _uniqueId: function() { return '$' + (this._idCount++).toString(36); } , defaultViews: { doctype: function() { return ''; } , root: empty , charset: function() { return ''; } , title$s: empty , head: empty , header: empty , body: empty , footer: empty , scripts: empty , tail: empty } , _selfNs: 'app' , make: function(name, template, options, templatePath, boundMacro) { var view = this , onBind, renderer, render, matchTitle, ns, isString; // Cache any templates that are made so that they can be // re-parsed with different items bound when using macros this._made[name] = [template, options, templatePath]; if (options && 'nonvoid' in options) { this._nonvoidComponents[name] = true; } if (templatePath && (render = this._renders[templatePath])) { this._views[name] = render; return } name = name.toLowerCase(); matchTitle = /(?:^|\:)title(\$s)?$/.exec(name); if (matchTitle) { isString = !!matchTitle[1]; if (isString) { onBind = function(events, name) { var macro = false; return bindEvents(events, macro, name, render, ['$_doc', 'prop', 'title']); }; } else { this.make(name + '$s', template, options, templatePath); } } renderer = function(ctx, model, triggerPath, triggerId) { renderer = parse(view, name, template, isString, onBind, boundMacro); return renderer(ctx, model, triggerPath, triggerId); } render = function(ctx, model, triggerPath, triggerId) { return renderer(ctx, model, triggerPath, triggerId); } this._views[name] = render; if (templatePath) this._renders[templatePath] = render; } , _makeAll: function(templates, instances) { var name, instance, options, templatePath; this.clear(); for (name in instances) { instance = instances[name]; templatePath = instance[0]; options = instance[1]; this.make(name, templates[templatePath], options, templatePath); } } , _makeComponents: function(components) { var libraries = this._libraries , name, component, view; for (name in components) { component = components[name]; view = libraries[name].view; view._makeAll(component.templates, component.instances); } } , _findItem: function(name, ns, prop) { var items = this[prop] , item, last, i, segments, testNs; if (ns) { ns = ns.toLowerCase(); item = items[ns + ':' + name]; if (item) return item; segments = ns.split(':'); last = segments.length - 1; if (last > 0) { for (i = last; i--;) { testNs = segments.slice(0, i).join(':'); item = items[testNs + ':' + name]; if (item) return item; } } } return items[name]; } , _find: function(name, ns, boundMacro) { var hash, hashedName, out, item, template, options, templatePath; if (boundMacro && (hash = keyHash(boundMacro))) { hash = '$b:' + hash; hashedName = name + hash; out = this._findItem(hashedName, ns, '_views'); if (out) return out; item = this._findItem(name, ns, '_made') || notFound(name, ns); template = item[0]; options = item[1]; templatePath = item[2] + hash; this.make(hashedName, template, options, templatePath, boundMacro); return this._find(hashedName, ns); } return this._findItem(name, ns, '_views') || notFound(name, ns); } , get: function(name, ns, ctx) { if (typeof ns === 'object') { ctx = ns; ns = ''; } ctx = ctx ? extend(ctx, defaultCtx) : Object.create(defaultCtx); return this._find(name, ns)(ctx); } , inline: empty , fn: function(name, fn) { var get, set; if (typeof fn === 'object') { get = fn.get; set = fn.set; } else { get = fn; } this.getFns[name] = get; if (set) this.setFns[name] = set; } , render: function(model, ns, ctx, silent) { if (typeof ns === 'object') { silent = ctx; ctx = ns; ns = ''; } this.model = model; this._idCount = 0; this.model.__pathMap.clear(); this.model.__events.clear(); this.model.__blockPaths = {}; this.model.del('_$component'); this.dom.clear(); var title = this.get('title$s', ns, ctx) , rootHtml = this.get('root', ns, ctx) , bodyHtml = this.get('header', ns, ctx) + this.get('body', ns, ctx) + this.get('footer', ns, ctx); if (silent) return; var doc = document , documentElement = doc.documentElement , attrs = documentElement.attributes , i, attr, fakeRoot, body; // Remove all current attributes on the documentElement and replace // them with the attributes in the rendered rootHtml for (i = attrs.length; i--;) { attr = attrs[i]; documentElement.removeAttribute(attr.name); } // Using the DOM to get the attributes on an tag would require // some sort of iframe hack until DOMParser has better browser support. // String parsing the html should be simpler and more efficient parseHtml(rootHtml, { start: function(tag, tagName, attrs) { if (tagName !== 'html') return; for (var attr in attrs) { documentElement.setAttribute(attr, attrs[attr]); } } }); fakeRoot = doc.createElement('html'); fakeRoot.innerHTML = bodyHtml; body = fakeRoot.getElementsByTagName('body')[0]; documentElement.replaceChild(body, doc.body); doc.title = title; } , escapeHtml: escapeHtml , escapeAttribute: escapeAttribute } function keyHash(obj) { var keys = [] , key; for (key in obj) { keys.push(key); } return keys.sort().join(','); } function extend(parent, obj) { var out = Object.create(parent) , key; if (typeof obj !== 'object' || Array.isArray(obj)) { return out; } for (key in obj) { out[key] = obj[key]; } return out; } function modelListener(params, triggerId, blockPaths, pathId, partial, ctx) { var listener = typeof params === 'function' ? params(triggerId, blockPaths, pathId) : params; listener.partial = partial; listener.ctx = ctx.$stringCtx || ctx; return listener; } function bindEvents(events, macro, name, partial, params) { if (~name.indexOf('(')) { var args = pathFnArgs(name); if (!args.length) return; events.push(function(ctx, modelEvents, dom, pathMap, view, blockPaths, triggerId) { var listener = modelListener(params, triggerId, blockPaths, null, partial, ctx) , path, pathId, i; listener.getValue = function(model, triggerPath) { patchCtx(ctx, triggerPath); return dataValue(view, ctx, model, name, macro); } for (i = args.length; i--;) { path = ctxPath(ctx, args[i], macro); pathId = pathMap.id(path + '*'); modelEvents.bind(pathId, listener); } }); return; } var match = /(\.*)(.*)/.exec(name) , prefix = match[1] || '' , relativeName = match[2] || '' , segments = relativeName.split('.') , bindName, i; for (i = segments.length; i; i--) { bindName = prefix + segments.slice(0, i).join('.'); (function(bindName) { events.push(function(ctx, modelEvents, dom, pathMap, view, blockPaths, triggerId) { var path = ctxPath(ctx, name, macro) , listener, pathId; if (!path) return; pathId = pathMap.id(path); listener = modelListener(params, triggerId, blockPaths, pathId, partial, ctx); if (name !== bindName) { path = ctxPath(ctx, bindName, macro); pathId = pathMap.id(path); listener.getValue = function(model, triggerPath) { patchCtx(ctx, triggerPath); return dataValue(view, ctx, model, name, macro); }; } modelEvents.bind(pathId, listener); }); })(bindName); } } function bindEventsById(events, macro, name, partial, attrs, method, prop, isBlock) { function params(triggerId, blockPaths, pathId) { var id = attrs._id || attrs.id; if (isBlock && pathId) blockPaths[id] = pathId; return [id, method, prop]; } bindEvents(events, macro, name, partial, params); } function bindEventsByIdString(events, macro, name, partial, attrs, method, prop) { function params(triggerId) { var id = triggerId || attrs._id || attrs.id; return [id, method, prop]; } bindEvents(events, macro, name, partial, params); } function addId(view, attrs) { if (attrs.id == null) { attrs.id = function() { return attrs._id = view._uniqueId(); }; } } function reduceStack(stack) { var html = [''] , i = 0 , attrs, bool, item, key, value, j, len; function pushValue(value, isAttr) { if (value && value.call) { return i = html.push(value, '') - 1; } else { return html[i] += isAttr ? escapeAttribute(value) : value; } } for (j = 0, len = stack.length; j < len; j++) { item = stack[j]; switch (item[0]) { case 'start': html[i] += '<' + item[1]; attrs = item[2]; // Make sure that the id attribute is rendered first if ('id' in attrs) { html[i] += ' id='; pushValue(attrs.id, true); } for (key in attrs) { if (key === 'id') continue; value = attrs[key]; if (value != null) { if (bool = value.bool) { pushValue(bool); continue; } html[i] += ' ' + key + '='; pushValue(value, true); } else { html[i] += ' ' + key; } } html[i] += '>'; break; case 'text': pushValue(item[1]); break; case 'end': html[i] += ''; break; case 'marker': html[i] += ''; } } return html; } function patchCtx(ctx, triggerPath) { var path = ctx.$paths[0]; if (!(triggerPath && path)) return; var segments = path.split('.') , triggerSegments = triggerPath.replace(/\*$/, '').split('.') , indices = ctx.$indices.slice() , index = indices.length , i, len, segment, triggerSegment, n; for (i = 0, len = segments.length; i < len; i++) { segment = segments[i]; triggerSegment = triggerSegments[i]; // `(n = +triggerSegment) === n` will be false only if segment is NaN if (segment === '$#' && (n = +triggerSegment) === n) { indices[--index] = n; } else if (segment !== triggerSegment) { break; } } ctx.$indices = indices; } function renderer(view, items, events, onRender) { return function(ctx, model, triggerPath, triggerId) { patchCtx(ctx, triggerPath); if (!model) model = view.model; // Needed, since model parameter is optional var pathMap = model.__pathMap , modelEvents = model.__events , blockPaths = model.__blockPaths , dom = view.dom , html = '' , i, len, item, event; if (onRender) ctx = onRender(ctx); for (i = 0, len = items.length; i < len; i++) { item = items[i]; html += typeof item === 'function' ? item(ctx, model) || '' : item; } for (i = 0; event = events[i++];) { event(ctx, modelEvents, dom, pathMap, view, blockPaths, triggerId); } return html; } } function createComponent(view, model, ns, name, scope, ctx, macroCtx, boundMacro) { var library = view._libraries[ns] , script = library && library.scripts[name]; if (!script) return; var initComponent = script.init , isServer = view.isServer , key, path, value, prefix, scoped; if (!initComponent && isServer) return; ctx.$fnCtx = script; scoped = model.at(scope); prefix = scope + '.'; for (key in macroCtx) { path = boundMacro[key]; if (path) { path = ctxPath(ctx, path); model.ref(prefix + key, path, null, true); continue; } value = macroCtx[key]; if (typeof value === 'function') continue; model.set(prefix + key, value); } if (initComponent) initComponent(model, scoped); if (isServer) return; var eventPrefix = (macroCtx['name'] || (ns + ':' + name)) + ':' , cancelled; function cancel() { cancelled = true; } scoped.trigger = function(name) { var args = [eventPrefix + name, scoped] .concat(arraySlice.call(arguments, 1), cancel); cancelled = false; model.emit.apply(model, args); return cancelled; }; setTimeout(function() { var elements = ctx.$elements , dom = view.dom.componentDom() , key, id; for (key in elements) { id = elements[key]; if (typeof id !== 'string') continue; elements[key] = document.getElementById(id); } script.create(model, scoped, dom, elements); }, 0); } function extendCtx(ctx, value, name, alias, index, isArray) { var path = ctxPath(ctx, name, null, true) , aliases; ctx = extend(ctx, value); ctx["this"] = value; if (alias) { aliases = ctx.$aliases = Object.create(ctx.$aliases); aliases[alias] = ctx.$depth; } if (path) ctx.$paths = [path].concat(ctx.$paths); if (name) ctx.$depth++; if (index != null) { ctx.$indices = [index].concat(ctx.$indices); isArray = true; } if (isArray && ctx.$paths[0]) { ctx.$paths[0] += '.$#'; } return ctx; } function partialValue(view, ctx, model, name, value, listener, macro) { if (listener) return value; return name ? dataValue(view, ctx, model, name, macro) : true; } function partialFn(view, name, type, alias, render, macroCtx, macro, boundMacro) { function conditionalRender(ctx, model, triggerPath, value, index, condition) { if (condition) { var renderCtx = extendCtx(ctx, value, name, alias, index); return render(renderCtx, model, triggerPath); } return ''; } function withFn(ctx, model, triggerPath, triggerId, value, index, listener) { value = partialValue(view, ctx, model, name, value, listener, macro); return conditionalRender(ctx, model, triggerPath, value, index, true); } if (type === 'partial') { return function(ctx, model, triggerPath, triggerId, value, index, listener) { var parentMacroCtx = ctx.$macroCtx , renderCtx, scope; if (alias) { scope = '_$component.' + view._uniqueId(); renderCtx = extendCtx(ctx, null, scope, alias); createComponent(view, model, name[0], name[1], scope, renderCtx, macroCtx, boundMacro); } else { renderCtx = Object.create(ctx); } renderCtx.$macroCtx = parentMacroCtx ? extend(parentMacroCtx, macroCtx) : macroCtx; return render(renderCtx, model, triggerPath); } } if (type === 'with' || type === 'else') { return withFn; } if (type === 'if' || type === 'else if') { return function(ctx, model, triggerPath, triggerId, value, index, listener) { value = partialValue(view, ctx, model, name, value, listener, macro); var condition = !!(Array.isArray(value) ? value.length : value); return conditionalRender(ctx, model, triggerPath, value, index, condition); } } if (type === 'unless') { return function(ctx, model, triggerPath, triggerId, value, index, listener) { value = partialValue(view, ctx, model, name, value, listener, macro); var condition = !(Array.isArray(value) ? value.length : value); return conditionalRender(ctx, model, triggerPath, value, index, condition); } } if (type === 'each') { return function(ctx, model, triggerPath, triggerId, value, index, listener) { var indices, isArray, item, out, renderCtx, i, len; value = partialValue(view, ctx, model, name, value, listener, macro); isArray = Array.isArray(value); if (listener && !isArray) { return withFn(ctx, model, triggerPath, triggerId, value, index, true); } if (!isArray) return ''; ctx = extendCtx(ctx, null, name, alias, null, true); out = ''; indices = ctx.$indices; for (i = 0, len = value.length; i < len; i++) { item = value[i]; renderCtx = extend(ctx, item); renderCtx["this"] = item; renderCtx.$indices = [i].concat(indices); out += render(renderCtx, model, triggerPath); } return out; } } throw new Error('Unknown block type: ' + type); } var objectToString = Object.prototype.toString; function textFn(view, name, escape, macro) { return function(ctx, model) { var value = dataValue(view, ctx, model, name, macro) , text = typeof value === 'string' ? value : value == null ? '' : value.toString === objectToString ? JSON.stringify(value) : value.toString(); return escape ? escape(text) : text; } } function sectionFn(view, queue) { var render = renderer(view, reduceStack(queue.stack), queue.events) , block = queue.block; return partialFn(view, block.name, block.type, block.alias, render, null, block.macro); } function blockFn(view, sections) { var len = sections.length; if (!len) return; if (len === 1) { return sectionFn(view, sections[0]); } else { var fns = [] , i; for (i = 0; i < len; i++) { fns.push(sectionFn(view, sections[i])); } return function(ctx, model, triggerPath, triggerId, value, index, listener) { var out, fn; for (i = 0; i < len; i++) { fn = fns[i]; out = fn(ctx, model, triggerPath, triggerId, value, index, listener); if (out) return out; } return ''; } } } function parseMarkup(type, attr, tagName, events, attrs, name) { var parser = markup[type][attr] , anyOut, anyParser, elOut, elParser, out; if (!parser) return; if (anyParser = parser['*']) { anyOut = anyParser(events, attrs, name); } if (elParser = parser[tagName]) { elOut = elParser(events, attrs, name); } out = anyOut ? extend(anyOut, elOut) : elOut; if (out && out.del) delete attrs[attr]; return out; } function pushText(stack, text) { if (text) stack.push(['text', text]); } function pushVarFn(view, stack, fn, name, escapeFn, macro) { if (fn) { pushText(stack, fn); } else { pushText(stack, textFn(view, name, escapeFn, macro)); } } function boundMacroName(boundMacro, name) { var macroVar = name.split('.')[0]; return boundMacro[macroVar]; } function boundName(boundMacro, match, name) { if (!(name && match.macro)) return match.bound && name; if (~name.indexOf('(')) { var args = pathFnArgs(name) , i, len; for (i = 0, len = args.length; i < len; i++) { if (boundMacroName(boundMacro, args[i])) return name; } return false; } return boundMacroName(boundMacro, name); } function isPartial(view, partial) { var arr = partial.split(':') , partialNs = arr[0]; return arr.length >= 2 && (partialNs === view._selfNs || !!view._libraries[partialNs]); } function splitPartial(view, partial, ns) { var i = partial.indexOf(':') , partialNs = partial.slice(0, i) , partialName = partial.slice(i + 1) , partialView; if (partialNs !== view._selfNs) { partialView = view._libraries[partialNs].view; partialView._uniqueId = function() { return view._uniqueId(); }; partialView.dom = view.dom; } else { partialView = view; } return [partialNs, partialName, partialView]; } function isNonvoid(view, partial, ns) { var arr = splitPartial(view, partial, ns) , partialName = arr[1] , view = arr[2]; return !!view._findItem(partialName, ns, '_nonvoidComponents'); } function pushVar(view, ns, stack, events, boundMacro, remainder, match, fn) { var name = match.name , partial = match.partial , macro = match.macro , escapeFn = match.escaped && escapeHtml , attr, attrs, boundOut, last, tagName, wrap, render, parseName; if (partial) { var arr = splitPartial(view, partial, ns) , partialNs = arr[0] , partialName = arr[1] , alias = partialNs === view._selfNs ? '' : 'self' render = arr[2]._find(partialName, ns, boundMacro); fn = partialFn(view, arr, 'partial', alias, render, match.macroCtx, null, boundMacro); } if (parseName = boundName(boundMacro, match, name)) { last = stack[stack.length - 1]; wrap = match.pre || !last || (last[0] !== 'start') || isVoid(tagName = last[1]) || wrapRemainder(tagName, remainder); if (wrap) { stack.push(['marker', '', attrs = {}]); } else { attrs = last[2]; for (attr in attrs) { parseMarkup('boundParent', attr, tagName, events, attrs, parseName); } boundOut = parseMarkup('boundParent', '*', tagName, events, attrs, parseName); if (boundOut) { bindEventsById(events, macro, name, null, attrs, boundOut.method, boundOut.property); } } addId(view, attrs); if (!boundOut) { bindEventsById(events, macro, name, fn, attrs, 'html', !fn && escapeFn, true); } } pushVarFn(view, stack, fn, name, escapeFn, macro); if (wrap) { stack.push([ 'marker' , '$' , { id: function() { return attrs._id } } ]); } } function pushVarString(view, ns, stack, events, boundMacro, remainder, match, fn) { var name = match.name , escapeFn = !match.escaped && unescapeEntities; function bindOnce(ctx) { ctx.$onBind(events, name); bindOnce = empty; } if (boundName(boundMacro, match, name)) { events.push(function(ctx) { bindOnce(ctx); }); } pushVarFn(view, stack, fn, name, escapeFn, match.macro); } function parseMatchError(text, message) { throw new Error(message + '\n\n' + text + '\n'); } function onBlock(start, end, block, queues, callbacks) { var boundMacro, lastQueue, queue; if (end) { lastQueue = queues.pop(); queue = queues.last(); queue.sections.push(lastQueue); } else { queue = queues.last(); } if (start) { boundMacro = Object.create(queue.boundMacro); queues.push(queue = { stack: [] , events: [] , block: block , sections: [] , boundMacro: boundMacro }); callbacks.onStart(queue); } else { if (end) { callbacks.onStart(queue); callbacks.onEnd(queue.sections); queue.sections = []; } else { callbacks.onContent(block); } } } function parseMatch(text, match, queues, callbacks) { var hash = match.hash , type = match.type , name = match.name , block = queues.last().block , blockType = block && block.type , startBlock, endBlock; if (type === 'if' || type === 'unless' || type === 'each' || type === 'with') { if (hash === '#') { startBlock = true; } else if (hash === '/') { endBlock = true; } else { parseMatchError(text, type + ' blocks must begin with a #'); } } else if (type === 'else' || type === 'else if') { if (hash) { parseMatchError(text, type + ' blocks may not start with ' + hash); } if (blockType !== 'if' && blockType !== 'else if' && blockType !== 'unless' && blockType !== 'each') { parseMatchError(text, type + ' may only follow `if`, `else if`, `unless`, or `each`'); } startBlock = true; endBlock = true; } else if (hash === '/') { endBlock = true; } else if (hash === '#') { parseMatchError(text, '# must be followed by `if`, `unless`, `each`, or `with`'); } if (endBlock && !block) { parseMatchError(text, 'Unmatched template end tag'); } onBlock(startBlock, endBlock, match, queues, callbacks); } function parseAttr(view, viewName, events, boundMacro, tagName, attrs, attr, value) { if (typeof value === 'function') return; var attrOut = parseMarkup('attr', attr, tagName, events, attrs, value) || {} , parseName, boundOut, macro, match, name, render, method, property; if (attrOut.addId) addId(view, attrs); if (match = extractPlaceholder(value)) { name = match.name; macro = match.macro; if (match.pre || match.post) { // Attributes must be a single string, so create a string partial addId(view, attrs); render = parse(view, viewName, value, true, function(events, name) { bindEventsByIdString(events, macro, name, render, attrs, 'attr', attr); }, boundMacro); attrs[attr] = attr === 'id' ? function(ctx, model) { return attrs._id = escapeAttribute(render(ctx, model)); } : function(ctx, model) { return escapeAttribute(render(ctx, model)); } return; } if (parseName = boundName(boundMacro, match, name)) { boundOut = parseMarkup('bound', attr, tagName, events, attrs, parseName) || {}; addId(view, attrs); method = boundOut.method || 'attr'; property = boundOut.property || attr; bindEventsById(events, macro, name, null, attrs, method, property); } if (!attrOut.del) { macro = match.macro; attrs[attr] = attrOut.bool ? { bool: function(ctx, model) { return (dataValue(view, ctx, model, name, macro)) ? ' ' + attr : ''; } } : textFn(view, name, escapeAttribute, macro); } } } function parsePartialAttr(view, viewName, events, attrs, attr, value) { var bound = false , match = extractPlaceholder(value) , name; if (attr === 'content') { throw new Error('components may not have an attribute named "content"'); } if (match) { if (match.pre || match.post) { throw new Error('unimplemented: blocks in component attributes'); } name = match.name; bound = match.bound; attrs[attr] = {$macroVar: name}; } else if (value === 'true') { attrs[attr] = true; } else if (value === 'false') { attrs[attr] = false; } else if (value === 'null') { attrs[attr] = null; } else if (!isNaN(value)) { attrs[attr] = +value; } return bound; } function parse(view, viewName, template, isString, onBind, boundMacro) { if (boundMacro == null) boundMacro = {}; var queues, stack, events, onRender, push; queues = [{ stack: stack = [] , events: events = [] , sections: [] , boundMacro: boundMacro }]; queues.last = function() { return queues[queues.length - 1]; }; function onStart(queue) { stack = queue.stack; events = queue.events; boundMacro = queue.boundMacro; } if (isString) { push = pushVarString; onRender = function(ctx) { if (ctx.$stringCtx) return ctx; ctx = Object.create(ctx); ctx.$onBind = onBind; ctx.$stringCtx = ctx; return ctx; } } else { push = pushVar; } var index = viewName.lastIndexOf(':') , ns = ~index ? viewName.slice(0, index) : '' , minifyContent = true; function parseStart(tag, tagName, attrs) { var attr, block, bound, out, parser, value if ('x-no-minify' in attrs) { delete attrs['x-no-minify']; minifyContent = false; } else { minifyContent = true; } if (isPartial(view, tagName)) { for (attr in attrs) { value = attrs[attr]; bound = parsePartialAttr(view, viewName, events, attrs, attr, value); if (bound) { boundMacro[attr] = attrs[attr].$macroVar; } } block = { partial: tagName , macroCtx: attrs }; if (isNonvoid(view, tagName, ns)) { onBlock(true, false, block, queues, {onStart: onStart}); } else { push(view, ns, stack, events, boundMacro, '', block); } return; } if (parser = markup.element[tagName]) { out = parser(events, attrs); if (out != null ? out.addId : void 0) { addId(view, attrs); } } for (attr in attrs) { value = attrs[attr]; parseAttr(view, viewName, events, boundMacro, tagName, attrs, attr, value); } stack.push(['start', tagName, attrs]); } function parseText(text, isRawText, remainder) { var match = extractPlaceholder(text) , post, pre; if (!match || isRawText) { if (minifyContent) { text = isString ? unescapeEntities(trimLeading(text)) : trimLeading(text); } pushText(stack, text); return; } pre = match.pre; post = match.post; if (isString) pre = unescapeEntities(pre); pushText(stack, pre); remainder = post || remainder; parseMatch(text, match, queues, { onStart: onStart , onEnd: function(sections) { var fn = blockFn(view, sections); push(view, ns, stack, events, boundMacro, remainder, sections[0].block, fn); } , onContent: function(match) { push(view, ns, stack, events, boundMacro, remainder, match); } }); if (post) return parseText(post); } function parseEnd(tag, tagName) { if (isPartial(view, tagName)) { onBlock(false, true, null, queues, { onStart: onStart , onEnd: function(queues) { var queue = queues[0] , block = queue.block; block.macroCtx.content = renderer(view, reduceStack(queue.stack), queue.events); push(view, ns, stack, events, boundMacro, '', block); } }) return; } stack.push(['end', tagName]); } if (isString) { parseText(template); } else { parseHtml(template, { start: parseStart , text: parseText , end: parseEnd , comment: function(tag) { if (conditionalComment(tag)) pushText(stack, tag); } , other: function(tag) { pushText(stack, tag); } }); } return renderer(view, reduceStack(stack), events, onRender); }