if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
if (typeof ext.listeners !== 'object') {
ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
for (var ln in ext.listeners) {
if (ext.listeners.hasOwnProperty(ln)) {
if (typeof ext.listeners[ln] !== 'function') {
ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
' must be a function but ' + typeof ext.listeners[ln] + ' given';
if (typeof ext.filter !== 'function') {
ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
if (showdown.helper.isString(ext.regex)) {
ext.regex = new RegExp(ext.regex, 'g');
if (!(ext.regex instanceof RegExp)) {
ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
if (showdown.helper.isUndefined(ext.replace)) {
ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
showdown.validateExtension = function (ext) {
var validateExtension = validate(ext, null);
if (!validateExtension.valid) {
console.warn(validateExtension.error);
* showdownjs helper functions
if (!showdown.hasOwnProperty('helper')) {
showdown.helper.isString = function (a) {
return (typeof a === 'string' || a instanceof String);
* Check if var is a function
showdown.helper.isFunction = function (a) {
return a && getType.toString.call(a) === '[object Function]';
* isArray helper function
showdown.helper.isArray = function (a) {
* Check if value is undefined
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
showdown.helper.isUndefined = function (value) {
return typeof value === 'undefined';
* ForEach helper function
* Iterates over Arrays and Objects (own properties only)
* @param {function} callback Accepts 3 params: 1. value, 2. key, 3. the original array/object
showdown.helper.forEach = function (obj, callback) {
// check if obj is defined
if (showdown.helper.isUndefined(obj)) {
throw new Error('obj param is required');
if (showdown.helper.isUndefined(callback)) {
throw new Error('callback param is required');
if (!showdown.helper.isFunction(callback)) {
throw new Error('callback param must be a function/closure');
if (typeof obj.forEach === 'function') {
} else if (showdown.helper.isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
callback(obj[i], i, obj);
} else if (typeof (obj) === 'object') {
if (obj.hasOwnProperty(prop)) {
callback(obj[prop], prop, obj);
throw new Error('obj does not seem to be an array or an iterable object');
* Standardidize extension name
* @param {string} s extension name
showdown.helper.stdExtName = function (s) {
return s.replace(/[_?*+\/\\.^-]/g, '').replace(/\s/g, '').toLowerCase();
function escapeCharactersCallback (wholeMatch, m1) {
var charCodeToEscape = m1.charCodeAt(0);
return '¨E' + charCodeToEscape + 'E';
* Callback used to escape characters when passing through String.replace
* @param {string} wholeMatch
showdown.helper.escapeCharactersCallback = escapeCharactersCallback;
* Escape characters in a string
* @param {string} charsToEscape
* @param {boolean} afterBackslash
* @returns {XML|string|void|*}
showdown.helper.escapeCharacters = function (text, charsToEscape, afterBackslash) {
// First we have to escape the escape characters so that
// we can build a character class out of them
var regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])';
regexString = '\\\\' + regexString;
var regex = new RegExp(regexString, 'g');
text = text.replace(regex, escapeCharactersCallback);
showdown.helper.unescapeHTMLEntities = function (txt) {
var rgxFindMatchPos = function (str, left, right, flags) {
x = new RegExp(left + '|' + right, 'g' + f.replace(/g/g, '')),
l = new RegExp(left, f.replace(/g/g, '')),
while ((m = x.exec(str))) {
end = m.index + m[0].length;
left: {start: start, end: s},
match: {start: s, end: m.index},
right: {start: m.index, end: end},
wholeMatch: {start: start, end: end}
} while (t && (x.lastIndex = s));
* (c) 2007 Steven Levithan <stevenlevithan.com>
* Accepts a string to search, a left and right format delimiter
* as regex patterns, and optional regex flags. Returns an array
* of matches, allowing nested instances of left/right delimiters.
* Use the "g" flag to return all matches, otherwise only the
* first is returned. Be careful to ensure that the left and
* right format delimiters produce mutually exclusive matches.
* Backreferences are not supported within the right delimiter
* due to how it is internally combined with the left delimiter.
* When matching strings whose format delimiters are unbalanced
* to the left or right, the output is intentionally as a
* conventional regex library with recursion support would
* produce, e.g. "<<x>" and "<x>>" both produce ["x"] when using
* "<" and ">" as the delimiters (both strings contain a single,
* balanced instance of "<x>").
* matchRecursiveRegExp("test", "\\(", "\\)")
* matchRecursiveRegExp("<t<<e>><s>>t<>", "<", ">", "g")
* returns: ["t<<e>><s>", ""]
* matchRecursiveRegExp("<div id=\"x\">test</div>", "<div\\b[^>]*>", "</div>", "gi")
showdown.helper.matchRecursiveRegExp = function (str, left, right, flags) {
var matchPos = rgxFindMatchPos (str, left, right, flags),
for (var i = 0; i < matchPos.length; ++i) {
str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
str.slice(matchPos[i].match.start, matchPos[i].match.end),
str.slice(matchPos[i].left.start, matchPos[i].left.end),
str.slice(matchPos[i].right.start, matchPos[i].right.end)
* @param {string|function} replacement
showdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) {
if (!showdown.helper.isFunction(replacement)) {
var repStr = replacement;
replacement = function () {
var matchPos = rgxFindMatchPos(str, left, right, flags),
if (matchPos[0].wholeMatch.start !== 0) {
bits.push(str.slice(0, matchPos[0].wholeMatch.start));
for (var i = 0; i < lng; ++i) {
str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
str.slice(matchPos[i].match.start, matchPos[i].match.end),
str.slice(matchPos[i].left.start, matchPos[i].left.end),
str.slice(matchPos[i].right.start, matchPos[i].right.end)
bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start));
if (matchPos[lng - 1].wholeMatch.end < str.length) {
bits.push(str.slice(matchPos[lng - 1].wholeMatch.end));
finalStr = bits.join('');
* Returns the index within the passed String object of the first occurrence of the specified regex,
* starting the search at fromIndex. Returns -1 if the value is not found.
* @param {string} str string to search
* @param {RegExp} regex Regular expression to search
* @param {int} [fromIndex = 0] Index to start the search
* @throws InvalidArgumentError
showdown.helper.regexIndexOf = function (str, regex, fromIndex) {
if (!showdown.helper.isString(str)) {
throw 'InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string';
if (regex instanceof RegExp === false) {
throw 'InvalidArgumentError: second parameter of showdown.helper.regexIndexOf function must be an instance of RegExp';
var indexOf = str.substring(fromIndex || 0).search(regex);
return (indexOf >= 0) ? (indexOf + (fromIndex || 0)) : indexOf;
* Splits the passed string object at the defined index, and returns an array composed of the two substrings
* @param {string} str string to split
* @param {int} index index to split string at
* @returns {[string,string]}
* @throws InvalidArgumentError
showdown.helper.splitAtIndex = function (str, index) {
if (!showdown.helper.isString(str)) {
throw 'InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string';
return [str.substring(0, index), str.substring(index)];
* Obfuscate an e-mail address through the use of Character Entities,
* transforming ASCII characters into their equivalent decimal or hex entities.
* Since it has a random component, subsequent calls to this function produce different results
showdown.helper.encodeEmailAddress = function (mail) {
return '&#' + ch.charCodeAt(0) + ';';
return '&#x' + ch.charCodeAt(0).toString(16) + ';';
mail = mail.replace(/./g, function (ch) {
// this *must* be encoded. I insist.
ch = encode[Math.floor(Math.random() * 2)](ch);
// roughly 10% raw, 45% hex, 45% dec
r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
showdown.helper.padEnd = function padEnd (str, targetLength, padString) {
/*jshint bitwise: false*/
// eslint-disable-next-line space-infix-ops
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
padString = String(padString || ' ');
if (str.length > targetLength) {
targetLength = targetLength - str.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
return String(str) + padString.slice(0,targetLength);
// use this instead of builtin is undefined for IE8 compatibility
if (typeof console === 'undefined') {
* We declare some common regexes to improve performance
showdown.helper.regexes = {
asteriskDashAndColon: /([*_:~])/g
showdown.helper.emojis = {
'1st_place_medal':'\ud83e\udd47',
'2nd_place_medal':'\ud83e\udd48',
'3rd_place_medal':'\ud83e\udd49',
'a':'\ud83c\udd70\ufe0f',