Skip to content

Instantly share code, notes, and snippets.

@lleaff
Last active September 8, 2016 10:35
Show Gist options
  • Save lleaff/e314744b80e7db76493b9214df0eed7c to your computer and use it in GitHub Desktop.
Save lleaff/e314744b80e7db76493b9214df0eed7c to your computer and use it in GitHub Desktop.
Selectively escape or exclude from escaping HTML tags
/**
* Slice an array every n element
* @example
* array_slice_every_n([1,2,3,4,5,6,7], 3)
* //=> [[1,4,7],[2,5],[3,6]]
*/
function array_slice_every_n(array, n) {
let subs = Array(n);
const min_len = Math.floor(array.length / n);
const rem = array.length - n * min_len;
for (let m = 0; m < n; m++) {
subs[m] = Array(min_len + (rem - m > 0 ? 1 : 0));
}
for (let i = 0; i < array.length; i++) {
subs[i % n][Math.floor(i / n)] = array[i];
}
return subs;
}
function array_separate_odd_and_pair_indices(array) {
const [odds, pairs] = array_slice_every_n(array, 2);
return { odds, pairs };
}
function increment_wrap({low: lo, high: hi, index: i}) {
return i >= hi ? lo : i + 1;
}
/**
* Interleave arrays elements into one array
* @example
* arrays_interleave([1,2,3,4], "abcdefgh".split(''), "ABCDE".split(''))
* //=> [1,"a","A" 2,"b","B",3,"c","C",4,"d","D","e","E","f","g", "h"]
* @param {...[]} arrays
*/
function arrays_interleave(array_a, array_b) {
var arrays = Array.prototype.slice.call(arguments, 0);
var result = [];
for (let i = 0; arrays.length; i = increment_wrap({low: 0, high: arrays.length - 1, index: i})) {
if (arrays[i].length === 0) {
arrays.splice(i, 1);
i = i - 1;
} else {
result.push(arrays[i].shift());
}
}
return result;
}
function escapeHTML(text) {
const replacements = [
{ match: /&/g, subst: "&amp;" },
{ match: /</g, subst: "&lt;" },
{ match: />/g, subst: "&gt;" },
{ match: /"/g, subst: "&quot;" },
{ match: /'/g, subst: "&#039;" },
{ match: /\//g, subst: "&#047;" },
];
return replacements.reduce((p, { match, subst }) => p.replace(match, subst), text);
}
/**
* @param {string} options.text - Raw HTML input
* @param {string|string[]} options.tagNames - Name of the tag(s) to exclude or include in the escaping
* depending on the value of `exclude`
* @param {boolean} [options.exclude=false] - If true: escape all but the selected tags, else escape only the
* selected tags
* @return {string} escaped HTML
*/
function escapeHTMLTags({ text, tags: tagNames, exclude }) {
const tagsRegexp = (tagNames) ?
`(?:${(typeof tagNames === 'string' ? [tagNames] : tagNames).join('|')})` :
'(?:\\w+)';
const regexp = new RegExp(`(<\\s*/?\\s*${tagsRegexp}[^>]*\\s*>)`, 'ig');
/* Split text into selected tags and the rest: [text, tag, text, tag, ...] */
let { odds: chunks, pairs: tags } = array_separate_odd_and_pair_indices(text.split(regexp));
if (exclude) { /* If in exclude tags mode, we escape all the rest */
chunks = chunks.map(escapeHTML);
} else { /* else escape only the selected tags */
tags = tags.map(escapeHTML);
}
return arrays_interleave(chunks, tags).join("");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment