converter.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /**
  2. * Created by Estevao on 31-05-2015.
  3. */
  4. /**
  5. * Showdown Converter class
  6. * @class
  7. * @param {object} [converterOptions]
  8. * @returns {Converter}
  9. */
  10. showdown.Converter = function (converterOptions) {
  11. 'use strict';
  12. var
  13. /**
  14. * Options used by this converter
  15. * @private
  16. * @type {{}}
  17. */
  18. options = {},
  19. /**
  20. * Language extensions used by this converter
  21. * @private
  22. * @type {Array}
  23. */
  24. langExtensions = [],
  25. /**
  26. * Output modifiers extensions used by this converter
  27. * @private
  28. * @type {Array}
  29. */
  30. outputModifiers = [],
  31. /**
  32. * The parser Order
  33. * @private
  34. * @type {string[]}
  35. */
  36. parserOrder = [
  37. 'githubCodeBlocks',
  38. 'hashHTMLBlocks',
  39. 'stripLinkDefinitions',
  40. 'blockGamut',
  41. 'unescapeSpecialChars'
  42. ];
  43. _constructor();
  44. /**
  45. * Converter constructor
  46. * @private
  47. */
  48. function _constructor() {
  49. converterOptions = converterOptions || {};
  50. for (var gOpt in globalOptions) {
  51. if (globalOptions.hasOwnProperty(gOpt)) {
  52. options[gOpt] = globalOptions[gOpt];
  53. }
  54. }
  55. // Merge options
  56. if (typeof converterOptions === 'object') {
  57. for (var opt in converterOptions) {
  58. if (converterOptions.hasOwnProperty(opt)) {
  59. options[opt] = converterOptions[opt];
  60. }
  61. }
  62. } else {
  63. throw Error('Converter expects the passed parameter to be an object, but ' + typeof converterOptions +
  64. ' was passed instead.');
  65. }
  66. if (options.extensions) {
  67. showdown.helper.forEach(options.extensions, _parseExtension);
  68. }
  69. }
  70. /**
  71. * Parse extension
  72. * @param {*} ext
  73. * @param {string} [name='']
  74. * @private
  75. */
  76. function _parseExtension(ext, name) {
  77. name = name || null;
  78. // If it's a string, the extension was previously loaded
  79. if (showdown.helper.isString(ext)) {
  80. ext = showdown.helper.stdExtName(ext);
  81. name = ext;
  82. // LEGACY_SUPPORT CODE
  83. if (showdown.extensions[ext]) {
  84. console.warn('DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' +
  85. 'Please inform the developer that the extension should be updated!');
  86. legacyExtensionLoading(showdown.extensions[ext], ext);
  87. return;
  88. // END LEGACY SUPPORT CODE
  89. } else if (!showdown.helper.isUndefined(extensions[ext])) {
  90. ext = extensions[ext];
  91. } else {
  92. throw Error('Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.');
  93. }
  94. }
  95. if (typeof ext === 'function') {
  96. ext = ext();
  97. }
  98. if (!showdown.helper.isArray(ext)) {
  99. ext = [ext];
  100. }
  101. var validExt = validate(ext, name);
  102. if (!validExt.valid) {
  103. throw Error(validExt.error);
  104. }
  105. for (var i = 0; i < ext.length; ++i) {
  106. switch (ext[i].type) {
  107. case 'lang':
  108. langExtensions.push(ext[i]);
  109. break;
  110. case 'output':
  111. outputModifiers.push(ext[i]);
  112. break;
  113. default:
  114. // should never reach here
  115. throw Error('Extension loader error: Type unrecognized!!!');
  116. }
  117. }
  118. }
  119. /**
  120. * LEGACY_SUPPORT
  121. * @param {*} ext
  122. * @param {string} name
  123. */
  124. function legacyExtensionLoading(ext, name) {
  125. if (typeof ext === 'function') {
  126. ext = ext(new showdown.Converter());
  127. }
  128. if (!showdown.helper.isArray(ext)) {
  129. ext = [ext];
  130. }
  131. var valid = validate(ext, name);
  132. if (!valid.valid) {
  133. throw Error(valid.error);
  134. }
  135. for (var i = 0; i < ext.length; ++i) {
  136. switch (ext[i].type) {
  137. case 'lang':
  138. langExtensions.push(ext[i]);
  139. break;
  140. case 'output':
  141. outputModifiers.push(ext[i]);
  142. break;
  143. default:// should never reach here
  144. throw Error('Extension loader error: Type unrecognized!!!');
  145. }
  146. }
  147. }
  148. /**
  149. * Converts a markdown string into HTML
  150. * @param {string} text
  151. * @returns {*}
  152. */
  153. this.makeHtml = function (text) {
  154. //check if text is not falsy
  155. if (!text) {
  156. return text;
  157. }
  158. var globals = {
  159. gHtmlBlocks: [],
  160. gUrls: {},
  161. gTitles: {},
  162. gDimensions: {},
  163. gListLevel: 0,
  164. hashLinkCounts: {},
  165. langExtensions: langExtensions,
  166. outputModifiers: outputModifiers,
  167. converter: this
  168. };
  169. // attacklab: Replace ~ with ~T
  170. // This lets us use tilde as an escape char to avoid md5 hashes
  171. // The choice of character is arbitrary; anything that isn't
  172. // magic in Markdown will work.
  173. text = text.replace(/~/g, '~T');
  174. // attacklab: Replace $ with ~D
  175. // RegExp interprets $ as a special character
  176. // when it's in a replacement string
  177. text = text.replace(/\$/g, '~D');
  178. // Standardize line endings
  179. text = text.replace(/\r\n/g, '\n'); // DOS to Unix
  180. text = text.replace(/\r/g, '\n'); // Mac to Unix
  181. // Make sure text begins and ends with a couple of newlines:
  182. text = '\n\n' + text + '\n\n';
  183. // detab
  184. text = showdown.subParser('detab')(text, options, globals);
  185. // stripBlankLines
  186. text = showdown.subParser('stripBlankLines')(text, options, globals);
  187. //run languageExtensions
  188. showdown.helper.forEach(langExtensions, function (ext) {
  189. text = showdown.subParser('runExtension')(ext, text, options, globals);
  190. });
  191. // Run all registered parsers
  192. for (var i = 0; i < parserOrder.length; ++i) {
  193. var name = parserOrder[i];
  194. text = parsers[name](text, options, globals);
  195. }
  196. // attacklab: Restore dollar signs
  197. text = text.replace(/~D/g, '$$');
  198. // attacklab: Restore tildes
  199. text = text.replace(/~T/g, '~');
  200. // Run output modifiers
  201. showdown.helper.forEach(outputModifiers, function (ext) {
  202. text = showdown.subParser('runExtension')(ext, text, options, globals);
  203. });
  204. return text;
  205. };
  206. /**
  207. * Set an option of this Converter instance
  208. * @param {string} key
  209. * @param {*} value
  210. */
  211. this.setOption = function (key, value) {
  212. options[key] = value;
  213. };
  214. /**
  215. * Get the option of this Converter instance
  216. * @param {string} key
  217. * @returns {*}
  218. */
  219. this.getOption = function (key) {
  220. return options[key];
  221. };
  222. /**
  223. * Get the options of this Converter instance
  224. * @returns {{}}
  225. */
  226. this.getOptions = function () {
  227. return options;
  228. };
  229. /**
  230. * Add extension to THIS converter
  231. * @param {{}} extension
  232. * @param {string} [name=null]
  233. */
  234. this.addExtension = function (extension, name) {
  235. name = name || null;
  236. _parseExtension(extension, name);
  237. };
  238. /**
  239. * Use a global registered extension with THIS converter
  240. * @param {string} extensionName Name of the previously registered extension
  241. */
  242. this.useExtension = function (extensionName) {
  243. _parseExtension(extensionName);
  244. };
  245. /**
  246. * Set the flavor THIS converter should use
  247. * @param {string} name
  248. */
  249. this.setFlavor = function (name) {
  250. if (flavor.hasOwnProperty(name)) {
  251. var preset = flavor[name];
  252. for (var option in preset) {
  253. if (preset.hasOwnProperty(option)) {
  254. options[option] = preset[option];
  255. }
  256. }
  257. }
  258. };
  259. /**
  260. * Remove an extension from THIS converter.
  261. * Note: This is a costly operation. It's better to initialize a new converter
  262. * and specify the extensions you wish to use
  263. * @param {Array} extension
  264. */
  265. this.removeExtension = function (extension) {
  266. if (!showdown.helper.isArray(extension)) {
  267. extension = [extension];
  268. }
  269. for (var a = 0; a < extension.length; ++a) {
  270. var ext = extension[a];
  271. for (var i = 0; i < langExtensions.length; ++i) {
  272. if (langExtensions[i] === ext) {
  273. langExtensions[i].splice(i, 1);
  274. }
  275. }
  276. for (var ii = 0; ii < outputModifiers.length; ++i) {
  277. if (outputModifiers[ii] === ext) {
  278. outputModifiers[ii].splice(i, 1);
  279. }
  280. }
  281. }
  282. };
  283. /**
  284. * Get all extension of THIS converter
  285. * @returns {{language: Array, output: Array}}
  286. */
  287. this.getAllExtensions = function () {
  288. return {
  289. language: langExtensions,
  290. output: outputModifiers
  291. };
  292. };
  293. };