/**
 * Showdown configuration options
 */
export interface ShowdownOptions {
	/** Omit the default extra whiteline added to code blocks */
	omitExtraWLInCodeBlocks?: boolean
	/** Turn on/off generated header id */
	noHeaderId?: boolean
	/** Add a prefix to the generated header ids */
	prefixHeaderId?: boolean | string
	/** Prevent showdown from modifying the prefix */
	rawPrefixHeaderId?: boolean
	/** Generate header ids compatible with github style */
	ghCompatibleHeaderId?: boolean
	/** Remove only spaces, ' and " from generated header ids */
	rawHeaderId?: boolean
	/** The header blocks level start */
	headerLevelStart?: number
	/** Turn on/off image dimension parsing */
	parseImgDimensions?: boolean
	/** Turn on/off GFM autolink style */
	simplifiedAutoLink?: boolean
	/** Excludes trailing punctuation from links generated with autoLinking */
	excludeTrailingPunctuationFromURLs?: boolean
	/** Parse midword underscores as literal underscores */
	literalMidWordUnderscores?: boolean
	/** Parse midword asterisks as literal asterisks */
	literalMidWordAsterisks?: boolean
	/** Turn on/off strikethrough support */
	strikethrough?: boolean
	/** Turn on/off tables support */
	tables?: boolean
	/** Add an id to table headers */
	tablesHeaderId?: boolean
	/** Turn on/off GFM fenced code blocks support */
	ghCodeBlocks?: boolean
	/** Turn on/off GFM tasklist support */
	tasklists?: boolean
	/** Prevents weird effects in live previews due to incomplete input */
	smoothLivePreview?: boolean
	/** Try to smartly fix indentation problems */
	smartIndentationFix?: boolean
	/** Disable the automatic generation of header ids */
	disableForced4SpacesIndentedSublists?: boolean
	/** Turn on/off support for syntax highlighting of code blocks */
	simpleLineBreaks?: boolean
	/** Parse line breaks as <br> like GitHub does */
	requireSpaceBeforeHeadingText?: boolean
	/** Makes adding a space between # and the header text mandatory */
	ghMentions?: boolean
	/** Enables github @mentions */
	ghMentionsLink?: string
	/** Changes the link generated by @mentions */
	encodeEmails?: boolean
	/** Encode emails with hex entities */
	openLinksInNewWindow?: boolean
	/** Open all links in new windows */
	backslashEscapesHTMLTags?: boolean
	/** Support for HTML Tag escaping */
	customizedHeaderId?: boolean
	/** Enable custom header IDs using {#custom-id} syntax */
	tableHeaderId?: boolean
	/** Alias for tablesHeaderId */
	underline?: boolean
	/** Enable support for underline */
	completeHTMLDocument?: boolean
	/** Outputs a complete html document */
	splitAdjacentBlockquotes?: boolean
	/** Split adjacent blockquote blocks */
	[key: string]: any
}

const defaultOptions: ShowdownOptions = {
	omitExtraWLInCodeBlocks: false,
	noHeaderId: false,
	prefixHeaderId: false,
	rawPrefixHeaderId: false,
	ghCompatibleHeaderId: false,
	rawHeaderId: false,
	headerLevelStart: 0,
	parseImgDimensions: false,
	simplifiedAutoLink: true,
	excludeTrailingPunctuationFromURLs: false,
	literalMidWordUnderscores: false,
	literalMidWordAsterisks: false,
	strikethrough: false,
	tables: false,
	tablesHeaderId: false,
	ghCodeBlocks: true,
	tasklists: false,
	smoothLivePreview: false,
	smartIndentationFix: false,
	disableForced4SpacesIndentedSublists: false,
	simpleLineBreaks: false,
	requireSpaceBeforeHeadingText: false,
	ghMentions: false,
	encodeEmails: true,
	openLinksInNewWindow: true,
	backslashEscapesHTMLTags: false,
	customizedHeaderId: false,
	tableHeaderId: false,
	underline: false,
	completeHTMLDocument: false,
	splitAdjacentBlockquotes: false,
}

/**
 * Internal Showdown Converter interface (simplified)
 */
export interface ShowdownConverter {
	makeHtml: (text: string) => string
	makeMarkdown: (src: string, HTMLParser?: any) => string
}

/**
 * Internal Showdown object structure (simplified)
 */
interface ShowdownStatic {
	Converter: new (options?: ShowdownOptions) => ShowdownConverter
	helper: Record<string, any>
	subParser: ((name: string) => any) & ((name: string, func: any) => void)
	[key: string]: any
}

// Declare showdown globally for internal use
const parsers: Record<string, any> = {}

const showdown: ShowdownStatic = {
	helper: {} as any,
	subParser: ((name: string, func?: any) => {
		if (func !== undefined) {
			// Register a subparser
			parsers[name] = func
		} else {
			// Retrieve a subparser
			return parsers[name]
		}
	}) as any,
	Converter: undefined as any
}

/**
 * showdownjs helper functions
 */

/**
 * Check if var is string
 * @static
 * @param {string} a
 * @returns {boolean}
 */
showdown.helper.isString = function (a: any): boolean {
	return (typeof a === 'string' || a instanceof String)
}

/**
 * Check if var is a function
 * @static
 * @param {*} a
 * @returns {boolean}
 */
showdown.helper.isFunction = function (a: any): boolean {
	let getType = {}
	return a && getType.toString.call(a) === '[object Function]'
}

/**
 * isArray helper function
 * @static
 * @param {*} a
 * @returns {boolean}
 */
showdown.helper.isArray = function (a: any): boolean {
	return Array.isArray(a)
}

/**
 * Check if value is undefined
 * @static
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
 */
showdown.helper.isUndefined = function (value: any): boolean {
	return typeof value === 'undefined'
}

/**
 * ForEach helper function
 * Iterates over Arrays and Objects (own properties only)
 * @static
 * @param {*} obj
 * @param {Function} callback Accepts 3 params: 1. value, 2. key, 3. the original array/object
 */
showdown.helper.forEach = function (obj: any, callback: (item: any, index?: number | string, obj?: any) => void): void {
	// check if obj is defined
	if (showdown.helper.isUndefined(obj)) {
		throw new TypeError('obj param is required')
	}

	if (showdown.helper.isUndefined(callback)) {
		throw new TypeError('callback param is required')
	}

	if (!showdown.helper.isFunction(callback)) {
		throw new TypeError('callback param must be a function/closure')
	}

	if (typeof obj.forEach === 'function') {
		obj.forEach(callback)
	} else if (showdown.helper.isArray(obj)) {
		for (let i = 0; i < obj.length; i++) {
			callback(obj[i], i, obj)
		}
	} else if (typeof (obj) === 'object') {
		for (let prop in obj) {
			if (obj.hasOwnProperty(prop)) {
				callback(obj[prop], prop, obj)
			}
		}
	} else {
		throw new TypeError('obj does not seem to be an array or an iterable object')
	}
}

function escapeCharactersCallback(wholeMatch: string, m1: string): string {
	let charCodeToEscape = m1.charCodeAt(0)
	return `¨E${charCodeToEscape}E`
}

/**
 * Callback used to escape characters when passing through String.replace
 * @static
 * @param {string} wholeMatch
 * @param {string} m1
 * @returns {string}
 */
showdown.helper.escapeCharactersCallback = escapeCharactersCallback

/**
 * Escape characters in a string
 * @static
 * @param {string} text
 * @param {string} charsToEscape
 * @param {boolean} afterBackslash
 * @returns {XML|string|void|*}
 */
showdown.helper.escapeCharacters = function (text: string, charsToEscape: string, afterBackslash?: boolean): string {
	// First we have to escape the escape characters so that
	// we can build a character class out of them
	let regexString = `([${charsToEscape.replace(/([[\]\\])/g, '\\$1')}])`

	if (afterBackslash) {
		regexString = `\\\\${regexString}`
	}

	let regex = new RegExp(regexString, 'g')
	text = text.replace(regex, escapeCharactersCallback)

	return text
}

/**
 * Unescape HTML entities
 * @param txt
 * @returns {string}
 */
showdown.helper.unescapeHTMLEntities = function (txt: string): string {
	return txt
		.replace(/&quot;/g, '"')
		.replace(/&lt;/g, '<')
		.replace(/&gt;/g, '>')
		.replace(/&amp;/g, '&')
}

interface MatchPosition {
	left: { start: number, end: number }
	match: { start: number, end: number }
	right: { start: number, end: number }
	wholeMatch: { start: number, end: number }
}

let rgxFindMatchPos = function (str: string, left: string, right: string, flags?: string): MatchPosition[] {
	let f = flags || ''
	let g = f.includes('g')
	let x = new RegExp(`${left}|${right}`, `g${f.replace(/g/g, '')}`)
	let l = new RegExp(left, f.replace(/g/g, ''))
	let pos: MatchPosition[] = []
	let t: number = 0
	let s: number = 0
	let m: RegExpExecArray | null
	let start: number = 0
	let end: number = 0

	do {
		t = 0
		while ((m = x.exec(str))) {
			if (l.test(m[0])) {
				if (!(t++)) {
					s = x.lastIndex
					start = s - m[0].length
				}
			} else if (t) {
				if (!--t) {
					end = m.index + m[0].length
					let obj = {
						left: { start, end: s },
						match: { start: s, end: m.index },
						right: { start: m.index, end },
						wholeMatch: { start, end }
					}
					pos.push(obj)
					if (!g) {
						return pos
					}
				}
			}
		}
	} while (t && (x.lastIndex = s))

	return pos
}

/**
 * matchRecursiveRegExp
 * 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>").
 *
 * examples:
 * matchRecursiveRegExp("test", "\\(", "\\)")
 * returns: []
 * matchRecursiveRegExp("<t<<e>><s>>t<>", "<", ">", "g")
 * returns: ["t<<e>><s>", ""]
 * matchRecursiveRegExp("<div id=\"x\">test</div>", "<div\\b[^>]*>", "</div>", "gi")
 * returns: ["test"]
 */
showdown.helper.matchRecursiveRegExp = function (str: string, left: string, right: string, flags?: string): string[][] {
	let matchPos = rgxFindMatchPos (str, left, right, flags)
	let results: string[][] = []

	for (let i = 0; i < matchPos.length; ++i) {
		results.push([
			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)
		])
	}
	return results
}

/**
 *
 * @param {string} str
 * @param {string | Function} replacement
 * @param {string} left
 * @param {string} right
 * @param {string} flags
 * @returns {string}
 */
showdown.helper.replaceRecursiveRegExp = function (str: string, replacement: string | ((match: string, left: string, match2: string, right: string) => string), left: string, right: string, flags?: string): string {
	let replacementFn: (match: string, left: string, match2: string, right: string) => string
	if (!showdown.helper.isFunction(replacement)) {
		let repStr = replacement as string
		replacementFn = function () {
			return repStr
		}
	} else {
		replacementFn = replacement as (match: string, left: string, match2: string, right: string) => string
	}

	let matchPos = rgxFindMatchPos(str, left, right, flags)
	let finalStr = str
	let lng = matchPos.length

	if (lng > 0) {
		let bits = []
		if (matchPos[0].wholeMatch.start !== 0) {
			bits.push(str.slice(0, matchPos[0].wholeMatch.start))
		}
		for (let i = 0; i < lng; ++i) {
			bits.push(
				replacementFn(
					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)
				)
			)
			if (i < lng - 1) {
				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('')
	}
	return finalStr
}

/**
 * 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
 * @returns {number}
 * @throws InvalidArgumentError
 */
showdown.helper.regexIndexOf = function (str: string, regex: RegExp, fromIndex?: number): number {
	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'
	}
	let 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: string, index: number): [string, string] {
	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
 *
 * @param {string} mail
 * @returns {string}
 */
showdown.helper.encodeEmailAddress = function (mail: string): string {
	let encode = [
		function (ch: string): string {
			return `&#${ch.charCodeAt(0)};`
		},
		function (ch: string): string {
			return `&#x${ch.charCodeAt(0).toString(16)};`
		},
		function (ch: string): string {
			return ch
		}
	]

	mail = mail.replace(/./g, (ch) => {
		if (ch === '@') {
			// this *must* be encoded. I insist.
			ch = encode[Math.floor(Math.random() * 2)](ch)
		} else {
			let r = Math.random()
			// roughly 10% raw, 45% hex, 45% dec
			ch = (
				r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
			)
		}
		return ch
	})

	return mail
}

/**
 *
 * @param str
 * @param targetLength
 * @param padString
 * @returns {string}
 */
showdown.helper.padEnd = function padEnd(str: string, targetLength: number, padString?: string): string {
	/* jshint bitwise: false */

	targetLength = targetLength >> 0 // floor if number or convert non-number to 0;
	/* jshint bitwise: true */
	padString = String(padString || ' ')
	if (str.length > targetLength) {
		return String(str)
	} else {
		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)
	}
}

/**
 * POLYFILLS
 */
// use this instead of builtin is undefined for IE8 compatibility
if (typeof (console) === 'undefined') {
	(console as any) = {
		warn(msg: any) {
			alert(msg)
		},
		log(msg: any) {
			alert(msg)
		},
		error(msg: any) {
			throw msg
		}
	}
}

/**
 * Common regexes.
 * We declare some common regexes to improve performance
 */
showdown.helper.regexes = {
	asteriskDashAndColon: /([*_:~])/g
}

/**
 * Showdown Converter class
 * @class
 * @param {object} [converterOptions]
 * @returns {Converter}
 */
// @ts-ignore - Constructor function pattern
showdown.Converter = function (this: any, converterOptions?: ShowdownOptions) {
	let
		/**
		 * Options used by this converter
		 * @private
		 * @type {{}}
		 */
		options: any = {}

	_constructor()

	/**
	 * Converter constructor
	 * @private
	 */
	function _constructor() {
		converterOptions = converterOptions || {}
		if (typeof converterOptions !== 'object') {
			throw new TypeError(`Converter expects the passed parameter to be an object, but ${typeof converterOptions} was passed instead.`)
		}
		// Merge defaults with overrides
		options = { ...defaultOptions, ...converterOptions }
	}

	function rTrimInputText(text: string): string {
		let rsp = text.match(/^\s*/)?.[0].length || 0
		let rgx = new RegExp(`^\\s{0,${rsp}}`, 'gm')
		return text.replace(rgx, '')
	}

	/**
	 * Converts a markdown string into HTML
	 * @param {string} text
	 * @returns {*}
	 */
	this.makeHtml = function (text: string): string {
		// check if text is not falsy
		if (!text) {
			return text
		}

		let globals = {
			gHtmlBlocks: [],
			gHtmlMdBlocks: [],
			gHtmlSpans: [],
			gUrls: {},
			gTitles: {},
			gDimensions: {},
			gListLevel: 0,
			hashLinkCounts: {},
			converter: this,
			ghCodeBlocks: [],
			metadata: {
				parsed: {},
				raw: '',
				format: ''
			}
		}

		// This lets us use ¨ trema as an escape char to avoid md5 hashes
		// The choice of character is arbitrary; anything that isn't
		// magic in Markdown will work.
		text = text.replace(/¨/g, '¨T')

		// Replace $ with ¨D
		// RegExp interprets $ as a special character
		// when it's in a replacement string
		text = text.replace(/\$/g, '¨D')

		// Standardize line endings
		text = text.replace(/\r\n/g, '\n') // DOS to Unix
		text = text.replace(/\r/g, '\n') // Mac to Unix

		// Stardardize line spaces
		text = text.replace(/\u00A0/g, '&nbsp;')

		if (options.smartIndentationFix) {
			text = rTrimInputText(text)
		}

		// Make sure text begins and ends with a couple of newlines:
		text = `\n\n${text}\n\n`

		// detab
		text = showdown.subParser('detab')(text, options, globals)

		/**
		 * Strip any lines consisting only of spaces and tabs.
		 * This makes subsequent regexs easier to write, because we can
		 * match consecutive blank lines with /\n+/ instead of something
		 * contorted like /[ \t]*\n+/
		 */
		text = text.replace(/^[ \t]+$/gm, '')

		// run the sub parsers
		text = showdown.subParser('metadata')(text, options, globals)
		text = showdown.subParser('hashPreCodeTags')(text, options, globals)
		text = showdown.subParser('githubCodeBlocks')(text, options, globals)
		text = showdown.subParser('hashHTMLBlocks')(text, options, globals)
		text = showdown.subParser('hashCodeTags')(text, options, globals)
		text = showdown.subParser('stripLinkDefinitions')(text, options, globals)
		text = showdown.subParser('blockGamut')(text, options, globals)
		text = showdown.subParser('unhashHTMLSpans')(text, options, globals)
		text = showdown.subParser('unescapeSpecialChars')(text, options, globals)

		// attacklab: Restore dollar signs
		text = text.replace(/¨D/g, '$$')

		// attacklab: Restore tremas
		text = text.replace(/¨T/g, '¨')

		// render a complete html document instead of a partial if the option is enabled
		text = showdown.subParser('completeHTMLDocument')(text, options, globals)

		return text
	}

	/**
	 * Converts an HTML string into a markdown string
	 * @param src
	 * @param [HTMLParser] A WHATWG DOM and HTML parser, such as JSDOM. If none is supplied, window.document will be used.
	 * @returns {string}
	 */
	this.makeMarkdown = this.makeMd = function (src: string, HTMLParser?: any): string {
		// replace \r\n with \n
		src = src.replace(/\r\n/g, '\n')
		src = src.replace(/\r/g, '\n') // old macs

		// due to an edge case, we need to find this: > <
		// to prevent removing of non silent white spaces
		// ex: <em>this is</em> <strong>sparta</strong>
		src = src.replace(/>[ \t]+</, '>¨NBSP;<')

		if (!HTMLParser) {
			if (typeof window !== 'undefined' && window.document) {
				HTMLParser = window.document
			} else {
				throw new Error('HTMLParser is undefined. If in a webworker or nodejs environment, you need to provide a WHATWG DOM and HTML such as JSDOM')
			}
		}

		let doc = HTMLParser.createElement('div')
		doc.innerHTML = src

		let globals = {
			preList: substitutePreCodeTags(doc)
		}

		// remove all newlines and collapse spaces
		clean(doc)

		// some stuff, like accidental reference links must now be escaped
		// TODO
		// doc.innerHTML = doc.innerHTML.replace(/\[[\S\t ]]/);

		let nodes = doc.childNodes
		let mdDoc = ''

		for (let i = 0; i < nodes.length; i++) {
			mdDoc += showdown.subParser('makeMarkdown.node')(nodes[i], globals)
		}

		function clean(node: any): void {
			for (let n = 0; n < node.childNodes.length; ++n) {
				let child = node.childNodes[n]
				if (child.nodeType === 3) {
					if (!/\S/.test(child.nodeValue) && !/^ +$/.test(child.nodeValue)) {
						node.removeChild(child)
						--n
					} else {
						child.nodeValue = child.nodeValue.split('\n').join(' ')
						child.nodeValue = child.nodeValue.replace(/(\s)+/g, '$1')
					}
				} else if (child.nodeType === 1) {
					clean(child)
				}
			}
		}

		// find all pre tags and replace contents with placeholder
		// we need this so that we can remove all indentation from html
		// to ease up parsing
		function substitutePreCodeTags(doc: any): string[] {
			let pres = doc.querySelectorAll('pre')
			let presPH = []

			for (let i = 0; i < pres.length; ++i) {
				if (pres[i].childElementCount === 1 && pres[i].firstChild.tagName.toLowerCase() === 'code') {
					let content = pres[i].firstChild.innerHTML.trim()
					let language = pres[i].firstChild.getAttribute('data-language') || ''

					// if data-language attribute is not defined, then we look for class language-*
					if (language === '') {
						let classes = pres[i].firstChild.className.split(' ')
						for (let c = 0; c < classes.length; ++c) {
							let matches = classes[c].match(/^language-(.+)$/)
							if (matches !== null) {
								language = matches[1]
								break
							}
						}
					}

					// unescape html entities in content
					content = showdown.helper.unescapeHTMLEntities(content)

					presPH.push(content)
					pres[i].outerHTML = `<precode language="${language}" precodenum="${i.toString()}"></precode>`
				} else {
					presPH.push(pres[i].innerHTML)
					pres[i].innerHTML = ''
					pres[i].setAttribute('prenum', i.toString())
				}
			}
			return presPH
		}

		return mdDoc
	}
}

/**
 * Turn Markdown link shortcuts into XHTML <a> tags.
 */
showdown.subParser('anchors', (text: string, options: any, globals: any): string => {
	let writeAnchorTag = function (wholeMatch: string, linkText: string, linkId: string, url: string, m5: string, m6: string, title?: string): string {
		if (showdown.helper.isUndefined(title)) {
			title = ''
		}
		linkId = linkId.toLowerCase()

		// Special case for explicit empty url
		if (wholeMatch.search(/\(<?\s*>? ?(['"].*['"])?\)$/m) > -1) {
			url = ''
		} else if (!url) {
			if (!linkId) {
				// lower-case and turn embedded newlines into spaces
				linkId = linkText.toLowerCase().replace(/ ?\n/g, ' ')
			}
			url = `#${linkId}`

			if (!showdown.helper.isUndefined(globals.gUrls[linkId])) {
				url = globals.gUrls[linkId]
				if (!showdown.helper.isUndefined(globals.gTitles[linkId])) {
					title = globals.gTitles[linkId]
				}
			} else {
				return wholeMatch
			}
		}

		// url = showdown.helper.escapeCharacters(url, '*_', false); // replaced line to improve performance
		url = url.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback)

		let result = `<a href="${url}"`

		if (title !== '' && title !== null && title !== undefined) {
			title = title.replace(/"/g, '&quot;')
			// title = showdown.helper.escapeCharacters(title, '*_', false); // replaced line to improve performance
			title = title.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback)
			result += ` title="${title}"`
		}

		// optionLinksInNewWindow only applies
		// to external links. Hash links (#) open in same page
		if (options.openLinksInNewWindow && !/^#/.test(url)) {
			// escaped _
			result += ' rel="noopener noreferrer" target="¨E95Eblank"'
		}

		result += `>${linkText}</a>`

		return result
	}

	// First, handle reference-style links: [link text] [id]
	text = text.replace(/\[((?:\[[^\]]*\]|[^[\]])*)\] ?(?:\n *)?\[(.*?)\]()()()()/g, writeAnchorTag)

	// Next, inline-style links: [link text](url "optional title")
	// cases with crazy urls like ./image/cat1).png
	text = text.replace(/\[((?:\[[^\]]*\]|[^[\]])*)\]()[ \t]*\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g, writeAnchorTag)

	// normal cases
	text = text.replace(/\[((?:\[[^\]]*\]|[^[\]])*)\]()[ \t]*\([ \t]?<?(\S+?(?:\(\S*?\)\S*?)?)>?(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g, writeAnchorTag)

	// handle reference-style shortcuts: [link text]
	// These must come last in case you've also got [link test][1]
	// or [link test](/foo)
	text = text.replace(/\[([^[\]]+)\]()()()()()/g, writeAnchorTag)

	// Lastly handle GithubMentions if option is enabled
	if (options.ghMentions) {
		text = text.replace(/(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d.-]+?[a-z\d]+)*))/gim, (wm, st, escape, mentions, username) => {
			if (escape === '\\') {
				return st + mentions
			}

			// check if options.ghMentionsLink is a string
			if (!showdown.helper.isString(options.ghMentionsLink)) {
				throw new TypeError('ghMentionsLink option must be a string')
			}
			let lnk = options.ghMentionsLink.replace(/\{u\}/g, username)
			let target = ''
			if (options.openLinksInNewWindow) {
				target = ' rel="noopener noreferrer" target="¨E95Eblank"'
			}
			return `${st}<a href="${lnk}"${target}>${mentions}</a>`
		})
	}

	return text
})

// url allowed chars [a-z\d_.~:/?#[]@!$&'()*+,;=-]

let simpleURLRegex = /([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+?\.[^'">\s]+?)()(\1)?(?=\s|$)(?!["<>])/gi
let simpleURLRegex2 = /([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?,()[\]])?(\1)?(?=\s|$)(?!["<>])/gi
let delimUrlRegex = /()<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)()>()/gi
let simpleMailRegex = /(^|\s)(?:mailto:)?([\w!#$%&'*+-/=?^`{|}~]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim
let delimMailRegex = /<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi

let replaceLink = function (options: any) {
	return function (wm: string, leadingMagicChars: string, link: string, m2: string, m3: string, trailingPunctuation: string, trailingMagicChars: string): string {
		link = link.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback)
		let lnkTxt = link
		let append = ''
		let target = ''
		let lmc = leadingMagicChars || ''
		let tmc = trailingMagicChars || ''
		if (/^www\./i.test(link)) {
			link = link.replace(/^www\./i, 'http://www.')
		}
		if (options.excludeTrailingPunctuationFromURLs && trailingPunctuation) {
			append = trailingPunctuation
		}
		if (options.openLinksInNewWindow) {
			target = ' rel="noopener noreferrer" target="¨E95Eblank"'
		}
		return `${lmc}<a href="${link}"${target}>${lnkTxt}</a>${append}${tmc}`
	}
}

let replaceMail = function (options: any, globals: any) {
	return function (wholeMatch: string, b: string, mail: string): string {
		let href = 'mailto:'
		b = b || ''
		mail = showdown.subParser('unescapeSpecialChars')(mail, options, globals)
		if (options.encodeEmails) {
			href = showdown.helper.encodeEmailAddress(href + mail)
			mail = showdown.helper.encodeEmailAddress(mail)
		} else {
			href = href + mail
		}
		return `${b}<a href="${href}">${mail}</a>`
	}
}

showdown.subParser('autoLinks', (text: string, options: any, globals: any): string => {
	text = text.replace(delimUrlRegex, replaceLink(options))
	text = text.replace(delimMailRegex, replaceMail(options, globals))

	return text
})

showdown.subParser('simplifiedAutoLinks', (text: string, options: any, globals: any): string => {
	if (!options.simplifiedAutoLink) {
		return text
	}

	if (options.excludeTrailingPunctuationFromURLs) {
		text = text.replace(simpleURLRegex2, replaceLink(options))
	} else {
		text = text.replace(simpleURLRegex, replaceLink(options))
	}
	text = text.replace(simpleMailRegex, replaceMail(options, globals))

	return text
})

/**
 * These are all the transformations that form block-level
 * tags like paragraphs, headers, and list items.
 */
showdown.subParser('blockGamut', (text: string, options: any, globals: any): string => {
	// we parse blockquotes first so that we can have headings and hrs
	// inside blockquotes
	text = showdown.subParser('blockQuotes')(text, options, globals)
	text = showdown.subParser('headers')(text, options, globals)

	// Do Horizontal Rules:
	text = showdown.subParser('horizontalRule')(text, options, globals)

	text = showdown.subParser('lists')(text, options, globals)
	text = showdown.subParser('codeBlocks')(text, options, globals)
	text = showdown.subParser('tables')(text, options, globals)

	// We already ran _HashHTMLBlocks() before, in Markdown(), but that
	// was to escape raw HTML in the original Markdown source. This time,
	// we're escaping the markup we've just created, so that we don't wrap
	// <p> tags around block-level tags.
	text = showdown.subParser('hashHTMLBlocks')(text, options, globals)
	text = showdown.subParser('paragraphs')(text, options, globals)

	return text
})

showdown.subParser('blockQuotes', (text: string, options: any, globals: any): string => {
	// add a couple extra lines after the text and endtext mark
	text = `${text}\n\n`

	let rgx = /(^ {0,3}>.+\n(.+\n)*\n*)+/gm

	if (options.splitAdjacentBlockquotes) {
		rgx = /^ {0,3}>[\s\S]*?\n\n/gm
	}

	text = text.replace(rgx, (bq) => {
		// attacklab: hack around Konqueror 3.5.4 bug:
		// "----------bug".replace(/^-/g,"") == "bug"
		bq = bq.replace(/^[ \t]*>[ \t]?/gm, '') // trim one level of quoting

		// attacklab: clean up hack
		bq = bq.replace(/¨0/g, '')

		bq = bq.replace(/^[ \t]+$/gm, '') // trim whitespace-only lines
		bq = showdown.subParser('githubCodeBlocks')(bq, options, globals)
		bq = showdown.subParser('blockGamut')(bq, options, globals) // recurse

		bq = bq.replace(/(^|\n)/g, '$1  ')
		// These leading spaces screw with <pre> content, so we need to fix that:
		bq = bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/g, (wholeMatch, m1) => {
			let pre = m1
			// attacklab: hack around Konqueror 3.5.4 bug:
			pre = pre.replace(/^ {2}/gm, '¨0')
			pre = pre.replace(/¨0/g, '')
			return pre
		})

		return showdown.subParser('hashBlock')(`<blockquote>\n${bq}\n</blockquote>`, options, globals)
	})

	return text
})

/**
 * Process Markdown `<pre><code>` blocks.
 */
showdown.subParser('codeBlocks', (text: string, options: any, globals: any): string => {
	// sentinel workarounds for lack of \A and \Z, safari\khtml bug
	text += '¨0'

	let pattern = /(?:\n\n|^)((?:(?: {4}|\t).*\n+)+)(\n* {0,3}[^ \t\n]|(?=¨0))/g
	text = text.replace(pattern, (wholeMatch, m1, m2) => {
		let codeblock = m1
		let nextChar = m2
		let end = '\n'

		codeblock = showdown.subParser('outdent')(codeblock, options, globals)
		codeblock = showdown.subParser('encodeCode')(codeblock, options, globals)
		codeblock = showdown.subParser('detab')(codeblock, options, globals)
		codeblock = codeblock.replace(/^\n+/g, '') // trim leading newlines
		codeblock = codeblock.replace(/\n+$/g, '') // trim trailing newlines

		if (options.omitExtraWLInCodeBlocks) {
			end = ''
		}

		codeblock = `<pre><code>${codeblock}${end}</code></pre>`

		return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar
	})

	// strip sentinel
	text = text.replace(/¨0/, '')

	return text
})

/**
 *
 *   Backtick quotes are used for <code></code> spans.
 *
 *   You can use multiple backticks as the delimiters if you want to
 *     include literal backticks in the code span. So, this input:
 *
 *         Just type ``foo `bar` baz`` at the prompt.
 *
 *       Will translate to:
 *
 *         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
 *
 *    There's no arbitrary limit to the number of backticks you
 *    can use as delimters. If you need three consecutive backticks
 *    in your code, use four for delimiters, etc.
 *
 *  You can use spaces to get literal backticks at the edges:
 *
 *         ... type `` `bar` `` ...
 *
 *       Turns to:
 *
 *         ... type <code>`bar`</code> ...
 */
showdown.subParser('codeSpans', (text: string, options: any, globals: any): string => {
	if (typeof (text) === 'undefined') {
		text = ''
	}
	text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, (wholeMatch, m1, m2, m3) => {
		let c = m3
		c = c.replace(/^([ \t]*)/g, '')	// leading whitespace
		c = c.replace(/[ \t]*$/g, '')	// trailing whitespace
		c = showdown.subParser('encodeCode')(c, options, globals)
		c = `${m1}<code>${c}</code>`
		c = showdown.subParser('hashHTMLSpans')(c, options, globals)
		return c
	})

	return text
})

/**
 * Create a full HTML document from the processed markdown
 */
showdown.subParser('completeHTMLDocument', (text: string, options: any, globals: any): string => {
	if (!options.completeHTMLDocument) {
		return text
	}

	let doctype = 'html'
	let doctypeParsed = '<!DOCTYPE HTML>\n'
	let title = ''
	let charset = '<meta charset="utf-8">\n'
	let lang = ''
	let metadata = ''

	if (typeof globals.metadata.parsed.doctype !== 'undefined') {
		doctypeParsed = `<!DOCTYPE ${globals.metadata.parsed.doctype}>\n`
		doctype = globals.metadata.parsed.doctype.toString().toLowerCase()
		if (doctype === 'html' || doctype === 'html5') {
			charset = '<meta charset="utf-8">'
		}
	}

	for (let meta in globals.metadata.parsed) {
		if (globals.metadata.parsed.hasOwnProperty(meta)) {
			switch (meta.toLowerCase()) {
				case 'doctype':
					break

				case 'title':
					title = `<title>${globals.metadata.parsed.title}</title>\n`
					break

				case 'charset':
					if (doctype === 'html' || doctype === 'html5') {
						charset = `<meta charset="${globals.metadata.parsed.charset}">\n`
					} else {
						charset = `<meta name="charset" content="${globals.metadata.parsed.charset}">\n`
					}
					break

				case 'language':
				case 'lang':
					lang = ` lang="${globals.metadata.parsed[meta]}"`
					metadata += `<meta name="${meta}" content="${globals.metadata.parsed[meta]}">\n`
					break

				default:
					metadata += `<meta name="${meta}" content="${globals.metadata.parsed[meta]}">\n`
			}
		}
	}

	text = `${doctypeParsed}<html${lang}>\n<head>\n${title}${charset}${metadata}</head>\n<body>\n${text.trim()}\n</body>\n</html>`

	return text
})

/**
 * Convert all tabs to spaces
 */
showdown.subParser('detab', (text: string, options: any, globals: any): string => {
	// expand first n-1 tabs
	text = text.replace(/\t(?=\t)/g, '    ') // g_tab_width

	// replace the nth with two sentinels
	text = text.replace(/\t/g, '¨A¨B')

	// use the sentinel to anchor our regex so it doesn't explode
	text = text.replace(/¨B(.+?)¨A/g, (wholeMatch, m1) => {
		let leadingText = m1
		let numSpaces = 4 - leadingText.length % 4 // g_tab_width

		// there *must* be a better way to do this:
		for (let i = 0; i < numSpaces; i++) {
			leadingText += ' '
		}

		return leadingText
	})

	// clean up sentinels
	text = text.replace(/¨A/g, '    ') // g_tab_width
	text = text.replace(/¨B/g, '')

	return text
})

showdown.subParser('ellipsis', (text: string, options: any, globals: any): string => {
	if (!options.ellipsis) {
		return text
	}

	text = text.replace(/\.\.\./g, '…')

	return text
})

/**
 * Smart processing for ampersands and angle brackets that need to be encoded.
 */
showdown.subParser('encodeAmpsAndAngles', (text: string, options: any, globals: any): string => {
	// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
	// http://bumppo.net/projects/amputator/
	text = text.replace(/&(?!#?x?(?:[0-9a-f]+|\w+);)/gi, '&amp;')

	// Encode naked <'s
	text = text.replace(/<(?![a-z/?$!])/gi, '&lt;')

	// Encode <
	text = text.replace(/</g, '&lt;')

	// Encode >
	text = text.replace(/>/g, '&gt;')

	return text
})

/**
 * Returns the string, with after processing the following backslash escape sequences.
 *
 * attacklab: The polite way to do this is with the new escapeCharacters() function:
 *
 *    text = escapeCharacters(text,"\\",true);
 *    text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
 *
 * ...but we're sidestepping its use of the (slow) RegExp constructor
 * as an optimization for Firefox.  This function gets called a LOT.
 */
showdown.subParser('encodeBackslashEscapes', (text: string, options: any, globals: any): string => {
	text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback)
	text = text.replace(/\\([`*_{}[\]()>#+.!~=|:-])/g, showdown.helper.escapeCharactersCallback)

	return text
})

/**
 * Encode/escape certain characters inside Markdown code runs.
 * The point is that in code, these characters are literals,
 * and lose their special Markdown meanings.
 */
showdown.subParser('encodeCode', (text: string, options: any, globals: any): string => {
	// Encode all ampersands; HTML entities are not
	// entities within a Markdown code span.
	text = text
		.replace(/&/g, '&amp;')
	// Do the angle bracket song and dance:
		.replace(/</g, '&lt;')
		.replace(/>/g, '&gt;')
	// Now, escape characters that are magic in Markdown:
		.replace(/([*_{}[\]\\=~-])/g, showdown.helper.escapeCharactersCallback)

	return text
})

/**
 * Within tags -- meaning between < and > -- encode [\ ` * _ ~ =] so they
 * don't conflict with their use in Markdown for code, italics and strong.
 */
showdown.subParser('escapeSpecialCharsWithinTagAttributes', (text: string, options: any, globals: any): string => {
	// Build a regex to find HTML tags.
	let tags = /<\/?[\w:-]+(?:\s[\s\S]+?)?>/g
	let comments = /<!(--(?:[^>-]|-[^>])(?:[^-]|-[^-])*--)>/g

	text = text.replace(tags, (wholeMatch) => {
		return wholeMatch
			.replace(/(.)<\/?code>(?=.)/g, '$1`')
			.replace(/([\\`*_~=|])/g, showdown.helper.escapeCharactersCallback)
	})

	text = text.replace(comments, (wholeMatch) => {
		return wholeMatch
			.replace(/([\\`*_~=|])/g, showdown.helper.escapeCharactersCallback)
	})

	return text
})

/**
 * Handle github codeblocks prior to running HashHTML so that
 * HTML contained within the codeblock gets escaped properly
 * Example:
 * ```ruby
 *     def hello_world(x)
 *       puts "Hello, #{x}"
 *     end
 * ```
 */
showdown.subParser('githubCodeBlocks', (text: string, options: any, globals: any): string => {
	// early exit if option is not enabled
	if (!options.ghCodeBlocks) {
		return text
	}

	text += '¨0'

	text = text.replace(/(?:^|\n) {0,3}(`{3,}|~{3,}) *([^\s`~]*)\n([\s\S]*?)\n {0,3}\1/g, (wholeMatch, delim, language, codeblock) => {
		let end = (options.omitExtraWLInCodeBlocks) ? '' : '\n'

		// First parse the github code block
		codeblock = showdown.subParser('encodeCode')(codeblock, options, globals)
		codeblock = showdown.subParser('detab')(codeblock, options, globals)
		codeblock = codeblock.replace(/^\n+/g, '') // trim leading newlines
		codeblock = codeblock.replace(/\n+$/g, '') // trim trailing whitespace

		codeblock = `<pre><code${language ? ` class="${language} language-${language}"` : ''}>${codeblock}${end}</code></pre>`

		codeblock = showdown.subParser('hashBlock')(codeblock, options, globals)

		// Since GHCodeblocks can be false positives, we need to
		// store the primitive text and the parsed text in a global var,
		// and then return a token
		return `\n\n¨G${globals.ghCodeBlocks.push({ text: wholeMatch, codeblock }) - 1}G\n\n`
	})

	// attacklab: strip sentinel
	text = text.replace(/¨0/, '')

	return text
})

showdown.subParser('hashBlock', (text: string, options: any, globals: any): string => {
	text = text.replace(/(^\n+|\n+$)/g, '')
	text = `\n\n¨K${globals.gHtmlBlocks.push(text) - 1}K\n\n`
	return text
})

/**
 * Hash and escape <code> elements that should not be parsed as markdown
 */
showdown.subParser('hashCodeTags', (text: string, options: any, globals: any): string => {
	let repFunc = function (wholeMatch: string, match: string, left: string, right: string): string {
		let codeblock = left + showdown.subParser('encodeCode')(match, options, globals) + right
		return `¨C${globals.gHtmlSpans.push(codeblock) - 1}C`
	}

	// Hash naked <code>
	text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '<code\\b[^>]*>', '</code>', 'gim')

	return text
})

showdown.subParser('hashElement', (text: string, options: any, globals: any): ((wholeMatch: string, m1: string) => string) => {
	return function (wholeMatch: string, m1: string): string {
		let blockText = m1

		// Undo double lines
		blockText = blockText.replace(/\n\n/g, '\n')
		blockText = blockText.replace(/^\n/, '')

		// strip trailing blank lines
		blockText = blockText.replace(/\n+$/g, '')

		// Replace the element text with a marker ("¨KxK" where x is its key)
		blockText = `\n\n¨K${globals.gHtmlBlocks.push(blockText) - 1}K\n\n`

		return blockText
	}
})

showdown.subParser('hashHTMLBlocks', (text: string, options: any, globals: any): string => {
	let blockTags = [
		'pre',
		'div',
		'h1',
		'h2',
		'h3',
		'h4',
		'h5',
		'h6',
		'blockquote',
		'table',
		'dl',
		'ol',
		'ul',
		'script',
		'noscript',
		'form',
		'fieldset',
		'iframe',
		'math',
		'style',
		'section',
		'header',
		'footer',
		'nav',
		'article',
		'aside',
		'address',
		'audio',
		'canvas',
		'figure',
		'hgroup',
		'output',
		'video',
		'p'
	]
	let repFunc = function (wholeMatch: string, match: string, left: string, right: string): string {
		let txt = wholeMatch
		// check if this html element is marked as markdown
		// if so, it's contents should be parsed as markdown
		if (left.search(/\bmarkdown\b/) !== -1) {
			txt = left + globals.converter.makeHtml(match) + right
		}
		return `\n\n¨K${globals.gHtmlBlocks.push(txt) - 1}K\n\n`
	}

	if (options.backslashEscapesHTMLTags) {
		// encode backslash escaped HTML tags
		text = text.replace(/\\<([^>]+)>/g, (wm, inside) => {
			return `&lt;${inside}&gt;`
		})
	}

	// hash HTML Blocks
	for (let i = 0; i < blockTags.length; ++i) {
		var opTagPos
		let rgx1 = new RegExp(`^ {0,3}(<${blockTags[i]}\\b[^>]*>)`, 'im')
		let patLeft = `<${blockTags[i]}\\b[^>]*>`
		let patRight = `</${blockTags[i]}>`
		// 1. Look for the first position of the first opening HTML tag in the text
		while ((opTagPos = showdown.helper.regexIndexOf(text, rgx1)) !== -1) {
			// if the HTML tag is \ escaped, we need to escape it and break

			// 2. Split the text in that position
			let subTexts = showdown.helper.splitAtIndex(text, opTagPos)
			// 3. Match recursively
			let newSubText1 = showdown.helper.replaceRecursiveRegExp(subTexts[1], repFunc, patLeft, patRight, 'im')

			// prevent an infinite loop
			if (newSubText1 === subTexts[1]) {
				break
			}
			text = subTexts[0].concat(newSubText1)
		}
	}
	// HR SPECIAL CASE
	text = text.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, showdown.subParser('hashElement')(text, options, globals))

	// Special case for standalone HTML comments
	text = showdown.helper.replaceRecursiveRegExp(text, (txt: string): string => {
		return `\n\n¨K${globals.gHtmlBlocks.push(txt) - 1}K\n\n`
	}, '^ {0,3}<!--', '-->', 'gm')

	// PHP and ASP-style processor instructions (<?...?> and <%...%>)
	text = text.replace(/\n\n( {0,3}<([?%])[^\r]*?\2>[ \t]*(?=\n{2,}))/g, showdown.subParser('hashElement')(text, options, globals))

	return text
})

/**
 * Hash span elements that should not be parsed as markdown
 */
showdown.subParser('hashHTMLSpans', (text: string, options: any, globals: any): string => {
	function hashHTMLSpan(html: string): string {
		return `¨C${globals.gHtmlSpans.push(html) - 1}C`
	}

	// Hash Self Closing tags
	text = text.replace(/<[^>]+?\/>/g, (wm: string): string => {
		return hashHTMLSpan(wm)
	})

	// Hash tags without properties
	text = text.replace(/<([^>]+)>[\s\S]*?<\/\1>/g, (wm) => {
		return hashHTMLSpan(wm)
	})

	// Hash tags with properties
	text = text.replace(/<([^>]+?)\s[^>]+>[\s\S]*?<\/\1>/g, (wm) => {
		return hashHTMLSpan(wm)
	})

	// Hash self closing tags without />
	text = text.replace(/<[^>]+>/g, (wm) => {
		return hashHTMLSpan(wm)
	})

	/* showdown.helper.matchRecursiveRegExp(text, '<code\\b[^>]*>', '</code>', 'gi'); */

	return text
})

/**
 * Unhash HTML spans
 */
showdown.subParser('unhashHTMLSpans', (text: string, options: any, globals: any): string => {
	for (let i = 0; i < globals.gHtmlSpans.length; ++i) {
		let repText = globals.gHtmlSpans[i]
		// limiter to prevent infinite loop (assume 10 as limit for recurse)
		let limit = 0

		while (/¨C(\d+)C/.test(repText)) {
			let num = RegExp.$1
			repText = repText.replace(`¨C${num}C`, globals.gHtmlSpans[num])
			if (limit === 10) {
				console.error('maximum nesting of 10 spans reached!!!')
				break
			}
			++limit
		}
		text = text.replace(`¨C${i}C`, repText)
	}

	return text
})

/**
 * Hash and escape <pre><code> elements that should not be parsed as markdown
 */
showdown.subParser('hashPreCodeTags', (text: string, options: any, globals: any): string => {
	let repFunc = function (wholeMatch: string, match: string, left: string, right: string): string {
		// encode html entities
		let codeblock = left + showdown.subParser('encodeCode')(match, options, globals) + right
		return `\n\n¨G${globals.ghCodeBlocks.push({ text: wholeMatch, codeblock }) - 1}G\n\n`
	}

	// Hash <pre><code>
	text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^ {0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>', '^ {0,3}</code>\\s*</pre>', 'gim')

	return text
})

showdown.subParser('headers', (text: string, options: any, globals: any): string => {
	let headerLevelStart = (isNaN(Number.parseInt(options.headerLevelStart))) ? 1 : Number.parseInt(options.headerLevelStart)

	// Set text-style headers:
	//	Header 1
	//	========
	//
	//	Header 2
	//	--------
	//
	let setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm
	let setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm

	text = text.replace(setextRegexH1, (wholeMatch, m1) => {
		let spanGamut = showdown.subParser('spanGamut')(m1, options, globals)
		let hID = (options.noHeaderId) ? '' : ` id="${headerId(m1)}"`
		let hLevel = headerLevelStart
		let hashBlock = `<h${hLevel}${hID}>${spanGamut}</h${hLevel}>`
		return showdown.subParser('hashBlock')(hashBlock, options, globals)
	})

	text = text.replace(setextRegexH2, (matchFound, m1) => {
		let spanGamut = showdown.subParser('spanGamut')(m1, options, globals)
		let hID = (options.noHeaderId) ? '' : ` id="${headerId(m1)}"`
		let hLevel = headerLevelStart + 1
		let hashBlock = `<h${hLevel}${hID}>${spanGamut}</h${hLevel}>`
		return showdown.subParser('hashBlock')(hashBlock, options, globals)
	})

	// atx-style headers:
	//  # Header 1
	//  ## Header 2
	//  ## Header 2 with closing hashes ##
	//  ...
	//  ###### Header 6
	//
	let atxStyle = (options.requireSpaceBeforeHeadingText) ? /^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm : /^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm

	text = text.replace(atxStyle, (_wholeMatch, m1, m2) => {
		let hText = m2
		if (options.customizedHeaderId) {
			hText = m2.replace(/\s?\{([^{]+?)\}\s*$/, '')
		}

		let span = showdown.subParser('spanGamut')(hText, options, globals)
		let hID = (options.noHeaderId) ? '' : ` id="${headerId(m2)}"`
		let hLevel = headerLevelStart - 1 + m1.length
		let header = `<h${hLevel}${hID}>${span}</h${hLevel}>`

		return showdown.subParser('hashBlock')(header, options, globals)
	})

	function headerId(m: string): string {
		let title: string = ''
		let prefix: string

		// It is separate from other options to allow combining prefix and customized
		if (options.customizedHeaderId) {
			let match = m.match(/\{([^{]+?)\}\s*$/)
			if (match && match[1]) {
				m = match[1]
			}
		}

		title = m

		// Prefix id to prevent causing inadvertent pre-existing style matches.
		if (showdown.helper.isString(options.prefixHeaderId)) {
			prefix = options.prefixHeaderId
		} else if (options.prefixHeaderId === true) {
			prefix = 'section-'
		} else {
			prefix = ''
		}

		if (!options.rawPrefixHeaderId) {
			title = prefix + title
		}

		if (options.ghCompatibleHeaderId) {
			title = title
				.replace(/ /g, '-')
			// replace previously escaped chars (&, ¨ and $)
				.replace(/&amp;/g, '')
				.replace(/¨T/g, '')
				.replace(/¨D/g, '')
			// replace rest of the chars (&~$ are repeated as they might have been escaped)
			// borrowed from github's redcarpet (some they should produce similar results)
				.replace(/[&+$,/:;=?@"#{}|^¨~[\]`\\*)(%.!'<>]/g, '')
				.toLowerCase()
		} else if (options.rawHeaderId) {
			title = title
				.replace(/ /g, '-')
			// replace previously escaped chars (&, ¨ and $)
				.replace(/&amp;/g, '&')
				.replace(/¨T/g, '¨')
				.replace(/¨D/g, '$')
			// replace " and '
				.replace(/["']/g, '-')
				.toLowerCase()
		} else {
			title = title
				.replace(/\W/g, '')
				.toLowerCase()
		}

		if (options.rawPrefixHeaderId) {
			title = prefix + title
		}

		if (globals.hashLinkCounts[title]) {
			title = `${title}-${globals.hashLinkCounts[title]++}`
		} else {
			globals.hashLinkCounts[title] = 1
		}
		return title
	}

	return text
})

/**
 * Turn Markdown link shortcuts into XHTML <a> tags.
 */
showdown.subParser('horizontalRule', (text: string, options: any, globals: any): string => {
	let key = showdown.subParser('hashBlock')('<hr />', options, globals)
	text = text.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm, key)
	text = text.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm, key)
	text = text.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm, key)

	return text
})

/**
 * Turn Markdown image shortcuts into <img> tags.
 */
showdown.subParser('images', (text: string, options: any, globals: any): string => {
	let inlineRegExp = /!\[([^\]]*)\][ \t]*()\([ \t]?<?(\S+?(?:\(\S*?\)\S*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g
	let crazyRegExp = /!\[([^\]]*)\][ \t]*()\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g
	let base64RegExp = /!\[([^\]]*)\][ \t]*()\([ \t]?<?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g
	let referenceRegExp = /!\[([^\]]*)\] ?(?:\n *)?\[([\s\S]*?)\]()()()()()/g
	let refShortcutRegExp = /!\[([^[\]]+)\]()()()()()/g

	function writeImageTagBase64(wholeMatch: string, altText: string, linkId: string, url: string, width: string, height: string, m5: string, title: string): string {
		url = url.replace(/\s/g, '')
		return writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title)
	}

	function writeImageTag(wholeMatch: string, altText: string, linkId: string, url: string, width: string, height: string, m5: string, title: string): string {
		let { gUrls } = globals
		let { gTitles } = globals
		let gDims = globals.gDimensions

		linkId = linkId.toLowerCase()

		if (!title) {
			title = ''
		}
		// Special case for explicit empty url
		if (wholeMatch.search(/\(<?\s*>? ?(['"].*['"])?\)$/m) > -1) {
			url = ''
		} else if (url === '' || url === null) {
			if (linkId === '' || linkId === null) {
				// lower-case and turn embedded newlines into spaces
				linkId = altText.toLowerCase().replace(/ ?\n/g, ' ')
			}
			url = `#${linkId}`

			if (!showdown.helper.isUndefined(gUrls[linkId])) {
				url = gUrls[linkId]
				if (!showdown.helper.isUndefined(gTitles[linkId])) {
					title = gTitles[linkId]
				}
				if (!showdown.helper.isUndefined(gDims[linkId])) {
					width = gDims[linkId].width
					height = gDims[linkId].height
				}
			} else {
				return wholeMatch
			}
		}

		altText = altText
			.replace(/"/g, '&quot;')
		// altText = showdown.helper.escapeCharacters(altText, '*_', false);
			.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback)
		// url = showdown.helper.escapeCharacters(url, '*_', false);
		url = url.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback)
		let result = `<img src="${url}" alt="${altText}"`

		if (title && showdown.helper.isString(title)) {
			title = title
				.replace(/"/g, '&quot;')
			// title = showdown.helper.escapeCharacters(title, '*_', false);
				.replace(showdown.helper.regexes.asteriskDashAndColon, showdown.helper.escapeCharactersCallback)
			result += ` title="${title}"`
		}

		if (width && height) {
			width = (width === '*') ? 'auto' : width
			height = (height === '*') ? 'auto' : height

			result += ` width="${width}"`
			result += ` height="${height}"`
		}

		result += ' />'

		return result
	}

	// First, handle reference-style labeled images: ![alt text][id]
	text = text.replace(referenceRegExp, writeImageTag)

	// Next, handle inline images:  ![alt text](url =<width>x<height> "optional title")

	// base64 encoded images
	text = text.replace(base64RegExp, writeImageTagBase64)

	// cases with crazy urls like ./image/cat1).png
	text = text.replace(crazyRegExp, writeImageTag)

	// normal cases
	text = text.replace(inlineRegExp, writeImageTag)

	// handle reference-style shortcuts: ![img text]
	text = text.replace(refShortcutRegExp, writeImageTag)

	return text
})

showdown.subParser('italicsAndBold', (text: string, options: any, globals: any): string => {
	// it's faster to have 3 separate regexes for each case than have just one
	// because of backtracing, in some cases, it could lead to an exponential effect
	// called "catastrophic backtrace". Ominous!

	function parseInside(txt: string, left: string, right: string): string {
	/*
   if (options.simplifiedAutoLink) {
     txt = showdown.subParser('simplifiedAutoLinks')(txt, options, globals);
   }
   */
		return left + txt + right
	}

	// Parse underscores
	if (options.literalMidWordUnderscores) {
		text = text.replace(/\b___(\S[\s\S]*?)___\b/g, (wm, txt) => {
			return parseInside (txt, '<strong><em>', '</em></strong>')
		})
		text = text.replace(/\b__(\S[\s\S]*?)__\b/g, (wm, txt) => {
			return parseInside (txt, '<strong>', '</strong>')
		})
		text = text.replace(/\b_(\S[\s\S]*?)_\b/g, (wm, txt) => {
			return parseInside (txt, '<em>', '</em>')
		})
	} else {
		text = text.replace(/___(\S[\s\S]*?)___/g, (wm, m) => {
			return (/\S$/.test(m)) ? parseInside (m, '<strong><em>', '</em></strong>') : wm
		})
		text = text.replace(/__(\S[\s\S]*?)__/g, (wm, m) => {
			return (/\S$/.test(m)) ? parseInside (m, '<strong>', '</strong>') : wm
		})
		text = text.replace(/_([^\s_][\s\S]*?)_/g, (wm, m) => {
			// !/^_[^_]/.test(m) - test if it doesn't start with __ (since it seems redundant, we removed it)
			return (/\S$/.test(m)) ? parseInside (m, '<em>', '</em>') : wm
		})
	}

	// Now parse asterisks
	if (options.literalMidWordAsterisks) {
		text = text.replace(/([^*]|^)\B\*\*\*(\S[\s\S]*?)\*\*\*\B(?!\*)/g, (wm, lead, txt) => {
			return parseInside (txt, `${lead}<strong><em>`, '</em></strong>')
		})
		text = text.replace(/([^*]|^)\B\*\*(\S[\s\S]*?)\*\*\B(?!\*)/g, (wm, lead, txt) => {
			return parseInside (txt, `${lead}<strong>`, '</strong>')
		})
		text = text.replace(/([^*]|^)\B\*(\S[\s\S]*?)\*\B(?!\*)/g, (wm, lead, txt) => {
			return parseInside (txt, `${lead}<em>`, '</em>')
		})
	} else {
		text = text.replace(/\*\*\*(\S[\s\S]*?)\*\*\*/g, (wm, m) => {
			return (/\S$/.test(m)) ? parseInside (m, '<strong><em>', '</em></strong>') : wm
		})
		text = text.replace(/\*\*(\S[\s\S]*?)\*\*/g, (wm, m) => {
			return (/\S$/.test(m)) ? parseInside (m, '<strong>', '</strong>') : wm
		})
		text = text.replace(/\*([^\s*][\s\S]*?)\*/g, (wm, m) => {
			// !/^\*[^*]/.test(m) - test if it doesn't start with ** (since it seems redundant, we removed it)
			return (/\S$/.test(m)) ? parseInside (m, '<em>', '</em>') : wm
		})
	}

	return text
})

/**
 * Form HTML ordered (numbered) and unordered (bulleted) lists.
 */
showdown.subParser('lists', (text: string, options: any, globals: any): string => {
	/**
	 * Process the contents of a single ordered or unordered list, splitting it
	 * into individual list items.
	 * @param {string} listStr
	 * @param {boolean} trimTrailing
	 * @returns {string}
	 */
	function processListItems(listStr: string, trimTrailing?: boolean): string {
		// The $g_list_level global keeps track of when we're inside a list.
		// Each time we enter a list, we increment it; when we leave a list,
		// we decrement. If it's zero, we're not in a list anymore.
		//
		// We do this because when we're not inside a list, we want to treat
		// something like this:
		//
		//    I recommend upgrading to version
		//    8. Oops, now this line is treated
		//    as a sub-list.
		//
		// As a single paragraph, despite the fact that the second line starts
		// with a digit-period-space sequence.
		//
		// Whereas when we're inside a list (or sub-list), that line will be
		// treated as the start of a sub-list. What a kludge, huh? This is
		// an aspect of Markdown's syntax that's hard to parse perfectly
		// without resorting to mind-reading. Perhaps the solution is to
		// change the syntax rules such that sub-lists must start with a
		// starting cardinal number; e.g. "1." or "a.".
		globals.gListLevel++

		// trim trailing blank lines:
		listStr = listStr.replace(/\n{2,}$/, '\n')

		// attacklab: add sentinel to emulate \z
		listStr += '¨0'

		let rgx = /(\n)?(^ {0,3})([*+-]|\d+\.)[ \t]+((\[([x ])?\])?[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+\.)[ \t]+))/gim
		let isParagraphed = (/\n[ \t]*\n(?!¨0)/.test(listStr))

		// Since version 1.5, nesting sublists requires 4 spaces (or 1 tab) indentation,
		// which is a syntax breaking change
		// activating this option reverts to old behavior
		if (options.disableForced4SpacesIndentedSublists) {
			rgx = /(\n)?(^ {0,3})([*+-]|\d+\.)[ \t]+((\[([x ])?\])?[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+\.)[ \t]+))/gim
		}

		listStr = listStr.replace(rgx, (wholeMatch: string, m1: string, m2: string, m3: string, m4: string, taskbtn: string, checked: string): string => {
			let isChecked = (checked && checked.trim() !== '')

			let item = showdown.subParser('outdent')(m4, options, globals)
			let bulletStyle = ''

			// Support for github tasklists
			if (taskbtn && options.tasklists) {
				bulletStyle = ' class="task-list-item" style="list-style-type: none;"'
				item = item.replace(/^[ \t]*\[([x ])?\]/im, () => {
					let otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"'
					if (isChecked) {
						otp += ' checked'
					}
					otp += '>'
					return otp
				})
			}

			// ISSUE #312
			// This input: - - - a
			// causes trouble to the parser, since it interprets it as:
			// <ul><li><li><li>a</li></li></li></ul>
			// instead of:
			// <ul><li>- - a</li></ul>
			// So, to prevent it, we will put a marker (¨A)in the beginning of the line
			// Kind of hackish/monkey patching, but seems more effective than overcomplicating the list parser
			item = item.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g, (wm2: string): string => {
				return `¨A${wm2}`
			})

			// m1 - Leading line or
			// Has a double return (multi paragraph) or
			// Has sublist
			if (m1 || (item.search(/\n{2,}/) > -1)) {
				item = showdown.subParser('githubCodeBlocks')(item, options, globals)
				item = showdown.subParser('blockGamut')(item, options, globals)
			} else {
				// Recursion for sub-lists:
				item = showdown.subParser('lists')(item, options, globals)
				item = item.replace(/\n$/, '') // chomp(item)
				item = showdown.subParser('hashHTMLBlocks')(item, options, globals)

				// Colapse double linebreaks
				item = item.replace(/\n{2,}/g, '\n\n')
				if (isParagraphed) {
					item = showdown.subParser('paragraphs')(item, options, globals)
				} else {
					item = showdown.subParser('spanGamut')(item, options, globals)
				}
			}

			// now we need to remove the marker (¨A)
			item = item.replace('¨A', '')
			// we can finally wrap the line in list item tags
			item = `<li${bulletStyle}>${item}</li>\n`

			return item
		})

		// attacklab: strip sentinel
		listStr = listStr.replace(/¨0/g, '')

		globals.gListLevel--

		if (trimTrailing) {
			listStr = listStr.replace(/\s+$/, '')
		}

		return listStr
	}

	function styleStartNumber(list: string, listType: string): string {
	// check if ol and starts by a number different than 1
		if (listType === 'ol') {
			let res = list.match(/^ *(\d+)\./)
			if (res && res[1] !== '1') {
				return ` start="${res[1]}"`
			}
		}
		return ''
	}

	/**
	 * Check and parse consecutive lists (better fix for issue #142)
	 * @param {string} list
	 * @param {string} listType
	 * @param {boolean} trimTrailing
	 * @returns {string}
	 */
	function parseConsecutiveLists(list: string, listType: string, trimTrailing?: boolean): string {
		// check if we caught 2 or more consecutive lists by mistake
		// we use the counterRgx, meaning if listType is UL we look for OL and vice versa
		let olRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?\d+\.[ \t]/gm : /^ {0,3}\d+\.[ \t]/gm
		let ulRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?[*+-][ \t]/gm : /^ {0,3}[*+-][ \t]/gm
		let counterRxg = (listType === 'ul') ? olRgx : ulRgx
		let result = ''

		if (list.search(counterRxg) !== -1) {
			(function parseCL(txt) {
				let pos = txt.search(counterRxg)
				let style = styleStartNumber(list, listType)
				if (pos !== -1) {
					// slice
					result += `\n\n<${listType}${style}>\n${processListItems(txt.slice(0, pos), !!trimTrailing)}</${listType}>\n`

					// invert counterType and listType
					listType = (listType === 'ul') ? 'ol' : 'ul'
					counterRxg = (listType === 'ul') ? olRgx : ulRgx

					// recurse
					parseCL(txt.slice(pos))
				} else {
					result += `\n\n<${listType}${style}>\n${processListItems(txt, !!trimTrailing)}</${listType}>\n`
				}
			})(list)
		} else {
			let style = styleStartNumber(list, listType)
			result = `\n\n<${listType}${style}>\n${processListItems(list, !!trimTrailing)}</${listType}>\n`
		}

		return result
	}

	/** Start of list parsing */
	// add sentinel to hack around khtml/safari bug:
	// http://bugs.webkit.org/show_bug.cgi?id=11231
	text += '¨0'

	if (globals.gListLevel) {
		text = text.replace(/^(( {0,3}([*+-]|\d+\.)[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+\.)[ \t]+)))/gm, (wholeMatch, list, m2) => {
			let listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol'
			return parseConsecutiveLists(list, listType, true)
		})
	} else {
		text = text.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+\.)[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+\.)[ \t]+)))/gm, (wholeMatch, m1, list, m3) => {
			let listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol'
			return parseConsecutiveLists(list, listType, false)
		})
	}

	// strip sentinel
	text = text.replace(/¨0/, '')
	return text
})

/**
 * Parse metadata at the top of the document
 */
showdown.subParser('metadata', (text: string, options: any, globals: any): string => {
	if (!options.metadata) {
		return text
	}

	function parseMetadataContents(content: string): void {
	// raw is raw so it's not changed in any way
		globals.metadata.raw = content

		// escape chars forbidden in html attributes
		// double quotes
		content = content
		// ampersand first
			.replace(/&/g, '&amp;')
		// double quotes
			.replace(/"/g, '&quot;')

		content = content.replace(/\n {4}/g, ' ')
		content.replace(/^([\S ]+): +([\s\S]+?)$/gm, (wm: string, key: string, value: string): string => {
			globals.metadata.parsed[key] = value
			return ''
		})
	}

	text = text.replace(/^\s*«{3,}(\S*)\n([\s\S]+?)\n»{3,}\n/, (wholematch, format, content) => {
		parseMetadataContents(content)
		return '¨M'
	})

	text = text.replace(/^\s*-{3,}(\S*)\n([\s\S]+?)\n-{3,}\n/, (wholematch, format, content) => {
		if (format) {
			globals.metadata.format = format
		}
		parseMetadataContents(content)
		return '¨M'
	})

	text = text.replace(/¨M/g, '')

	return text
})

/**
 * Remove one level of line-leading tabs or spaces
 */
showdown.subParser('outdent', (text: string, options: any, globals: any): string => {
	// attacklab: hack around Konqueror 3.5.4 bug:
	// "----------bug".replace(/^-/g,"") == "bug"
	text = text.replace(/^(\t| {1,4})/gm, '¨0') // attacklab: g_tab_width

	// attacklab: clean up hack
	text = text.replace(/¨0/g, '')

	return text
})

/**
 *
 */
showdown.subParser('paragraphs', (text: string, options: any, globals: any): string => {
	// Strip leading and trailing lines:
	text = text.replace(/^\n+/g, '')
	text = text.replace(/\n+$/g, '')

	let grafs = text.split(/\n{2,}/)
	let grafsOut = []
	let end = grafs.length // Wrap <p> tags

	for (let i = 0; i < end; i++) {
		let str = grafs[i]
		// if this is an HTML marker, copy it
		if (str.search(/¨(K|G)(\d+)\1/) >= 0) {
			grafsOut.push(str)

			// test for presence of characters to prevent empty lines being parsed
			// as paragraphs (resulting in undesired extra empty paragraphs)
		} else if (str.search(/\S/) >= 0) {
			str = showdown.subParser('spanGamut')(str, options, globals)
			str = str.replace(/^([ \t]*)/g, '<p>')
			str += '</p>'
			grafsOut.push(str)
		}
	}

	/** Unhashify HTML blocks */
	end = grafsOut.length
	for (let i = 0; i < end; i++) {
		let blockText = ''
		let grafsOutIt: string = grafsOut[i]
		let codeFlag = false
		// if this is a marker for an html block...
		// use RegExp.test instead of string.search because of QML bug
		while (/¨(K|G)(\d+)\1/.test(grafsOutIt)) {
			let delim = RegExp.$1
			let num = RegExp.$2

			if (delim === 'K') {
				blockText = globals.gHtmlBlocks[num]
			} else {
				// we need to check if ghBlock is a false positive
				if (codeFlag) {
					// use encoded version of all text
					blockText = showdown.subParser('encodeCode')(globals.ghCodeBlocks[num].text, options, globals)
				} else {
					blockText = globals.ghCodeBlocks[num].codeblock
				}
			}
			blockText = blockText.replace(/\$/g, '$$$$') // Escape any dollar signs

			grafsOutIt = grafsOutIt.replace(/(\n\n)?¨(K|G)\d+\2(\n\n)?/, blockText)
			// Check if grafsOutIt is a pre->code
			if (/^<pre\b[^>]*>\s*<code\b[^>]*>/.test(grafsOutIt)) {
				codeFlag = true
			}
		}
		grafsOut[i] = grafsOutIt
	}
	text = grafsOut.join('\n')
	// Strip leading and trailing lines:
	text = text.replace(/^\n+/g, '')
	text = text.replace(/\n+$/g, '')
	return text
})

/**
 * These are all the transformations that occur *within* block-level
 * tags like paragraphs, headers, and list items.
 */
showdown.subParser('spanGamut', (text: string, options: any, globals: any): string => {
	text = showdown.subParser('codeSpans')(text, options, globals)
	text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals)
	text = showdown.subParser('encodeBackslashEscapes')(text, options, globals)

	// Process anchor and image tags. Images must come first,
	// because ![foo][f] looks like an anchor.
	text = showdown.subParser('images')(text, options, globals)
	text = showdown.subParser('anchors')(text, options, globals)

	// Make links out of things like `<http://example.com/>`
	// Must come after anchors, because you can use < and >
	// delimiters in inline links like [this](<url>).
	text = showdown.subParser('autoLinks')(text, options, globals)
	text = showdown.subParser('simplifiedAutoLinks')(text, options, globals)
	text = showdown.subParser('underline')(text, options, globals)
	text = showdown.subParser('italicsAndBold')(text, options, globals)
	text = showdown.subParser('strikethrough')(text, options, globals)
	text = showdown.subParser('ellipsis')(text, options, globals)

	// we need to hash HTML tags inside spans
	text = showdown.subParser('hashHTMLSpans')(text, options, globals)

	// now we encode amps and angles
	text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals)

	// Do hard breaks
	if (options.simpleLineBreaks) {
		// GFM style hard breaks
		// only add line breaks if the text does not contain a block (special case for lists)
		if (!/\n\n¨K/.test(text)) {
			text = text.replace(/\n+/g, '<br />\n')
		}
	} else {
		// Vanilla hard breaks
		text = text.replace(/ {2,}\n/g, '<br />\n')
	}

	return text
})

showdown.subParser('strikethrough', (text: string, options: any, globals: any): string => {
	function parseInside(txt: string): string {
		if (options.simplifiedAutoLink) {
			txt = showdown.subParser('simplifiedAutoLinks')(txt, options, globals)
		}
		return `<del>${txt}</del>`
	}

	if (options.strikethrough) {
		text = text.replace(/~{2}([\s\S]+?)~{2}/g, (wm, txt) => { return parseInside(txt) })
	}

	return text
})

/**
 * Strips link definitions from text, stores the URLs and titles in
 * hash references.
 * Link defs are in the form: ^[id]: url "optional title"
 */
showdown.subParser('stripLinkDefinitions', (text: string, options: any, globals: any): string => {
	let regex = /^ {0,3}\[([^\]]+)\]:[ \t]*(?:\n[ \t]*)?<?([^>\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:\n[ \t]*)?(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm
	let base64Regex = /^ {0,3}\[([^\]]+)\]:[ \t]*(?:\n[ \t]*)?<?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:\n[ \t]*)?(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm

	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
	text += '¨0'

	let replaceFunc = function (wholeMatch: any, linkId: any, url: any, width: any, height: any, blankLines: any, title: any) {
	// if there aren't two instances of linkId it must not be a reference link so back out
		linkId = linkId.toLowerCase()
		if (text.toLowerCase().split(linkId).length - 1 < 2) {
			return wholeMatch
		}
		if (url.match(/^data:.+?\/.+?;base64,/)) {
			// remove newlines
			globals.gUrls[linkId] = url.replace(/\s/g, '')
		} else {
			globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url, options, globals) // Link IDs are case-insensitive
		}

		if (blankLines) {
			// Oops, found blank lines, so it's not a title.
			// Put back the parenthetical statement we stole.
			return blankLines + title
		} else {
			if (title) {
				globals.gTitles[linkId] = title.replace(/"|'/g, '&quot;')
			}
			if (options.parseImgDimensions && width && height) {
				globals.gDimensions[linkId] = {
					width,
					height
				}
			}
		}
		// Completely remove the definition from the text
		return ''
	}

	// first we try to find base64 link references
	text = text.replace(base64Regex, replaceFunc)

	text = text.replace(regex, replaceFunc)

	// attacklab: strip sentinel
	text = text.replace(/¨0/, '')

	return text
})

showdown.subParser('tables', (text: string, options: any, globals: any): string => {
	if (!options.tables) {
		return text
	}

	let tableRgx = /^.[^\n\r|\u2028\u2029]*\|.+\n {0,3}\|?[ \t]*(?::[ \t]*)?[-=]{2,}[ \t]*(?::[ \t]*)?\|[ \t]*(?::[ \t]*)?[-=]{2}[\s\S]+?(?:\n\n|¨0)/gm
	// singeColTblRgx = /^ {0,3}\|.+\|\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n(?: {0,3}\|.+\|\n)+(?:\n\n|¨0)/gm;
	let singeColTblRgx = /^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*(?::[ \t]*)?[-=]{2,}[ \t]*(?::[ \t]*)?\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm

	function parseStyles(sLine: string): string {
		if (/^:[ \t]*-+$/.test(sLine)) {
			return ' style="text-align:left;"'
		} else if (/^-+[ \t]*:[ \t]*$/.test(sLine)) {
			return ' style="text-align:right;"'
		} else if (/^:[ \t]*-+[ \t]*:$/.test(sLine)) {
			return ' style="text-align:center;"'
		} else {
			return ''
		}
	}

	function parseHeaders(header: string, style: string): string {
		let id = ''
		header = header.trim()
		// support both tablesHeaderId and tableHeaderId due to error in documentation so we don't break backwards compatibility
		if (options.tablesHeaderId || options.tableHeaderId) {
			id = ` id="${header.replace(/ /g, '_').toLowerCase()}"`
		}
		header = showdown.subParser('spanGamut')(header, options, globals)

		return `<th${id}${style}>${header}</th>\n`
	}

	function parseCells(cell: string, style: string): string {
		let subText = showdown.subParser('spanGamut')(cell, options, globals)
		return `<td${style}>${subText}</td>\n`
	}

	function buildTable(headers: string[], cells: string[][]): string {
		let tb = '<table>\n<thead>\n<tr>\n'
		let tblLgn = headers.length

		for (let i = 0; i < tblLgn; ++i) {
			tb += headers[i]
		}
		tb += '</tr>\n</thead>\n<tbody>\n'

		for (let i = 0; i < cells.length; ++i) {
			tb += '<tr>\n'
			for (let ii = 0; ii < tblLgn; ++ii) {
				tb += cells[i][ii]
			}
			tb += '</tr>\n'
		}
		tb += '</tbody>\n</table>\n'
		return tb
	}

	function parseTable(rawTable: string): string {
		let tableLines = rawTable.split('\n')

		for (let i = 0; i < tableLines.length; ++i) {
			// strip wrong first and last column if wrapped tables are used
			if (/^ {0,3}\|/.test(tableLines[i])) {
				tableLines[i] = tableLines[i].replace(/^ {0,3}\|/, '')
			}
			if (/\|[ \t]*$/.test(tableLines[i])) {
				tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, '')
			}
			// parse code spans first, but we only support one line code spans
			tableLines[i] = showdown.subParser('codeSpans')(tableLines[i], options, globals)
		}

		let rawHeaders = tableLines[0].split('|').map((s: string): string => { return s.trim() })
		let rawStyles = tableLines[1].split('|').map((s: string): string => { return s.trim() })
		let rawCells = []
		let headers = []
		let styles = []
		let cells = []

		tableLines.shift()
		tableLines.shift()

		for (let i = 0; i < tableLines.length; ++i) {
			if (tableLines[i].trim() === '') {
				continue
			}
			rawCells.push(
				tableLines[i]
					.split('|')
					.map((s: string): string => {
						return s.trim()
					})
			)
		}

		if (rawHeaders.length < rawStyles.length) {
			return rawTable
		}

		for (let i = 0; i < rawStyles.length; ++i) {
			styles.push(parseStyles(rawStyles[i]))
		}

		for (let i = 0; i < rawHeaders.length; ++i) {
			if (showdown.helper.isUndefined(styles[i])) {
				styles[i] = ''
			}
			headers.push(parseHeaders(rawHeaders[i], styles[i]))
		}

		for (let i = 0; i < rawCells.length; ++i) {
			let row = []
			for (let ii = 0; ii < headers.length; ++ii) {
				if (showdown.helper.isUndefined(rawCells[i][ii])) {

				}
				row.push(parseCells(rawCells[i][ii], styles[ii]))
			}
			cells.push(row)
		}

		return buildTable(headers, cells)
	}

	// find escaped pipe characters
	text = text.replace(/\\(\|)/g, showdown.helper.escapeCharactersCallback)

	// parse multi column tables
	text = text.replace(tableRgx, parseTable)

	// parse one column tables
	text = text.replace(singeColTblRgx, parseTable)

	return text
})

showdown.subParser('underline', (text: string, options: any, globals: any): string => {
	if (!options.underline) {
		return text
	}

	if (options.literalMidWordUnderscores) {
		text = text.replace(/\b___(\S[\s\S]*?)___\b/g, (wm, txt) => {
			return `<u>${txt}</u>`
		})
		text = text.replace(/\b__(\S[\s\S]*?)__\b/g, (wm, txt) => {
			return `<u>${txt}</u>`
		})
	} else {
		text = text.replace(/___(\S[\s\S]*?)___/g, (wm, m) => {
			return (/\S$/.test(m)) ? `<u>${m}</u>` : wm
		})
		text = text.replace(/__(\S[\s\S]*?)__/g, (wm, m) => {
			return (/\S$/.test(m)) ? `<u>${m}</u>` : wm
		})
	}

	// escape remaining underscores to prevent them being parsed by italic and bold
	text = text.replace(/(_)/g, showdown.helper.escapeCharactersCallback)

	return text
})

/**
 * Swap back in all the special characters we've hidden.
 */
showdown.subParser('unescapeSpecialChars', (text: string, options: any, globals: any): string => {
	text = text.replace(/¨E(\d+)E/g, (wholeMatch, m1) => {
		let charCodeToReplace = Number.parseInt(m1)
		return String.fromCharCode(charCodeToReplace)
	})

	return text
})

showdown.subParser('makeMarkdown.blockquote', (node: any, globals: any): string => {
	let txt = ''
	if (node.hasChildNodes()) {
		let children = node.childNodes
		let childrenLength = children.length

		for (let i = 0; i < childrenLength; ++i) {
			let innerTxt = showdown.subParser('makeMarkdown.node')(children[i], globals)

			if (innerTxt === '') {
				continue
			}
			txt += innerTxt
		}
	}
	// cleanup
	txt = txt.trim()
	txt = `> ${txt.split('\n').join('\n> ')}`
	return txt
})

showdown.subParser('makeMarkdown.codeBlock', (node: any, globals: any): string => {
	let lang = node.getAttribute('language')
	let num = node.getAttribute('precodenum')
	return `\`\`\`${lang}\n${globals.preList[num]}\n\`\`\``
})

showdown.subParser('makeMarkdown.codeSpan', (node: any): string => {
	return `\`${node.innerHTML}\``
})

showdown.subParser('makeMarkdown.emphasis', (node: any, globals: any): string => {
	let txt = ''
	if (node.hasChildNodes()) {
		txt += '*'
		let children = node.childNodes
		let childrenLength = children.length
		for (let i = 0; i < childrenLength; ++i) {
			txt += showdown.subParser('makeMarkdown.node')(children[i], globals)
		}
		txt += '*'
	}
	return txt
})

showdown.subParser('makeMarkdown.header', (node: any, globals: any, headerLevel: any): string => {
	let headerMark = new Array(headerLevel + 1).join('#')
	let txt = ''

	if (node.hasChildNodes()) {
		txt = `${headerMark} `
		let children = node.childNodes
		let childrenLength = children.length

		for (let i = 0; i < childrenLength; ++i) {
			txt += showdown.subParser('makeMarkdown.node')(children[i], globals)
		}
	}
	return txt
})

showdown.subParser('makeMarkdown.hr', () => {
	return '---'
})

showdown.subParser('makeMarkdown.image', (node: any): string => {
	let txt = ''
	if (node.hasAttribute('src')) {
		txt += `![${node.getAttribute('alt')}](`
		txt += `<${node.getAttribute('src')}>`
		if (node.hasAttribute('width') && node.hasAttribute('height')) {
			txt += ` =${node.getAttribute('width')}x${node.getAttribute('height')}`
		}

		if (node.hasAttribute('title')) {
			txt += ` "${node.getAttribute('title')}"`
		}
		txt += ')'
	}
	return txt
})

showdown.subParser('makeMarkdown.links', (node: any, globals: any): string => {
	let txt = ''
	if (node.hasChildNodes() && node.hasAttribute('href')) {
		let children = node.childNodes
		let childrenLength = children.length
		txt = '['
		for (let i = 0; i < childrenLength; ++i) {
			txt += showdown.subParser('makeMarkdown.node')(children[i], globals)
		}
		txt += ']('
		txt += `<${node.getAttribute('href')}>`
		if (node.hasAttribute('title')) {
			txt += ` "${node.getAttribute('title')}"`
		}
		txt += ')'
	}
	return txt
})

showdown.subParser('makeMarkdown.list', (node: any, globals: any, type: any): string => {
	let txt = ''
	if (!node.hasChildNodes()) {
		return ''
	}
	let listItems = node.childNodes
	let listItemsLenght = listItems.length
	let listNum = node.getAttribute('start') || 1

	for (let i = 0; i < listItemsLenght; ++i) {
		if (typeof listItems[i].tagName === 'undefined' || listItems[i].tagName.toLowerCase() !== 'li') {
			continue
		}

		// define the bullet to use in list
		let bullet = ''
		if (type === 'ol') {
			bullet = `${listNum.toString()}. `
		} else {
			bullet = '- '
		}

		// parse list item
		txt += bullet + showdown.subParser('makeMarkdown.listItem')(listItems[i], globals)
		++listNum
	}

	// add comment at the end to prevent consecutive lists to be parsed as one
	txt += '\n<!-- -->\n'
	return txt.trim()
})

showdown.subParser('makeMarkdown.listItem', (node: any, globals: any): string => {
	let listItemTxt = ''

	let children = node.childNodes
	let childrenLenght = children.length

	for (let i = 0; i < childrenLenght; ++i) {
		listItemTxt += showdown.subParser('makeMarkdown.node')(children[i], globals)
	}
	// if it's only one liner, we need to add a newline at the end
	if (!/\n$/.test(listItemTxt)) {
		listItemTxt += '\n'
	} else {
		// it's multiparagraph, so we need to indent
		listItemTxt = listItemTxt
			.split('\n')
			.join('\n    ')
			.replace(/^ {4}$/gm, '')
			.replace(/\n{2,}/g, '\n\n')
	}

	return listItemTxt
})

showdown.subParser('makeMarkdown.node', (node: any, globals: any, spansOnly: any): string => {
	spansOnly = spansOnly || false

	let txt = ''

	// edge case of text without wrapper paragraph
	if (node.nodeType === 3) {
		return showdown.subParser('makeMarkdown.txt')(node, globals)
	}

	// HTML comment
	if (node.nodeType === 8) {
		return `<!--${node.data}-->\n\n`
	}

	// process only node elements
	if (node.nodeType !== 1) {
		return ''
	}

	let tagName = node.tagName.toLowerCase()

	switch (tagName) {
		//
		// BLOCKS
		//
		case 'h1':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.header')(node, globals, 1)}\n\n` }
			break
		case 'h2':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.header')(node, globals, 2)}\n\n` }
			break
		case 'h3':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.header')(node, globals, 3)}\n\n` }
			break
		case 'h4':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.header')(node, globals, 4)}\n\n` }
			break
		case 'h5':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.header')(node, globals, 5)}\n\n` }
			break
		case 'h6':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.header')(node, globals, 6)}\n\n` }
			break

		case 'p':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.paragraph')(node, globals)}\n\n` }
			break

		case 'blockquote':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.blockquote')(node, globals)}\n\n` }
			break

		case 'hr':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.hr')(node, globals)}\n\n` }
			break

		case 'ol':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.list')(node, globals, 'ol')}\n\n` }
			break

		case 'ul':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.list')(node, globals, 'ul')}\n\n` }
			break

		case 'precode':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.codeBlock')(node, globals)}\n\n` }
			break

		case 'pre':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.pre')(node, globals)}\n\n` }
			break

		case 'table':
			if (!spansOnly) { txt = `${showdown.subParser('makeMarkdown.table')(node, globals)}\n\n` }
			break

			//
			// SPANS
			//
		case 'code':
			txt = showdown.subParser('makeMarkdown.codeSpan')(node, globals)
			break

		case 'em':
		case 'i':
			txt = showdown.subParser('makeMarkdown.emphasis')(node, globals)
			break

		case 'strong':
		case 'b':
			txt = showdown.subParser('makeMarkdown.strong')(node, globals)
			break

		case 'del':
			txt = showdown.subParser('makeMarkdown.strikethrough')(node, globals)
			break

		case 'a':
			txt = showdown.subParser('makeMarkdown.links')(node, globals)
			break

		case 'img':
			txt = showdown.subParser('makeMarkdown.image')(node, globals)
			break

		default:
			txt = `${node.outerHTML}\n\n`
	}

	// common normalization
	// TODO eventually

	return txt
})

showdown.subParser('makeMarkdown.paragraph', (node: any, globals: any): string => {
	let txt = ''
	if (node.hasChildNodes()) {
		let children = node.childNodes
		let childrenLength = children.length
		for (let i = 0; i < childrenLength; ++i) {
			txt += showdown.subParser('makeMarkdown.node')(children[i], globals)
		}
	}

	// some text normalization
	txt = txt.trim()

	return txt
})

showdown.subParser('makeMarkdown.pre', (node: any, globals: any): string => {
	let num = node.getAttribute('prenum')
	return `<pre>${globals.preList[num]}</pre>`
})

showdown.subParser('makeMarkdown.strikethrough', (node: any, globals: any): string => {
	let txt = ''
	if (node.hasChildNodes()) {
		txt += '~~'
		let children = node.childNodes
		let childrenLength = children.length
		for (let i = 0; i < childrenLength; ++i) {
			txt += showdown.subParser('makeMarkdown.node')(children[i], globals)
		}
		txt += '~~'
	}
	return txt
})

showdown.subParser('makeMarkdown.strong', (node: any, globals: any): string => {
	let txt = ''
	if (node.hasChildNodes()) {
		txt += '**'
		let children = node.childNodes
		let childrenLength = children.length
		for (let i = 0; i < childrenLength; ++i) {
			txt += showdown.subParser('makeMarkdown.node')(children[i], globals)
		}
		txt += '**'
	}
	return txt
})

showdown.subParser('makeMarkdown.table', (node: any, globals: any): string => {
	let txt = ''
	let tableArray: string[][] = [[], []]
	let headings = node.querySelectorAll('thead>tr>th')
	let rows = node.querySelectorAll('tbody>tr')
	for (let i = 0; i < headings.length; ++i) {
		let headContent = showdown.subParser('makeMarkdown.tableCell')(headings[i], globals)
		let allign = '---'

		if (headings[i].hasAttribute('style')) {
			let style = headings[i].getAttribute('style').toLowerCase().replace(/\s/g, '')
			switch (style) {
				case 'text-align:left;':
					allign = ':---'
					break
				case 'text-align:right;':
					allign = '---:'
					break
				case 'text-align:center;':
					allign = ':---:'
					break
			}
		}
		tableArray[0][i] = headContent.trim()
		tableArray[1][i] = allign
	}

	for (let i = 0; i < rows.length; ++i) {
		let r = tableArray.push([]) - 1
		let cols = rows[i].getElementsByTagName('td')

		for (let ii = 0; ii < headings.length; ++ii) {
			let cellContent = ' '
			if (typeof cols[ii] !== 'undefined') {
				cellContent = showdown.subParser('makeMarkdown.tableCell')(cols[ii], globals)
			}
			tableArray[r].push(cellContent)
		}
	}

	let cellSpacesCount = 3
	for (let i = 0; i < tableArray.length; ++i) {
		for (let ii = 0; ii < tableArray[i].length; ++ii) {
			let strLen = tableArray[i][ii].length
			if (strLen > cellSpacesCount) {
				cellSpacesCount = strLen
			}
		}
	}

	for (let i = 0; i < tableArray.length; ++i) {
		for (let ii = 0; ii < tableArray[i].length; ++ii) {
			if (i === 1) {
				if (tableArray[i][ii].slice(-1) === ':') {
					tableArray[i][ii] = `${showdown.helper.padEnd(tableArray[i][ii].slice(-1), cellSpacesCount - 1, '-')}:`
				} else {
					tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount, '-')
				}
			} else {
				tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount)
			}
		}
		txt += `| ${tableArray[i].join(' | ')} |\n`
	}

	return txt.trim()
})

showdown.subParser('makeMarkdown.tableCell', (node: any, globals: any): string => {
	let txt = ''
	if (!node.hasChildNodes()) {
		return ''
	}
	let children = node.childNodes
	let childrenLength = children.length

	for (let i = 0; i < childrenLength; ++i) {
		txt += showdown.subParser('makeMarkdown.node')(children[i], globals, true)
	}
	return txt.trim()
})

showdown.subParser('makeMarkdown.txt', (node: any): string => {
	let txt = node.nodeValue

	// multiple spaces are collapsed
	txt = txt.replace(/ +/g, ' ')

	// replace the custom ¨NBSP; with a space
	txt = txt.replace(/¨NBSP;/g, ' ')

	// ", <, > and & should replace escaped html entities
	txt = showdown.helper.unescapeHTMLEntities(txt)

	// escape markdown magic characters
	// emphasis, strong and strikethrough - can appear everywhere
	// we also escape pipe (|) because of tables
	// and escape ` because of code blocks and spans
	txt = txt.replace(/([*_~|`])/g, '\\$1')

	// escape > because of blockquotes
	txt = txt.replace(/^(\s*)>/g, '\\$1>')

	// hash character, only troublesome at the beginning of a line because of headers
	txt = txt.replace(/^#/gm, '\\#')

	// horizontal rules
	txt = txt.replace(/^(\s*)([-=]{3,})(\s*)$/, '$1\\$2$3')

	// dot, because of ordered lists, only troublesome at the beginning of a line when preceded by an integer
	txt = txt.replace(/^( {0,3}\d+)\./gm, '$1\\.')

	// +, * and -, at the beginning of a line becomes a list, so we need to escape them also (asterisk was already escaped)
	txt = txt.replace(/^( {0,3})([+-])/gm, '$1\\$2')

	// images and links, ] followed by ( is problematic, so we escape it
	txt = txt.replace(/\](\s*)\(/g, '\\]$1\\(')
	// reference URIs must also be escaped
	txt = txt.replace(/^ {0,3}\[([\S \t]*?)\]:/gm, '\\[$1]:')
	return txt
})

export { defaultOptions, showdown }
