Skip to content

Instantly share code, notes, and snippets.

@Bajix
Last active March 18, 2016 18:46
Show Gist options
  • Save Bajix/1e7cb87151263da8bdb4 to your computer and use it in GitHub Desktop.
Save Bajix/1e7cb87151263da8bdb4 to your computer and use it in GitHub Desktop.
Clean stache template, allowing only links and tags other than subtemplates
define([
'can',
'url',
'urlregexp',
'can/view/stache/intermediate_and_imports'
], function( can, url, urlRegex, getIntermediateAndImports ) {
var tagFilter = /^(a|i|s|del|br|blockquote|code|q|cite|small|strong|sub|sup|em|mark|u|h[1-6])$/,
attrFilter = /^(href|rel)$/;
function parseIntermediate ( source ) {
var intermediateAndImports = getIntermediateAndImports(source),
intermediate = intermediateAndImports.intermediate;
return reduceIntermediate(intermediate);
}
function reduceIntermediate( intermediate ) {
filterAttr(intermediate, attrFilter, 0);
var insideLink = false;
for (var i = 0; i < intermediate.length; i++) {
var token = intermediate[i],
tokenType = token.tokenType,
tokenVal = token.args[0];
if (tokenType === 'start') {
if (!insideLink && tokenVal === 'a') {
insideLink = true;
i = parseLinkIntermediate(intermediate, i);
break;
}
if (insideLink && tokenVal === 'a' || !tagFilter.test(tokenVal)) {
// Keep only inline elements
intermediate.splice(i, tagCloseAt(intermediate, tokenVal, i + 1) - i);
i--;
}
break;
}
if (tokenType === 'close' && tokenVal === 'a') {
insideLink = false;
break;
}
// Remove Sub templates
if (tokenType === 'special' && tokenVal[0] === '>') {
intermediate.splice(i, 1);
i--;
break;
}
// Detect, sanitize & wrap urls
if (!insideLink && tokenType === 'chars' && urlRegex.test(tokenVal)) {
i = parseLink(intermediate, i);
}
}
return intermediate;
}
function nextTypeIndex ( intermediate, tokenType, i ) {
for (i; i < intermediate.length; i++) {
if (intermediate[i].tokenType === tokenType) {
return i;
}
}
return -1;
}
function nextTokenIndex ( intermediate, tokenType, tokenVal, i ) {
if (~i) {
var token = intermediate[i];
if (token.tokenType === tokenType) {
return i;
}
return nextTokenIndex(intermediate, tokenType, tokenVal, nextTypeIndex(intermediate, tokenType, i));
}
return -1;
}
function parseLinkIntermediate( intermediate, i ) {
var x = nextTokenIndex(intermediate, 'end', 'a', i);
var tagIntermediate = intermediate.slice(i + 1, x);
tagIntermediate.push({
args: ['target'],
tokenType: 'attrStart'
}, {
args: ['_blank'],
tokenType: 'attrValue'
}, {
args: ['target'],
tokenType: 'attrEnd'
});
// Check for href attr
if (~nextTokenIndex(tagIntermediate, 'attrStart', 'href', 0)) {
// Splice cleaned attributes
Array.prototype.splice.apply(intermediate, [i + 1, x - i - 1].concat(tagIntermediate));
return i;
}
// Remove invalid `a` tags
intermediate.splice(i, tagCloseAt(intermediate, tagName, i + 1) - i);
return i - 1;
}
function parseLink( intermediate, i ) {
var token = intermediate[i],
body = token.args[0];
var source = body.replace(urlRegex, function( uri ) {
if (!~uri.indexOf('://')) {
uri = 'http://' + uri;
}
uri = url.parse(uri);
return '<a href="' + uri.href + '">' + uri.hostname + uri.path + '</a>';
});
var linkIntermediate = parseIntermediate(source);
// Remove `done` token
linkIntermediate.pop();
Array.prototype.splice.apply(intermediate, [i, 1].concat(linkIntermediate));
return i - 1;
}
function filterAttr( intermediate, filter, i ) {
if (~i) {
var token = intermediate[i],
tokenType = token.tokenType,
tokenVal = token.args[0];
if (tokenType === 'attrStart' && !filter.test(tokenVal)) {
intermediate.splice(i, nextTypeIndex(intermediate, 'attrEnd', i + 1) - i + 1);
return filterAttr(intermediate, filter, i - 1);
} else {
return filterAttr(intermediate, filter, nextTypeIndex(intermediate, 'attrStart', i + 1));
}
}
return intermediate;
}
function tagCloseAt( intermediate, tagName, i ) {
for (i; i < intermediate.length; i++) {
var token = intermediate[i],
tokenType = token.tokenType,
tokenVal = token.args[0];
if (tokenVal === tagName) {
if (tokenType === 'close') {
return i;
}
if (tokenType === 'start') {
i = tagCloseAt(intermediate, tagName, i + 1);
}
}
}
}
function Markup( source ) {
if (!(this instanceof Markup)) {
return new Markup(source);
}
var intermediate = parseIntermediate(source);
this.template = can.stache(intermediate);
this.tagCount = 0;
this.length = 0;
for (var i = 0; i < intermediate.length; i++) {
var token = intermediate[i],
tokenType = token.tokenType,
tokenVal = token.args[0];
if (tokenType === 'start') {
this.tagCount++;
break;
}
if (tokenType === 'chars') {
this.length += tokenVal.length;
}
}
};
Markup.prototype.render = function( data, helpers, nodeList ) {
var template = this.template;
return template.apply(template, arguments);
};
Markup.prototype.toString = function() {
var frag = this.template.apply(this.template, arguments),
div = document.createElement('div');
div.appendChild(frag);
return div.innerHTML;
};
return Markup;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment