/ * *
*
* showdown : https : //github.com/showdownjs/showdown
*
* author : Di ( 微信小程序开发工程师 )
* organization : WeAppDev ( 微信小程序开发论坛 ) ( http : //weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址 : https : //github.com/icindy/wxParse
*
* for : 微信小程序富文本解析
* detail : http : //weappdev.com/t/wxparse-alpha0-1-html-markdown/184
* /
function getDefaultOpts ( simple ) {
'use strict' ;
var defaultOptions = {
omitExtraWLInCodeBlocks : {
defaultValue : false ,
describe : 'Omit the default extra whiteline added to code blocks' ,
type : 'boolean'
} ,
noHeaderId : {
defaultValue : false ,
describe : 'Turn on/off generated header id' ,
type : 'boolean'
} ,
prefixHeaderId : {
defaultValue : false ,
describe : 'Specify a prefix to generated header ids' ,
type : 'string'
} ,
headerLevelStart : {
defaultValue : false ,
describe : 'The header blocks level start' ,
type : 'integer'
} ,
parseImgDimensions : {
defaultValue : false ,
describe : 'Turn on/off image dimension parsing' ,
type : 'boolean'
} ,
simplifiedAutoLink : {
defaultValue : false ,
describe : 'Turn on/off GFM autolink style' ,
type : 'boolean'
} ,
literalMidWordUnderscores : {
defaultValue : false ,
describe : 'Parse midword underscores as literal underscores' ,
type : 'boolean'
} ,
strikethrough : {
defaultValue : false ,
describe : 'Turn on/off strikethrough support' ,
type : 'boolean'
} ,
tables : {
defaultValue : false ,
describe : 'Turn on/off tables support' ,
type : 'boolean'
} ,
tablesHeaderId : {
defaultValue : false ,
describe : 'Add an id to table headers' ,
type : 'boolean'
} ,
ghCodeBlocks : {
defaultValue : true ,
describe : 'Turn on/off GFM fenced code blocks support' ,
type : 'boolean'
} ,
tasklists : {
defaultValue : false ,
describe : 'Turn on/off GFM tasklist support' ,
type : 'boolean'
} ,
smoothLivePreview : {
defaultValue : false ,
describe : 'Prevents weird effects in live previews due to incomplete input' ,
type : 'boolean'
} ,
smartIndentationFix : {
defaultValue : false ,
description : 'Tries to smartly fix identation in es6 strings' ,
type : 'boolean'
}
} ;
if ( simple === false ) {
return JSON . parse ( JSON . stringify ( defaultOptions ) ) ;
}
var ret = { } ;
for ( var opt in defaultOptions ) {
if ( defaultOptions . hasOwnProperty ( opt ) ) {
ret [ opt ] = defaultOptions [ opt ] . defaultValue ;
}
}
return ret ;
}
/ * *
* Created by Tivie on 06 - 01 - 2015.
* /
// Private properties
var showdown = { } ,
parsers = { } ,
extensions = { } ,
globalOptions = getDefaultOpts ( true ) ,
flavor = {
github : {
omitExtraWLInCodeBlocks : true ,
prefixHeaderId : 'user-content-' ,
simplifiedAutoLink : true ,
literalMidWordUnderscores : true ,
strikethrough : true ,
tables : true ,
tablesHeaderId : true ,
ghCodeBlocks : true ,
tasklists : true
} ,
vanilla : getDefaultOpts ( true )
} ;
/ * *
* helper namespace
* @ type { { } }
* /
showdown . helper = { } ;
/ * *
* TODO LEGACY SUPPORT CODE
* @ type { { } }
* /
showdown . extensions = { } ;
/ * *
* Set a global option
* @ static
* @ param { string } key
* @ param { * } value
* @ returns { showdown }
* /
showdown . setOption = function ( key , value ) {
'use strict' ;
globalOptions [ key ] = value ;
return this ;
} ;
/ * *
* Get a global option
* @ static
* @ param { string } key
* @ returns { * }
* /
showdown . getOption = function ( key ) {
'use strict' ;
return globalOptions [ key ] ;
} ;
/ * *
* Get the global options
* @ static
* @ returns { { } }
* /
showdown . getOptions = function ( ) {
'use strict' ;
return globalOptions ;
} ;
/ * *
* Reset global options to the default values
* @ static
* /
showdown . resetOptions = function ( ) {
'use strict' ;
globalOptions = getDefaultOpts ( true ) ;
} ;
/ * *
* Set the flavor showdown should use as default
* @ param { string } name
* /
showdown . setFlavor = function ( name ) {
'use strict' ;
if ( flavor . hasOwnProperty ( name ) ) {
var preset = flavor [ name ] ;
for ( var option in preset ) {
if ( preset . hasOwnProperty ( option ) ) {
globalOptions [ option ] = preset [ option ] ;
}
}
}
} ;
/ * *
* Get the default options
* @ static
* @ param { boolean } [ simple = true ]
* @ returns { { } }
* /
showdown . getDefaultOptions = function ( simple ) {
'use strict' ;
return getDefaultOpts ( simple ) ;
} ;
/ * *
* Get or set a subParser
*
* subParser ( name ) - Get a registered subParser
* subParser ( name , func ) - Register a subParser
* @ static
* @ param { string } name
* @ param { function } [ func ]
* @ returns { * }
* /
showdown . subParser = function ( name , func ) {
'use strict' ;
if ( showdown . helper . isString ( name ) ) {
if ( typeof func !== 'undefined' ) {
parsers [ name ] = func ;
} else {
if ( parsers . hasOwnProperty ( name ) ) {
return parsers [ name ] ;
} else {
throw Error ( 'SubParser named ' + name + ' not registered!' ) ;
}
}
}
} ;
/ * *
* Gets or registers an extension
* @ static
* @ param { string } name
* @ param { object | function = } ext
* @ returns { * }
* /
showdown . extension = function ( name , ext ) {
'use strict' ;
if ( ! showdown . helper . isString ( name ) ) {
throw Error ( 'Extension \'name\' must be a string' ) ;
}
name = showdown . helper . stdExtName ( name ) ; // Getter
if ( showdown . helper . isUndefined ( ext ) ) {
if ( ! extensions . hasOwnProperty ( name ) ) {
throw Error ( 'Extension named ' + name + ' is not registered!' ) ;
}
return extensions [ name ] ; // Setter
} else {
// Expand extension if it's wrapped in a function
if ( typeof ext === 'function' ) {
ext = ext ( ) ;
} // Ensure extension is an array
if ( ! showdown . helper . isArray ( ext ) ) {
ext = [ ext ] ;
}
var validExtension = validate ( ext , name ) ;
if ( validExtension . valid ) {
extensions [ name ] = ext ;
} else {
throw Error ( validExtension . error ) ;
}
}
} ;
/ * *
* Gets all extensions registered
* @ returns { { } }
* /
showdown . getAllExtensions = function ( ) {
'use strict' ;
return extensions ;
} ;
/ * *
* Remove an extension
* @ param { string } name
* /
showdown . removeExtension = function ( name ) {
'use strict' ;
delete extensions [ name ] ;
} ;
/ * *
* Removes all extensions
* /
showdown . resetExtensions = function ( ) {
'use strict' ;
extensions = { } ;
} ;
/ * *
* Validate extension
* @ param { array } extension
* @ param { string } name
* @ returns { { valid : boolean , error : string } }
* /
function validate ( extension , name ) {
'use strict' ;
var errMsg = name ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension' ,
ret = {
valid : true ,
error : ''
} ;
if ( ! showdown . helper . isArray ( extension ) ) {
extension = [ extension ] ;
}
for ( var i = 0 ; i < extension . length ; ++ i ) {
var baseMsg = errMsg + ' sub-extension ' + i + ': ' ,
ext = extension [ i ] ;
if ( typeof ext !== 'object' ) {
ret . valid = false ;
ret . error = baseMsg + 'must be an object, but ' + typeof ext + ' given' ;
return ret ;
}
if ( ! showdown . helper . isString ( ext . type ) ) {
ret . valid = false ;
ret . error = baseMsg + 'property "type" must be a string, but ' + typeof ext . type + ' given' ;
return ret ;
}
var type = ext . type = ext . type . toLowerCase ( ) ; // normalize extension type
if ( type === 'language' ) {
type = ext . type = 'lang' ;
}
if ( type === 'html' ) {
type = ext . type = 'output' ;
}
if ( type !== 'lang' && type !== 'output' && type !== 'listener' ) {
ret . valid = false ;
ret . error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"' ;
return ret ;
}
if ( type === 'listener' ) {
if ( showdown . helper . isUndefined ( ext . listeners ) ) {
ret . valid = false ;
ret . error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"' ;
return ret ;
}
} else {
if ( showdown . helper . isUndefined ( ext . filter ) && showdown . helper . isUndefined ( ext . regex ) ) {
ret . valid = false ;
ret . error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method' ;
return ret ;
}
}
if ( ext . listeners ) {
if ( typeof ext . listeners !== 'object' ) {
ret . valid = false ;
ret . error = baseMsg + '"listeners" property must be an object but ' + typeof ext . listeners + ' given' ;
return ret ;
}
for ( var ln in ext . listeners ) {
if ( ext . listeners . hasOwnProperty ( ln ) ) {
if ( typeof ext . listeners [ ln ] !== 'function' ) {
ret . valid = false ;
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' ;
return ret ;
}
}
}
}
if ( ext . filter ) {
if ( typeof ext . filter !== 'function' ) {
ret . valid = false ;
ret . error = baseMsg + '"filter" must be a function, but ' + typeof ext . filter + ' given' ;
return ret ;
}
} else if ( ext . regex ) {
if ( showdown . helper . isString ( ext . regex ) ) {
ext . regex = new RegExp ( ext . regex , 'g' ) ;
}
if ( ! ext . regex instanceof RegExp ) {
ret . valid = false ;
ret . error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext . regex + ' given' ;
return ret ;
}
if ( showdown . helper . isUndefined ( ext . replace ) ) {
ret . valid = false ;
ret . error = baseMsg + '"regex" extensions must implement a replace string or function' ;
return ret ;
}
}
}
return ret ;
}
/ * *
* Validate extension
* @ param { object } ext
* @ returns { boolean }
* /
showdown . validateExtension = function ( ext ) {
'use strict' ;
var validateExtension = validate ( ext , null ) ;
if ( ! validateExtension . valid ) {
console . warn ( validateExtension . error ) ;
return false ;
}
return true ;
} ;
/ * *
* showdownjs helper functions
* /
if ( ! showdown . hasOwnProperty ( 'helper' ) ) {
showdown . helper = { } ;
}
/ * *
* Check if var is string
* @ static
* @ param { string } a
* @ returns { boolean }
* /
showdown . helper . isString = function isString ( a ) {
'use strict' ;
return typeof a === 'string' || a instanceof String ;
} ;
/ * *
* Check if var is a function
* @ static
* @ param { string } a
* @ returns { boolean }
* /
showdown . helper . isFunction = function isFunction ( a ) {
'use strict' ;
var getType = { } ;
return a && getType . toString . call ( a ) === '[object Function]' ;
} ;
/ * *
* ForEach helper function
* @ static
* @ param { * } obj
* @ param { function } callback
* /
showdown . helper . forEach = function forEach ( obj , callback ) {
'use strict' ;
if ( typeof obj . forEach === 'function' ) {
obj . forEach ( callback ) ;
} else {
for ( var i = 0 ; i < obj . length ; i ++ ) {
callback ( obj [ i ] , i , obj ) ;
}
}
} ;
/ * *
* isArray helper function
* @ static
* @ param { * } a
* @ returns { boolean }
* /
showdown . helper . isArray = function isArray ( a ) {
'use strict' ;
return a . constructor === Array ;
} ;
/ * *
* 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 isUndefined ( value ) {
'use strict' ;
return typeof value === 'undefined' ;
} ;
/ * *
* Standardidize extension name
* @ static
* @ param { string } s extension name
* @ returns { string }
* /
showdown . helper . stdExtName = function ( s ) {
'use strict' ;
return s . replace ( /[_-]||\s/g , '' ) . toLowerCase ( ) ;
} ;
function escapeCharactersCallback ( wholeMatch , m1 ) {
'use strict' ;
var 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 escapeCharacters ( text , charsToEscape , afterBackslash ) {
'use strict' ; // 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' ) + '])' ;
if ( afterBackslash ) {
regexString = '\\\\' + regexString ;
}
var regex = new RegExp ( regexString , 'g' ) ;
text = text . replace ( regex , escapeCharactersCallback ) ;
return text ;
} ;
var rgxFindMatchPos = function ( str , left , right , flags ) {
'use strict' ;
var f = flags || '' ,
g = f . indexOf ( 'g' ) > - 1 ,
x = new RegExp ( left + '|' + right , 'g' + f . replace ( /g/g , '' ) ) ,
l = new RegExp ( left , f . replace ( /g/g , '' ) ) ,
pos = [ ] ,
t ,
s ,
m ,
start ,
end ;
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 ;
var obj = {
left : {
start : start ,
end : s
} ,
match : {
start : s ,
end : m . index
} ,
right : {
start : m . index ,
end : end
} ,
wholeMatch : {
start : start ,
end : end
}
} ;
pos . push ( obj ) ;
if ( ! g ) {
return pos ;
}
}
}
}
} while ( t && ( x . lastIndex = s ) ) ;
return pos ;
} ;
/ * *
* matchRecursiveRegExp
*
* ( c ) 2007 Steven Levithan < stevenlevithan . com >
* MIT License
*
* 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 , left , right , flags ) {
'use strict' ;
var matchPos = rgxFindMatchPos ( str , left , right , flags ) ,
results = [ ] ;
for ( var 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 , replacement , left , right , flags ) {
'use strict' ;
if ( ! showdown . helper . isFunction ( replacement ) ) {
var repStr = replacement ;
replacement = function ( ) {
return repStr ;
} ;
}
var matchPos = rgxFindMatchPos ( str , left , right , flags ) ,
finalStr = str ,
lng = matchPos . length ;
if ( lng > 0 ) {
var bits = [ ] ;
if ( matchPos [ 0 ] . wholeMatch . start !== 0 ) {
bits . push ( str . slice ( 0 , matchPos [ 0 ] . wholeMatch . start ) ) ;
}
for ( var i = 0 ; i < lng ; ++ i ) {
bits . push ( replacement ( 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 ;
} ;
/ * *
* POLYFILLS
* /
if ( showdown . helper . isUndefined ( console ) ) {
console = {
warn : function ( msg ) {
'use strict' ;
alert ( msg ) ;
} ,
log : function ( msg ) {
'use strict' ;
alert ( msg ) ;
} ,
error : function ( msg ) {
'use strict' ;
throw msg ;
}
} ;
}
/ * *
* Created by Estevao on 31 - 05 - 2015.
* /
/ * *
* Showdown Converter class
* @ class
* @ param { object } [ converterOptions ]
* @ returns { Converter }
* /
showdown . Converter = function ( converterOptions ) {
'use strict' ;
var
/ * *
* Options used by this converter
* @ private
* @ type { { } }
* /
options = { } ,
/ * *
* Language extensions used by this converter
* @ private
* @ type { Array }
* /
langExtensions = [ ] ,
/ * *
* Output modifiers extensions used by this converter
* @ private
* @ type { Array }
* /
outputModifiers = [ ] ,
/ * *
* Event listeners
* @ private
* @ type { { } }
* /
listeners = { } ;
_constructor ( ) ;
/ * *
* Converter constructor
* @ private
* /
function _constructor ( ) {
converterOptions = converterOptions || { } ;
for ( var gOpt in globalOptions ) {
if ( globalOptions . hasOwnProperty ( gOpt ) ) {
options [ gOpt ] = globalOptions [ gOpt ] ;
}
} // Merge options
if ( typeof converterOptions === 'object' ) {
for ( var opt in converterOptions ) {
if ( converterOptions . hasOwnProperty ( opt ) ) {
options [ opt ] = converterOptions [ opt ] ;
}
}
} else {
throw Error ( 'Converter expects the passed parameter to be an object, but ' + typeof converterOptions + ' was passed instead.' ) ;
}
if ( options . extensions ) {
showdown . helper . forEach ( options . extensions , _parseExtension ) ;
}
}
/ * *
* Parse extension
* @ param { * } ext
* @ param { string } [ name = '' ]
* @ private
* /
function _parseExtension ( ext , name ) {
name = name || null ; // If it's a string, the extension was previously loaded
if ( showdown . helper . isString ( ext ) ) {
ext = showdown . helper . stdExtName ( ext ) ;
name = ext ; // LEGACY_SUPPORT CODE
if ( showdown . extensions [ ext ] ) {
console . warn ( 'DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' + 'Please inform the developer that the extension should be updated!' ) ;
legacyExtensionLoading ( showdown . extensions [ ext ] , ext ) ;
return ; // END LEGACY SUPPORT CODE
} else if ( ! showdown . helper . isUndefined ( extensions [ ext ] ) ) {
ext = extensions [ ext ] ;
} else {
throw Error ( 'Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.' ) ;
}
}
if ( typeof ext === 'function' ) {
ext = ext ( ) ;
}
if ( ! showdown . helper . isArray ( ext ) ) {
ext = [ ext ] ;
}
var validExt = validate ( ext , name ) ;
if ( ! validExt . valid ) {
throw Error ( validExt . error ) ;
}
for ( var i = 0 ; i < ext . length ; ++ i ) {
switch ( ext [ i ] . type ) {
case 'lang' :
langExtensions . push ( ext [ i ] ) ;
break ;
case 'output' :
outputModifiers . push ( ext [ i ] ) ;
break ;
}
if ( ext [ i ] . hasOwnProperty ( listeners ) ) {
for ( var ln in ext [ i ] . listeners ) {
if ( ext [ i ] . listeners . hasOwnProperty ( ln ) ) {
listen ( ln , ext [ i ] . listeners [ ln ] ) ;
}
}
}
}
}
/ * *
* LEGACY _SUPPORT
* @ param { * } ext
* @ param { string } name
* /
function legacyExtensionLoading ( ext , name ) {
if ( typeof ext === 'function' ) {
ext = ext ( new showdown . Converter ( ) ) ;
}
if ( ! showdown . helper . isArray ( ext ) ) {
ext = [ ext ] ;
}
var valid = validate ( ext , name ) ;
if ( ! valid . valid ) {
throw Error ( valid . error ) ;
}
for ( var i = 0 ; i < ext . length ; ++ i ) {
switch ( ext [ i ] . type ) {
case 'lang' :
langExtensions . push ( ext [ i ] ) ;
break ;
case 'output' :
outputModifiers . push ( ext [ i ] ) ;
break ;
default :
// should never reach here
throw Error ( 'Extension loader error: Type unrecognized!!!' ) ;
}
}
}
/ * *
* Listen to an event
* @ param { string } name
* @ param { function } callback
* /
function listen ( name , callback ) {
if ( ! showdown . helper . isString ( name ) ) {
throw Error ( 'Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given' ) ;
}
if ( typeof callback !== 'function' ) {
throw Error ( 'Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given' ) ;
}
if ( ! listeners . hasOwnProperty ( name ) ) {
listeners [ name ] = [ ] ;
}
listeners [ name ] . push ( callback ) ;
}
function rTrimInputText ( text ) {
var rsp = text . match ( /^\s*/ ) [ 0 ] . length ,
rgx = new RegExp ( '^\\s{0,' + rsp + '}' , 'gm' ) ;
return text . replace ( rgx , '' ) ;
}
/ * *
* Dispatch an event
* @ private
* @ param { string } evtName Event name
* @ param { string } text Text
* @ param { { } } options Converter Options
* @ param { { } } globals
* @ returns { string }
* /
this . _dispatch = function dispatch ( evtName , text , options , globals ) {
if ( listeners . hasOwnProperty ( evtName ) ) {
for ( var ei = 0 ; ei < listeners [ evtName ] . length ; ++ ei ) {
var nText = listeners [ evtName ] [ ei ] ( evtName , text , this , options , globals ) ;
if ( nText && typeof nText !== 'undefined' ) {
text = nText ;
}
}
}
return text ;
} ;
/ * *
* Listen to an event
* @ param { string } name
* @ param { function } callback
* @ returns { showdown . Converter }
* /
this . listen = function ( name , callback ) {
listen ( name , callback ) ;
return this ;
} ;
/ * *
* Converts a markdown string into HTML
* @ param { string } text
* @ returns { * }
* /
this . makeHtml = function ( text ) {
//check if text is not falsy
if ( ! text ) {
return text ;
}
var globals = {
gHtmlBlocks : [ ] ,
gHtmlMdBlocks : [ ] ,
gHtmlSpans : [ ] ,
gUrls : { } ,
gTitles : { } ,
gDimensions : { } ,
gListLevel : 0 ,
hashLinkCounts : { } ,
langExtensions : langExtensions ,
outputModifiers : outputModifiers ,
converter : this ,
ghCodeBlocks : [ ]
} ; // attacklab: Replace ~ with ~T
// This lets us use tilde 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' ) ; // attacklab: 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
if ( options . smartIndentationFix ) {
text = rTrimInputText ( text ) ;
} // Make sure text begins and ends with a couple of newlines:
//text = '\n\n' + text + '\n\n';
text = text ; // detab
text = showdown . subParser ( 'detab' ) ( text , options , globals ) ; // stripBlankLines
text = showdown . subParser ( 'stripBlankLines' ) ( text , options , globals ) ; //run languageExtensions
showdown . helper . forEach ( langExtensions , function ( ext ) {
text = showdown . subParser ( 'runExtension' ) ( ext , text , options , globals ) ;
} ) ; // run the sub parsers
text = showdown . subParser ( 'hashPreCodeTags' ) ( text , options , globals ) ;
text = showdown . subParser ( 'githubCodeBlocks' ) ( text , options , globals ) ;
text = showdown . subParser ( 'hashHTMLBlocks' ) ( text , options , globals ) ;
text = showdown . subParser ( 'hashHTMLSpans' ) ( 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 tildes
text = text . replace ( /~T/g , '~' ) ; // Run output modifiers
showdown . helper . forEach ( outputModifiers , function ( ext ) {
text = showdown . subParser ( 'runExtension' ) ( ext , text , options , globals ) ;
} ) ;
return text ;
} ;
/ * *
* Set an option of this Converter instance
* @ param { string } key
* @ param { * } value
* /
this . setOption = function ( key , value ) {
options [ key ] = value ;
} ;
/ * *
* Get the option of this Converter instance
* @ param { string } key
* @ returns { * }
* /
this . getOption = function ( key ) {
return options [ key ] ;
} ;
/ * *
* Get the options of this Converter instance
* @ returns { { } }
* /
this . getOptions = function ( ) {
return options ;
} ;
/ * *
* Add extension to THIS converter
* @ param { { } } extension
* @ param { string } [ name = null ]
* /
this . addExtension = function ( extension , name ) {
name = name || null ;
_parseExtension ( extension , name ) ;
} ;
/ * *
* Use a global registered extension with THIS converter
* @ param { string } extensionName Name of the previously registered extension
* /
this . useExtension = function ( extensionName ) {
_parseExtension ( extensionName ) ;
} ;
/ * *
* Set the flavor THIS converter should use
* @ param { string } name
* /
this . setFlavor = function ( name ) {
if ( flavor . hasOwnProperty ( name ) ) {
var preset = flavor [ name ] ;
for ( var option in preset ) {
if ( preset . hasOwnProperty ( option ) ) {
options [ option ] = preset [ option ] ;
}
}
}
} ;
/ * *
* Remove an extension from THIS converter .
* Note : This is a costly operation . It ' s better to initialize a new converter
* and specify the extensions you wish to use
* @ param { Array } extension
* /
this . removeExtension = function ( extension ) {
if ( ! showdown . helper . isArray ( extension ) ) {
extension = [ extension ] ;
}
for ( var a = 0 ; a < extension . length ; ++ a ) {
var ext = extension [ a ] ;
for ( var i = 0 ; i < langExtensions . length ; ++ i ) {
if ( langExtensions [ i ] === ext ) {
langExtensions [ i ] . splice ( i , 1 ) ;
}
}
for ( var ii = 0 ; ii < outputModifiers . length ; ++ i ) {
if ( outputModifiers [ ii ] === ext ) {
outputModifiers [ ii ] . splice ( i , 1 ) ;
}
}
}
} ;
/ * *
* Get all extension of THIS converter
* @ returns { { language : Array , output : Array } }
* /
this . getAllExtensions = function ( ) {
return {
language : langExtensions ,
output : outputModifiers
} ;
} ;
} ;
/ * *
* Turn Markdown link shortcuts into XHTML < a > tags .
* /
showdown . subParser ( 'anchors' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'anchors.before' , text , options , globals ) ;
var writeAnchorTag = function ( wholeMatch , m1 , m2 , m3 , m4 , m5 , m6 , m7 ) {
if ( showdown . helper . isUndefined ( m7 ) ) {
m7 = '' ;
}
wholeMatch = m1 ;
var linkText = m2 ,
linkId = m3 . toLowerCase ( ) ,
url = m4 ,
title = m7 ;
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 {
if ( wholeMatch . search ( /\(\s*\)$/m ) > - 1 ) {
// Special case for explicit empty url
url = '' ;
} else {
return wholeMatch ;
}
}
}
url = showdown . helper . escapeCharacters ( url , '*_' , false ) ;
var result = '<a href="' + url + '"' ;
if ( title !== '' && title !== null ) {
title = title . replace ( /"/g , '"' ) ;
title = showdown . helper . escapeCharacters ( title , '*_' , false ) ;
result += ' title="' + title + '"' ;
}
result += '>' + linkText + '</a>' ;
return result ;
} ; // First, handle reference-style links: [link text] [id]
/ *
text = text . replace ( /
( // wrap whole match in $1
\ [
(
( ? :
\ [ [ ^ \ ] ] * \ ] // allow brackets nested one level
|
[ ^ \ [ ] // or anything else
) *
)
\ ]
[ ] ? // one optional space
( ? : \ n [ ] * ) ? // one optional newline followed by spaces
\ [
( . * ? ) // id = $3
\ ]
) ( ) ( ) ( ) ( ) // pad remaining backreferences
/ g , _ D o A n c h o r s _ c a l l b a c k ) ;
* /
text = text . replace ( /(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g , writeAnchorTag ) ; //
// Next, inline-style links: [link text](url "optional title")
//
/ *
text = text . replace ( /
( // wrap whole match in $1
\ [
(
( ? :
\ [ [ ^ \ ] ] * \ ] // allow brackets nested one level
|
[ ^ \ [ \ ] ] // or anything else
)
)
\ ]
\ ( // literal paren
[ \ t ] *
( ) // no id, so leave $3 empty
< ? ( . * ? ) > ? // href = $4
[ \ t ] *
( // $5
( [ ' " ] ) // quote char = $6
( . * ? ) // Title = $7
\ 6 // matching quote
[ \ t ] * // ignore any spaces/tabs between closing quote and )
) ? // title is optional
\ )
)
/ g , w r i t e A n c h o r T a g ) ;
* /
text = text . replace ( /(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g , writeAnchorTag ) ; //
// Last, 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 ( /
( // wrap whole match in $1
\ [
( [ ^ \ [ \ ] ] + ) // link text = $2; can't contain '[' or ']'
\ ]
) ( ) ( ) ( ) ( ) ( ) // pad rest of backreferences
/ g , w r i t e A n c h o r T a g ) ;
* /
text = text . replace ( /(\[([^\[\]]+)])()()()()()/g , writeAnchorTag ) ;
text = globals . converter . _dispatch ( 'anchors.after' , text , options , globals ) ;
return text ;
} ) ;
showdown . subParser ( 'autoLinks' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'autoLinks.before' , text , options , globals ) ;
var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi ,
delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi ,
simpleMailRegex = /(?:^|[ \n\t])([A-Za-z0-9!#$%&'*+-/=?^_`\{|}~\.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?:$|[ \n\t])/gi ,
delimMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi ;
text = text . replace ( delimUrlRegex , replaceLink ) ;
text = text . replace ( delimMailRegex , replaceMail ) ; // simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi,
// Email addresses: <address@domain.foo>
if ( options . simplifiedAutoLink ) {
text = text . replace ( simpleURLRegex , replaceLink ) ;
text = text . replace ( simpleMailRegex , replaceMail ) ;
}
function replaceLink ( wm , link ) {
var lnkTxt = link ;
if ( /^www\./i . test ( link ) ) {
link = link . replace ( /^www\./i , 'http://www.' ) ;
}
return '<a href="' + link + '">' + lnkTxt + '</a>' ;
}
function replaceMail ( wholeMatch , m1 ) {
var unescapedStr = showdown . subParser ( 'unescapeSpecialChars' ) ( m1 ) ;
return showdown . subParser ( 'encodeEmailAddress' ) ( unescapedStr ) ;
}
text = globals . converter . _dispatch ( 'autoLinks.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
* These are all the transformations that form block - level
* tags like paragraphs , headers , and list items .
* /
showdown . subParser ( 'blockGamut' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'blockGamut.before' , text , options , globals ) ; // 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:
var 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 ) ;
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 ) ;
text = globals . converter . _dispatch ( 'blockGamut.after' , text , options , globals ) ;
return text ;
} ) ;
showdown . subParser ( 'blockQuotes' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'blockQuotes.before' , text , options , globals ) ;
/ *
text = text . replace ( /
( // Wrap whole match in $1
(
^ [ \ t ] * > [ \ t ] ? // '>' at the start of a line
. + \ n // rest of the first line
( . + \ n ) * // subsequent consecutive lines
\ n * // blanks
) +
)
/ g m , f u n c t i o n ( ) { . . . } ) ;
* /
text = text . replace ( /((^[ \t]{0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm , function ( wholeMatch , m1 ) {
var bq = m1 ; // attacklab: hack around Konqueror 3.5.4 bug:
// "----------bug".replace(/^-/g,"") == "bug"
bq = bq . replace ( /^[ \t]*>[ \t]?/gm , '~0' ) ; // 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>)/gm , function ( wholeMatch , m1 ) {
var pre = m1 ; // attacklab: hack around Konqueror 3.5.4 bug:
pre = pre . replace ( /^ /mg , '~0' ) ;
pre = pre . replace ( /~0/g , '' ) ;
return pre ;
} ) ;
return showdown . subParser ( 'hashBlock' ) ( '<blockquote>\n' + bq + '\n</blockquote>' , options , globals ) ;
} ) ;
text = globals . converter . _dispatch ( 'blockQuotes.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
* Process Markdown ` <pre><code> ` blocks .
* /
showdown . subParser ( 'codeBlocks' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'codeBlocks.before' , text , options , globals ) ;
/ *
text = text . replace ( text ,
/ ( ? : \ n \ n | ^ )
( // $1 = the code block -- one or more lines, starting with a space/tab
( ? :
( ? : [ ] { 4 } | \ t ) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
. * \ n +
) +
)
( \ n * [ ] { 0 , 3 } [ ^ \ t \ n ] | ( ? = ~ 0 ) ) // attacklab: g_tab_width
/ g , f u n c t i o n ( ) { . . . } ) ;
* /
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += '~0' ;
var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g ;
text = text . replace ( pattern , function ( wholeMatch , m1 , m2 ) {
var codeblock = m1 ,
nextChar = m2 ,
end = '\n' ;
codeblock = showdown . subParser ( 'outdent' ) ( codeblock ) ;
codeblock = showdown . subParser ( 'encodeCode' ) ( codeblock ) ;
codeblock = showdown . subParser ( 'detab' ) ( codeblock ) ;
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 ;
} ) ; // attacklab: strip sentinel
text = text . replace ( /~0/ , '' ) ;
text = globals . converter . _dispatch ( 'codeBlocks.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
*
* * Backtick quotes are used for < code > < / c o d e > s p a n s .
*
* * 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 < / c o d e > a t t h e p r o m p t . < / 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 ` < / c o d e > . . .
* /
showdown . subParser ( 'codeSpans' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'codeSpans.before' , text , options , globals ) ;
/ *
text = text . replace ( /
( ^ | [ ^ \ \ ] ) // Character before opening ` can't be a backslash
( ` +) // $ 2 = Opening run of `
( // $3 = The code block
[ ^ \ r ] * ?
[ ^ ` ] // attacklab: work around lack of lookbehind
)
\ 2 // Matching closer
( ? ! ` )
/ g m , f u n c t i o n ( ) { . . . } ) ;
* /
if ( typeof text === 'undefined' ) {
text = '' ;
}
text = text . replace ( /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm , function ( wholeMatch , m1 , m2 , m3 ) {
var c = m3 ;
c = c . replace ( /^([ \t]*)/g , '' ) ; // leading whitespace
c = c . replace ( /[ \t]*$/g , '' ) ; // trailing whitespace
c = showdown . subParser ( 'encodeCode' ) ( c ) ;
return m1 + '<code>' + c + '</code>' ;
} ) ;
text = globals . converter . _dispatch ( 'codeSpans.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
* Convert all tabs to spaces
* /
showdown . subParser ( 'detab' , function ( text ) {
'use strict' ; // 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 , function ( wholeMatch , m1 ) {
var leadingText = m1 ,
numSpaces = 4 - leadingText . length % 4 ; // g_tab_width
// there *must* be a better way to do this:
for ( var 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 ;
} ) ;
/ * *
* Smart processing for ampersands and angle brackets that need to be encoded .
* /
showdown . subParser ( 'encodeAmpsAndAngles' , function ( text ) {
'use strict' ; // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
// http://bumppo.net/projects/amputator/
text = text . replace ( /&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g , '&' ) ; // Encode naked <'s
text = text . replace ( /<(?![a-z\/?\$!])/gi , '<' ) ;
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' , function ( text ) {
'use strict' ;
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' , function ( text ) {
'use strict' ; // Encode all ampersands; HTML entities are not
// entities within a Markdown code span.
text = text . replace ( /&/g , '&' ) ; // Do the angle bracket song and dance:
text = text . replace ( /</g , '<' ) ;
text = text . replace ( />/g , '>' ) ; // Now, escape characters that are magic in Markdown:
text = showdown . helper . escapeCharacters ( text , '*_{}[]\\' , false ) ; // jj the line above breaks this:
//---
//* Item
// 1. Subitem
// special char: *
// ---
return text ;
} ) ;
/ * *
* Input : an email address , e . g . "foo@example.com"
*
* Output : the email address as a mailto link , with each character
* of the address encoded as either a decimal or hex entity , in
* the hopes of foiling most address harvesting spam bots . E . g . :
*
* < a href = " & # x6D ; & # 97 ; & # 105 ; & # 108 ; & # x74 ; & # 111 ; : & # 102 ; & # 111 ; & # 111 ; & # 64 ; & # 101 ;
* x & # x61 ; & # 109 ; & # x70 ; & # 108 ; & # x65 ; & # x2E ; & # 99 ; & # 111 ; & # 109 ; " > & # 102 ; & # 111 ; & # 111 ;
* & # 64 ; & # 101 ; x & # x61 ; & # 109 ; & # x70 ; & # 108 ; & # x65 ; & # x2E ; & # 99 ; & # 111 ; & # 109 ; < / a >
*
* Based on a filter by Matthew Wickline , posted to the BBEdit - Talk
* mailing list : < http : //tinyurl.com/yu7ue>
*
* /
showdown . subParser ( 'encodeEmailAddress' , function ( addr ) {
'use strict' ;
var encode = [ function ( ch ) {
return '&#' + ch . charCodeAt ( 0 ) + ';' ;
} , function ( ch ) {
return '&#x' + ch . charCodeAt ( 0 ) . toString ( 16 ) + ';' ;
} , function ( ch ) {
return ch ;
} ] ;
addr = 'mailto:' + addr ;
addr = addr . replace ( /./g , function ( ch ) {
if ( ch === '@' ) {
// this *must* be encoded. I insist.
ch = encode [ Math . floor ( Math . random ( ) * 2 ) ] ( ch ) ;
} else if ( ch !== ':' ) {
// leave ':' alone (to spot mailto: later)
var 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 ;
} ) ;
addr = '<a href="' + addr + '">' + addr + '</a>' ;
addr = addr . replace ( /">.+:/g , '">' ) ; // strip the mailto: from the visible part
return addr ;
} ) ;
/ * *
* Within tags -- meaning between < and > -- encode [ \ ` * _] so they
* don ' t conflict with their use in Markdown for code , italics and strong .
* /
showdown . subParser ( 'escapeSpecialCharsWithinTagAttributes' , function ( text ) {
'use strict' ; // Build a regex to find HTML tags and comments. See Friedl's
// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi ;
text = text . replace ( regex , function ( wholeMatch ) {
var tag = wholeMatch . replace ( /(.)<\/?code>(?=.)/g , '$1`' ) ;
tag = showdown . helper . escapeCharacters ( tag , '\\`*_' , false ) ;
return tag ;
} ) ;
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' , function ( text , options , globals ) {
'use strict' ; // early exit if option is not enabled
if ( ! options . ghCodeBlocks ) {
return text ;
}
text = globals . converter . _dispatch ( 'githubCodeBlocks.before' , text , options , globals ) ;
text += '~0' ;
text = text . replace ( /(?:^|\n)```(.*)\n([\s\S]*?)\n```/g , function ( wholeMatch , language , codeblock ) {
var end = options . omitExtraWLInCodeBlocks ? '' : '\n' ; // First parse the github code block
codeblock = showdown . subParser ( 'encodeCode' ) ( codeblock ) ;
codeblock = showdown . subParser ( 'detab' ) ( codeblock ) ;
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 : codeblock
} ) - 1 ) + 'G\n\n' ;
} ) ; // attacklab: strip sentinel
text = text . replace ( /~0/ , '' ) ;
return globals . converter . _dispatch ( 'githubCodeBlocks.after' , text , options , globals ) ;
} ) ;
showdown . subParser ( 'hashBlock' , function ( text , options , globals ) {
'use strict' ;
text = text . replace ( /(^\n+|\n+$)/g , '' ) ;
return '\n\n~K' + ( globals . gHtmlBlocks . push ( text ) - 1 ) + 'K\n\n' ;
} ) ;
showdown . subParser ( 'hashElement' , function ( text , options , globals ) {
'use strict' ;
return function ( wholeMatch , m1 ) {
var 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' , function ( text , options , globals ) {
'use strict' ;
var 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' ] ,
repFunc = function ( wholeMatch , match , left , right ) {
var 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' ;
} ;
for ( var i = 0 ; i < blockTags . length ; ++ i ) {
text = showdown . helper . replaceRecursiveRegExp ( text , repFunc , '^(?: |\\t){0,3}<' + blockTags [ i ] + '\\b[^>]*>' , '</' + blockTags [ i ] + '>' , 'gim' ) ;
} // 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 = text . replace ( /(<!--[\s\S]*?-->)/g , showdown . subParser ( 'hashElement' ) ( text , options , globals ) ) ; // 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' , function ( text , config , globals ) {
'use strict' ;
var matches = showdown . helper . matchRecursiveRegExp ( text , '<code\\b[^>]*>' , '</code>' , 'gi' ) ;
for ( var i = 0 ; i < matches . length ; ++ i ) {
text = text . replace ( matches [ i ] [ 0 ] , '~L' + ( globals . gHtmlSpans . push ( matches [ i ] [ 0 ] ) - 1 ) + 'L' ) ;
}
return text ;
} ) ;
/ * *
* Unhash HTML spans
* /
showdown . subParser ( 'unhashHTMLSpans' , function ( text , config , globals ) {
'use strict' ;
for ( var i = 0 ; i < globals . gHtmlSpans . length ; ++ i ) {
text = text . replace ( '~L' + i + 'L' , globals . gHtmlSpans [ i ] ) ;
}
return text ;
} ) ;
/ * *
* Hash span elements that should not be parsed as markdown
* /
showdown . subParser ( 'hashPreCodeTags' , function ( text , config , globals ) {
'use strict' ;
var repFunc = function ( wholeMatch , match , left , right ) {
// encode html entities
var codeblock = left + showdown . subParser ( 'encodeCode' ) ( match ) + right ;
return '\n\n~G' + ( globals . ghCodeBlocks . push ( {
text : wholeMatch ,
codeblock : codeblock
} ) - 1 ) + 'G\n\n' ;
} ;
text = showdown . helper . replaceRecursiveRegExp ( text , repFunc , '^(?: |\\t){0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>' , '^(?: |\\t){0,3}</code>\\s*</pre>' , 'gim' ) ;
return text ;
} ) ;
showdown . subParser ( 'headers' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'headers.before' , text , options , globals ) ;
var prefixHeader = options . prefixHeaderId ,
headerLevelStart = isNaN ( parseInt ( options . headerLevelStart ) ) ? 1 : parseInt ( options . headerLevelStart ) ,
// Set text-style headers:
// Header 1
// ========
//
// Header 2
// --------
//
setextRegexH1 = options . smoothLivePreview ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm ,
setextRegexH2 = options . smoothLivePreview ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm ;
text = text . replace ( setextRegexH1 , function ( wholeMatch , m1 ) {
var spanGamut = showdown . subParser ( 'spanGamut' ) ( m1 , options , globals ) ,
hID = options . noHeaderId ? '' : ' id="' + headerId ( m1 ) + '"' ,
hLevel = headerLevelStart ,
hashBlock = '<h' + hLevel + hID + '>' + spanGamut + '</h' + hLevel + '>' ;
return showdown . subParser ( 'hashBlock' ) ( hashBlock , options , globals ) ;
} ) ;
text = text . replace ( setextRegexH2 , function ( matchFound , m1 ) {
var spanGamut = showdown . subParser ( 'spanGamut' ) ( m1 , options , globals ) ,
hID = options . noHeaderId ? '' : ' id="' + headerId ( m1 ) + '"' ,
hLevel = headerLevelStart + 1 ,
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
//
text = text . replace ( /^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm , function ( wholeMatch , m1 , m2 ) {
var span = showdown . subParser ( 'spanGamut' ) ( m2 , options , globals ) ,
hID = options . noHeaderId ? '' : ' id="' + headerId ( m2 ) + '"' ,
hLevel = headerLevelStart - 1 + m1 . length ,
header = '<h' + hLevel + hID + '>' + span + '</h' + hLevel + '>' ;
return showdown . subParser ( 'hashBlock' ) ( header , options , globals ) ;
} ) ;
function headerId ( m ) {
var title ,
escapedId = m . replace ( /[^\w]/g , '' ) . toLowerCase ( ) ;
if ( globals . hashLinkCounts [ escapedId ] ) {
title = escapedId + '-' + globals . hashLinkCounts [ escapedId ] ++ ;
} else {
title = escapedId ;
globals . hashLinkCounts [ escapedId ] = 1 ;
} // Prefix id to prevent causing inadvertent pre-existing style matches.
if ( prefixHeader === true ) {
prefixHeader = 'section' ;
}
if ( showdown . helper . isString ( prefixHeader ) ) {
return prefixHeader + title ;
}
return title ;
}
text = globals . converter . _dispatch ( 'headers.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
* Turn Markdown image shortcuts into < img > tags .
* /
showdown . subParser ( 'images' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'images.before' , text , options , globals ) ;
var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g ,
referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g ;
function writeImageTag ( wholeMatch , altText , linkId , url , width , height , m5 , title ) {
var gUrls = globals . gUrls ,
gTitles = globals . gTitles ,
gDims = globals . gDimensions ;
linkId = linkId . toLowerCase ( ) ;
if ( ! title ) {
title = '' ;
}
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 , '"' ) ;
altText = showdown . helper . escapeCharacters ( altText , '*_' , false ) ;
url = showdown . helper . escapeCharacters ( url , '*_' , false ) ;
var result = '<img src="' + url + '" alt="' + altText + '"' ;
if ( title ) {
title = title . replace ( /"/g , '"' ) ;
title = showdown . helper . escapeCharacters ( title , '*_' , false ) ;
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")
text = text . replace ( inlineRegExp , writeImageTag ) ;
text = globals . converter . _dispatch ( 'images.after' , text , options , globals ) ;
return text ;
} ) ;
showdown . subParser ( 'italicsAndBold' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'italicsAndBold.before' , text , options , globals ) ;
if ( options . literalMidWordUnderscores ) {
//underscores
// Since we are consuming a \s character, we need to add it
text = text . replace ( /(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm , '$1<strong>$2</strong>' ) ;
text = text . replace ( /(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm , '$1<em>$2</em>' ) ; //asterisks
text = text . replace ( /(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g , '<strong>$2</strong>' ) ;
text = text . replace ( /(\*)(?=\S)([^\r]*?\S)\1/g , '<em>$2</em>' ) ;
} else {
// <strong> must go first:
text = text . replace ( /(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g , '<strong>$2</strong>' ) ;
text = text . replace ( /(\*|_)(?=\S)([^\r]*?\S)\1/g , '<em>$2</em>' ) ;
}
text = globals . converter . _dispatch ( 'italicsAndBold.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
* Form HTML ordered ( numbered ) and unordered ( bulleted ) lists .
* /
showdown . subParser ( 'lists' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'lists.before' , text , options , globals ) ;
/ * *
* 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 , trimTrailing ) {
// 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' ;
var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm ,
isParagraphed = /\n[ \t]*\n(?!~0)/ . test ( listStr ) ;
listStr = listStr . replace ( rgx , function ( wholeMatch , m1 , m2 , m3 , m4 , taskbtn , checked ) {
checked = checked && checked . trim ( ) !== '' ;
var item = showdown . subParser ( 'outdent' ) ( m4 , options , globals ) ,
bulletStyle = '' ; // Support for github tasklists
if ( taskbtn && options . tasklists ) {
bulletStyle = ' class="task-list-item" style="list-style-type: none;"' ;
item = item . replace ( /^[ \t]*\[(x|X| )?]/m , function ( ) {
var otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"' ;
if ( checked ) {
otp += ' checked' ;
}
otp += '>' ;
return otp ;
} ) ;
} // 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)
if ( isParagraphed ) {
item = showdown . subParser ( 'paragraphs' ) ( item , options , globals ) ;
} else {
item = showdown . subParser ( 'spanGamut' ) ( item , options , globals ) ;
}
}
item = '\n<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 ;
}
/ * *
* Check and parse consecutive lists ( better fix for issue # 142 )
* @ param { string } list
* @ param { string } listType
* @ param { boolean } trimTrailing
* @ returns { string }
* /
function parseConsecutiveLists ( list , listType , trimTrailing ) {
// check if we caught 2 or more consecutive lists by mistake
// we use the counterRgx, meaning if listType is UL we look for UL and vice versa
var counterRxg = listType === 'ul' ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm ,
subLists = [ ] ,
result = '' ;
if ( list . search ( counterRxg ) !== - 1 ) {
( function parseCL ( txt ) {
var pos = txt . search ( counterRxg ) ;
if ( pos !== - 1 ) {
// slice
result += '\n\n<' + listType + '>' + processListItems ( txt . slice ( 0 , pos ) , ! ! trimTrailing ) + '</' + listType + '>\n\n' ; // invert counterType and listType
listType = listType === 'ul' ? 'ol' : 'ul' ;
counterRxg = listType === 'ul' ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm ; //recurse
parseCL ( txt . slice ( pos ) ) ;
} else {
result += '\n\n<' + listType + '>' + processListItems ( txt , ! ! trimTrailing ) + '</' + listType + '>\n\n' ;
}
} ) ( list ) ;
for ( var i = 0 ; i < subLists . length ; ++ i ) { }
} else {
result = '\n\n<' + listType + '>' + processListItems ( list , ! ! trimTrailing ) + '</' + listType + '>\n\n' ;
}
return result ;
} // attacklab: add sentinel to hack around khtml/safari bug:
// http://bugs.webkit.org/show_bug.cgi?id=11231
text += '~0' ; // Re-usable pattern to match any entire ul or ol list:
var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm ;
if ( globals . gListLevel ) {
text = text . replace ( wholeList , function ( wholeMatch , list , m2 ) {
var listType = m2 . search ( /[*+-]/g ) > - 1 ? 'ul' : 'ol' ;
return parseConsecutiveLists ( list , listType , true ) ;
} ) ;
} else {
wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm ; //wholeList = /(\n\n|^\n?)( {0,3}([*+-]|\d+\.)[ \t]+[\s\S]+?)(?=(~0)|(\n\n(?!\t| {2,}| {0,3}([*+-]|\d+\.)[ \t])))/g;
text = text . replace ( wholeList , function ( wholeMatch , m1 , list , m3 ) {
var listType = m3 . search ( /[*+-]/g ) > - 1 ? 'ul' : 'ol' ;
return parseConsecutiveLists ( list , listType ) ;
} ) ;
} // attacklab: strip sentinel
text = text . replace ( /~0/ , '' ) ;
text = globals . converter . _dispatch ( 'lists.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
* Remove one level of line - leading tabs or spaces
* /
showdown . subParser ( 'outdent' , function ( text ) {
'use strict' ; // 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' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'paragraphs.before' , text , options , globals ) ; // Strip leading and trailing lines:
text = text . replace ( /^\n+/g , '' ) ;
text = text . replace ( /\n+$/g , '' ) ;
var grafs = text . split ( /\n{2,}/g ) ,
grafsOut = [ ] ,
end = grafs . length ; // Wrap <p> tags
for ( var i = 0 ; i < end ; i ++ ) {
var str = grafs [ i ] ; // if this is an HTML marker, copy it
if ( str . search ( /~(K|G)(\d+)\1/g ) >= 0 ) {
grafsOut . push ( str ) ;
} else {
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 ( i = 0 ; i < end ; i ++ ) {
var blockText = '' ,
grafsOutIt = grafsOut [ i ] ,
codeFlag = false ; // if this is a marker for an html block...
while ( grafsOutIt . search ( /~(K|G)(\d+)\1/ ) >= 0 ) {
var delim = RegExp . $1 ,
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 ) ;
} 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\n' ) ; // Strip leading and trailing lines:
text = text . replace ( /^\n+/g , '' ) ;
text = text . replace ( /\n+$/g , '' ) ;
return globals . converter . _dispatch ( 'paragraphs.after' , text , options , globals ) ;
} ) ;
/ * *
* Run extension
* /
showdown . subParser ( 'runExtension' , function ( ext , text , options , globals ) {
'use strict' ;
if ( ext . filter ) {
text = ext . filter ( text , globals . converter , options ) ;
} else if ( ext . regex ) {
// TODO remove this when old extension loading mechanism is deprecated
var re = ext . regex ;
if ( ! re instanceof RegExp ) {
re = new RegExp ( re , 'g' ) ;
}
text = text . replace ( re , ext . replace ) ;
}
return text ;
} ) ;
/ * *
* These are all the transformations that occur * within * block - level
* tags like paragraphs , headers , and list items .
* /
showdown . subParser ( 'spanGamut' , function ( text , options , globals ) {
'use strict' ;
text = globals . converter . _dispatch ( 'spanGamut.before' , text , options , globals ) ;
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 _DoAnchors(), because you can use < and >
// delimiters in inline links like [this](<url>).
text = showdown . subParser ( 'autoLinks' ) ( text , options , globals ) ;
text = showdown . subParser ( 'encodeAmpsAndAngles' ) ( text , options , globals ) ;
text = showdown . subParser ( 'italicsAndBold' ) ( text , options , globals ) ;
text = showdown . subParser ( 'strikethrough' ) ( text , options , globals ) ; // Do hard breaks:
text = text . replace ( / +\n/g , ' <br />\n' ) ;
text = globals . converter . _dispatch ( 'spanGamut.after' , text , options , globals ) ;
return text ;
} ) ;
showdown . subParser ( 'strikethrough' , function ( text , options , globals ) {
'use strict' ;
if ( options . strikethrough ) {
text = globals . converter . _dispatch ( 'strikethrough.before' , text , options , globals ) ;
text = text . replace ( /(?:~T){2}([\s\S]+?)(?:~T){2}/g , '<del>$1</del>' ) ;
text = globals . converter . _dispatch ( 'strikethrough.after' , text , options , globals ) ;
}
return text ;
} ) ;
/ * *
* 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 + /
* /
showdown . subParser ( 'stripBlankLines' , function ( text ) {
'use strict' ;
return text . replace ( /^[ \t]+$/mg , '' ) ;
} ) ;
/ * *
* Strips link definitions from text , stores the URLs and titles in
* hash references .
* Link defs are in the form : ^ [ id ] : url "optional title"
*
* ^ [ ] { 0 , 3 } \ [ ( . + ) \ ] : // id = $1 attacklab: g_tab_width - 1
* [ \ t ] *
* \ n ? // maybe *one* newline
* [ \ t ] *
* < ? ( \ S + ? ) > ? // url = $2
* [ \ t ] *
* \ n ? // maybe one newline
* [ \ t ] *
* ( ? :
* ( \ n * ) // any lines skipped = $3 attacklab: lookbehind removed
* [ " ( ]
* ( . + ? ) // title = $4
* [ " ) ]
* [ \ t ] *
* ) ? // title is optional
* ( ? : \ n + | $ )
* / g m ,
* function ( ) { ... } ) ;
*
* /
showdown . subParser ( 'stripLinkDefinitions' , function ( text , options , globals ) {
'use strict' ;
var 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 ; // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += '~0' ;
text = text . replace ( regex , function ( wholeMatch , linkId , url , width , height , blankLines , title ) {
linkId = linkId . toLowerCase ( ) ;
globals . gUrls [ linkId ] = showdown . subParser ( 'encodeAmpsAndAngles' ) ( url ) ; // 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 , '"' ) ;
}
if ( options . parseImgDimensions && width && height ) {
globals . gDimensions [ linkId ] = {
width : width ,
height : height
} ;
}
} // Completely remove the definition from the text
return '' ;
} ) ; // attacklab: strip sentinel
text = text . replace ( /~0/ , '' ) ;
return text ;
} ) ;
showdown . subParser ( 'tables' , function ( text , options , globals ) {
'use strict' ;
if ( ! options . tables ) {
return text ;
}
var tableRgx = /^[ \t]{0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|~0)/gm ;
function parseStyles ( sLine ) {
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 , style ) {
var id = '' ;
header = header . trim ( ) ;
if ( 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 , style ) {
var subText = showdown . subParser ( 'spanGamut' ) ( cell , options , globals ) ;
return '<td' + style + '>' + subText + '</td>\n' ;
}
function buildTable ( headers , cells ) {
var tb = '<table>\n<thead>\n<tr>\n' ,
tblLgn = headers . length ;
for ( var i = 0 ; i < tblLgn ; ++ i ) {
tb += headers [ i ] ;
}
tb += '</tr>\n</thead>\n<tbody>\n' ;
for ( i = 0 ; i < cells . length ; ++ i ) {
tb += '<tr>\n' ;
for ( var ii = 0 ; ii < tblLgn ; ++ ii ) {
tb += cells [ i ] [ ii ] ;
}
tb += '</tr>\n' ;
}
tb += '</tbody>\n</table>\n' ;
return tb ;
}
text = globals . converter . _dispatch ( 'tables.before' , text , options , globals ) ;
text = text . replace ( tableRgx , function ( rawTable ) {
var i ,
tableLines = rawTable . split ( '\n' ) ; // strip wrong first and last column if wrapped tables are used
for ( i = 0 ; i < tableLines . length ; ++ i ) {
if ( /^[ \t]{0,3}\|/ . test ( tableLines [ i ] ) ) {
tableLines [ i ] = tableLines [ i ] . replace ( /^[ \t]{0,3}\|/ , '' ) ;
}
if ( /\|[ \t]*$/ . test ( tableLines [ i ] ) ) {
tableLines [ i ] = tableLines [ i ] . replace ( /\|[ \t]*$/ , '' ) ;
}
}
var rawHeaders = tableLines [ 0 ] . split ( '|' ) . map ( function ( s ) {
return s . trim ( ) ;
} ) ,
rawStyles = tableLines [ 1 ] . split ( '|' ) . map ( function ( s ) {
return s . trim ( ) ;
} ) ,
rawCells = [ ] ,
headers = [ ] ,
styles = [ ] ,
cells = [ ] ;
tableLines . shift ( ) ;
tableLines . shift ( ) ;
for ( i = 0 ; i < tableLines . length ; ++ i ) {
if ( tableLines [ i ] . trim ( ) === '' ) {
continue ;
}
rawCells . push ( tableLines [ i ] . split ( '|' ) . map ( function ( s ) {
return s . trim ( ) ;
} ) ) ;
}
if ( rawHeaders . length < rawStyles . length ) {
return rawTable ;
}
for ( i = 0 ; i < rawStyles . length ; ++ i ) {
styles . push ( parseStyles ( rawStyles [ i ] ) ) ;
}
for ( i = 0 ; i < rawHeaders . length ; ++ i ) {
if ( showdown . helper . isUndefined ( styles [ i ] ) ) {
styles [ i ] = '' ;
}
headers . push ( parseHeaders ( rawHeaders [ i ] , styles [ i ] ) ) ;
}
for ( i = 0 ; i < rawCells . length ; ++ i ) {
var row = [ ] ;
for ( var 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 ) ;
} ) ;
text = globals . converter . _dispatch ( 'tables.after' , text , options , globals ) ;
return text ;
} ) ;
/ * *
* Swap back in all the special characters we ' ve hidden .
* /
showdown . subParser ( 'unescapeSpecialChars' , function ( text ) {
'use strict' ;
text = text . replace ( /~E(\d+)E/g , function ( wholeMatch , m1 ) {
var charCodeToReplace = parseInt ( m1 ) ;
return String . fromCharCode ( charCodeToReplace ) ;
} ) ;
return text ;
} ) ;
module . exports = showdown ;