: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if (!showdown.helper.isString(name)) {
throw Error('Extension \'name\' must be a string');
name = showdown.helper.stdExtName(name);
if (showdown.helper.isUndefined(ext)) {
if (!extensions.hasOwnProperty(name)) {
throw Error('Extension named ' + name + ' is not registered!');
// Expand extension if it's wrapped in a function
if (typeof ext === 'function') {
// Ensure extension is an array
if (!showdown.helper.isArray(ext)) {
var validExtension = validate(ext, name);
if (validExtension.valid) {
throw Error(validExtension.error);
* Gets all extensions registered
showdown.getAllExtensions = function () {
showdown.removeExtension = function (name) {
showdown.resetExtensions = function () {
* @param {array} extension
* @returns {{valid: boolean, error: string}}
function validate (extension, name) {
var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
if (!showdown.helper.isArray(extension)) {
for (var i = 0; i < extension.length; ++i) {
var baseMsg = errMsg + ' sub-extension ' + i + ': ',
if (typeof ext !== 'object') {
ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
if (!showdown.helper.isString(ext.type)) {
ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
var type = ext.type = ext.type.toLowerCase();
// normalize extension type
if (type === 'language') {
type = ext.type = 'lang';
type = ext.type = 'output';
if (type !== 'lang' && type !== 'output' && type !== 'listener') {
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
if (type === 'listener') {
if (showdown.helper.isUndefined(ext.listeners)) {
ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
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