Source: br-services/src/br/services/html/BRHtmlResourceService.js

/**
* @module br/services/html/BRHtmlResourceService
*/

var templatesLoaded = false;

var br = require('br/Core');
var File = require('br/core/File');
var HtmlResourceService = require('br/services/HtmlResourceService');
var i18n = require('br/I18n');

/**
 * @class
 * @alias module:br/services/html/BRHtmlResourceService
 * @implements module:br/services/HtmlResourceService
 *
 * @classdesc
 * Provides access to HTML templates loaded via the HTML bundler.
 * This is the default HtmlResourceService in BladeRunnerJS
 *
 * @param {String} url A URL to load HTML from.
 */
function BRHtmlResourceService(url, forceTemplateReload) {
	loadHtml(url || require('service!br.app-meta-service').getVersionedBundlePath("html/bundle.html"), forceTemplateReload);
}

br.implement(BRHtmlResourceService, HtmlResourceService);

BRHtmlResourceService.prototype.getTemplateFragment = function(templateId) {
	var template = document.getElementById(templateId);
	return (template) ? document.importNode(template.content, true) : null;
};

BRHtmlResourceService.prototype.getTemplateElement = function(templateId) {
	var templateFragment = this.getTemplateFragment(templateId);

	if(!templateFragment) {
		return null;
	}
	else {
		var templateNodes = nonEmptyNodes(templateFragment.childNodes);

		if(templateNodes.length != 1) throw new RangeError("The '" + templateId +
			"' template contained more than one root node -- use getTemplateFragment() instead.");

		return templateNodes[0];
	}
};

BRHtmlResourceService.prototype.getHTMLTemplate = function(templateId) {
	if(window.console) console.warn('getHTMLTemplate() is now deprecated -- please use getTemplateElement() instead.');
	return this.getTemplateElement(templateId);
};

function loadHtml(url, forceTemplateReload) {
	forceTemplateReload = forceTemplateReload || false;
	if (!templatesLoaded || forceTemplateReload) {
		// template#brjs-html-templates-inline is generated by the tag handler
		// we use a template tag since its the only element that can appear in both the HEAD and BODY
		// if it isn't present (templates havent been loaded inline) we need to XHR in the templates
		var templatesInlineIndicator = document.querySelector('template#brjs-html-templates-inline');
		if (!templatesInlineIndicator) {
			var templateContainer = document.createElement('div');
		
			var rawHtml = File.readFileSync(url);
			var translatedHtml = i18n.getTranslator().translate(rawHtml, "html");
			templateContainer.innerHTML = sanitizeHtml(translatedHtml);
			
			var headElem = document.querySelector('head');
			var templateContainerChildNodes = templateContainer.childNodes;
			while (templateContainerChildNodes.length > 0) {
				headElem.appendChild( templateContainerChildNodes[0] );
			}
		}
		
		fixNestedAutoWrappedTemplates();
		shimTemplates();
		fixAutoWrappedTemplates();
		
		templatesLoaded = true;
	}
}

function nonEmptyNodes(childNodes) {
	var nonEmptyNodes = [];

	for(var i = 0, l = childNodes.length; i < l; ++i) {
		var childNode = childNodes[i];

		if((childNode.nodeType == document.ELEMENT_NODE) ||
			((childNode.nodeType == document.TEXT_NODE) && (text(childNode).trim() != ''))) {
			nonEmptyNodes.push(childNode);
		}
	}

	return nonEmptyNodes;
}

function text(textNode) {
	return textNode.textContent || textNode.innerText || '';
}

function shimTemplate(templateElem) {
	if(!templateElem.content) {
		templateElem.content = document.createDocumentFragment();
	}
}

function shimTemplates() {
	if(!('content' in document.createElement('template'))) {
		var templateElems = document.getElementsByTagName('template');

		for(var i = 0;  i < templateElems.length; ++i) {
			var templateElem = templateElems[i];

			if(!templateElem.content) {
				var templateContent = document.createDocumentFragment();

				while(templateElem.childNodes[0]) {
					templateContent.appendChild(templateElem.childNodes[0]);
				}

				templateElem.content = templateContent;
			}
		}
	}
}

function fixNestedAutoWrappedTemplates() {
	var templateElems = document.querySelectorAll('template[data-auto-wrapped] template');

	for(var i = 0;  i < templateElems.length; ++i) {
		var templateElem = templateElems[i];
		var parentTemplate = templateElem.parentNode;
		parentTemplate.parentNode.insertBefore(templateElem, parentTemplate.nextSibling);
	}
}

function fixAutoWrappedTemplates() {
	var templateElems = document.getElementsByTagName('template');

	for(var i = 0;  i < templateElems.length; ++i) {
		var templateElem = templateElems[i];

		if(templateElem.hasAttribute('data-auto-wrapped')) {
			templateElem.removeAttribute('data-auto-wrapped');
			var templateNodes = nonEmptyNodes(templateElem.content.childNodes);

			for(var ni = 0; ni < templateNodes.length; ++ni) {
				var templateNode = templateNodes[ni];

				if(ni > 0) {
					if(templateNode.nodeName == 'TEMPLATE') {
						// Note: on browser's with native template support, this code is used instead
						// fixNestedAutoWrappedTemplates()
						templateElem.parentNode.insertBefore(templateNode, templateElem.nextSibling);
					}
					else {
						var newTemplateElem = document.createElement('template');
						shimTemplate(newTemplateElem);
						newTemplateElem.id = templateNode.id;
						newTemplateElem.content.appendChild(templateNode);
						templateElem.parentNode.insertBefore(newTemplateElem, templateElem.nextSibling);

						if(templateNode.hasAttribute('id')) {
							templateNode.removeAttribute('id');
						}
					}
				}
				else {
					if(templateNode.hasAttribute('id')) {
						templateNode.removeAttribute('id');
					}
				}
			}
		}
	}
}

// TODO: delete this method once we get to 2016
function sanitizeHtml(html) {
	function replacer(str, p1) {
		return '<div' + p1;
	}

	// IE and old Firefox's don't allow assigning text with script tag in it to innerHTML.
	if (html.match(/<script(.*)type=\"text\/html\"/)) {
	 	html = html.replace(/<script(.*)type=\"text\/html\"/g, replacer).replace(/<\/script>/g, '</div>');
	}

	return html;
}

module.exports = BRHtmlResourceService;