helpers.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /**
  2. * showdownjs helper functions
  3. */
  4. if (!showdown.hasOwnProperty('helper')) {
  5. showdown.helper = {};
  6. }
  7. /**
  8. * Check if var is string
  9. * @static
  10. * @param {string} a
  11. * @returns {boolean}
  12. */
  13. showdown.helper.isString = function (a) {
  14. 'use strict';
  15. return (typeof a === 'string' || a instanceof String);
  16. };
  17. /**
  18. * Check if var is a function
  19. * @static
  20. * @param {*} a
  21. * @returns {boolean}
  22. */
  23. showdown.helper.isFunction = function (a) {
  24. 'use strict';
  25. var getType = {};
  26. return a && getType.toString.call(a) === '[object Function]';
  27. };
  28. /**
  29. * isArray helper function
  30. * @static
  31. * @param {*} a
  32. * @returns {boolean}
  33. */
  34. showdown.helper.isArray = function (a) {
  35. 'use strict';
  36. return a.constructor === Array;
  37. };
  38. /**
  39. * Check if value is undefined
  40. * @static
  41. * @param {*} value The value to check.
  42. * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
  43. */
  44. showdown.helper.isUndefined = function (value) {
  45. 'use strict';
  46. return typeof value === 'undefined';
  47. };
  48. /**
  49. * ForEach helper function
  50. * Iterates over Arrays and Objects (own properties only)
  51. * @static
  52. * @param {*} obj
  53. * @param {function} callback Accepts 3 params: 1. value, 2. key, 3. the original array/object
  54. */
  55. showdown.helper.forEach = function (obj, callback) {
  56. 'use strict';
  57. // check if obj is defined
  58. if (showdown.helper.isUndefined(obj)) {
  59. throw new Error('obj param is required');
  60. }
  61. if (showdown.helper.isUndefined(callback)) {
  62. throw new Error('callback param is required');
  63. }
  64. if (!showdown.helper.isFunction(callback)) {
  65. throw new Error('callback param must be a function/closure');
  66. }
  67. if (typeof obj.forEach === 'function') {
  68. obj.forEach(callback);
  69. } else if (showdown.helper.isArray(obj)) {
  70. for (var i = 0; i < obj.length; i++) {
  71. callback(obj[i], i, obj);
  72. }
  73. } else if (typeof (obj) === 'object') {
  74. for (var prop in obj) {
  75. if (obj.hasOwnProperty(prop)) {
  76. callback(obj[prop], prop, obj);
  77. }
  78. }
  79. } else {
  80. throw new Error('obj does not seem to be an array or an iterable object');
  81. }
  82. };
  83. /**
  84. * Standardidize extension name
  85. * @static
  86. * @param {string} s extension name
  87. * @returns {string}
  88. */
  89. showdown.helper.stdExtName = function (s) {
  90. 'use strict';
  91. return s.replace(/[_?*+\/\\.^-]/g, '').replace(/\s/g, '').toLowerCase();
  92. };
  93. function escapeCharactersCallback(wholeMatch, m1) {
  94. 'use strict';
  95. var charCodeToEscape = m1.charCodeAt(0);
  96. return '~E' + charCodeToEscape + 'E';
  97. }
  98. /**
  99. * Callback used to escape characters when passing through String.replace
  100. * @static
  101. * @param {string} wholeMatch
  102. * @param {string} m1
  103. * @returns {string}
  104. */
  105. showdown.helper.escapeCharactersCallback = escapeCharactersCallback;
  106. /**
  107. * Escape characters in a string
  108. * @static
  109. * @param {string} text
  110. * @param {string} charsToEscape
  111. * @param {boolean} afterBackslash
  112. * @returns {XML|string|void|*}
  113. */
  114. showdown.helper.escapeCharacters = function (text, charsToEscape, afterBackslash) {
  115. 'use strict';
  116. // First we have to escape the escape characters so that
  117. // we can build a character class out of them
  118. var regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])';
  119. if (afterBackslash) {
  120. regexString = '\\\\' + regexString;
  121. }
  122. var regex = new RegExp(regexString, 'g');
  123. text = text.replace(regex, escapeCharactersCallback);
  124. return text;
  125. };
  126. var rgxFindMatchPos = function (str, left, right, flags) {
  127. 'use strict';
  128. var f = flags || '',
  129. g = f.indexOf('g') > -1,
  130. x = new RegExp(left + '|' + right, 'g' + f.replace(/g/g, '')),
  131. l = new RegExp(left, f.replace(/g/g, '')),
  132. pos = [],
  133. t, s, m, start, end;
  134. do {
  135. t = 0;
  136. while ((m = x.exec(str))) {
  137. if (l.test(m[0])) {
  138. if (!(t++)) {
  139. s = x.lastIndex;
  140. start = s - m[0].length;
  141. }
  142. } else if (t) {
  143. if (!--t) {
  144. end = m.index + m[0].length;
  145. var obj = {
  146. left: {start: start, end: s},
  147. match: {start: s, end: m.index},
  148. right: {start: m.index, end: end},
  149. wholeMatch: {start: start, end: end}
  150. };
  151. pos.push(obj);
  152. if (!g) {
  153. return pos;
  154. }
  155. }
  156. }
  157. }
  158. } while (t && (x.lastIndex = s));
  159. return pos;
  160. };
  161. /**
  162. * matchRecursiveRegExp
  163. *
  164. * (c) 2007 Steven Levithan <stevenlevithan.com>
  165. * MIT License
  166. *
  167. * Accepts a string to search, a left and right format delimiter
  168. * as regex patterns, and optional regex flags. Returns an array
  169. * of matches, allowing nested instances of left/right delimiters.
  170. * Use the "g" flag to return all matches, otherwise only the
  171. * first is returned. Be careful to ensure that the left and
  172. * right format delimiters produce mutually exclusive matches.
  173. * Backreferences are not supported within the right delimiter
  174. * due to how it is internally combined with the left delimiter.
  175. * When matching strings whose format delimiters are unbalanced
  176. * to the left or right, the output is intentionally as a
  177. * conventional regex library with recursion support would
  178. * produce, e.g. "<<x>" and "<x>>" both produce ["x"] when using
  179. * "<" and ">" as the delimiters (both strings contain a single,
  180. * balanced instance of "<x>").
  181. *
  182. * examples:
  183. * matchRecursiveRegExp("test", "\\(", "\\)")
  184. * returns: []
  185. * matchRecursiveRegExp("<t<<e>><s>>t<>", "<", ">", "g")
  186. * returns: ["t<<e>><s>", ""]
  187. * matchRecursiveRegExp("<div id=\"x\">test</div>", "<div\\b[^>]*>", "</div>", "gi")
  188. * returns: ["test"]
  189. */
  190. showdown.helper.matchRecursiveRegExp = function (str, left, right, flags) {
  191. 'use strict';
  192. var matchPos = rgxFindMatchPos (str, left, right, flags),
  193. results = [];
  194. for (var i = 0; i < matchPos.length; ++i) {
  195. results.push([
  196. str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
  197. str.slice(matchPos[i].match.start, matchPos[i].match.end),
  198. str.slice(matchPos[i].left.start, matchPos[i].left.end),
  199. str.slice(matchPos[i].right.start, matchPos[i].right.end)
  200. ]);
  201. }
  202. return results;
  203. };
  204. /**
  205. *
  206. * @param {string} str
  207. * @param {string|function} replacement
  208. * @param {string} left
  209. * @param {string} right
  210. * @param {string} flags
  211. * @returns {string}
  212. */
  213. showdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) {
  214. 'use strict';
  215. if (!showdown.helper.isFunction(replacement)) {
  216. var repStr = replacement;
  217. replacement = function () {
  218. return repStr;
  219. };
  220. }
  221. var matchPos = rgxFindMatchPos(str, left, right, flags),
  222. finalStr = str,
  223. lng = matchPos.length;
  224. if (lng > 0) {
  225. var bits = [];
  226. if (matchPos[0].wholeMatch.start !== 0) {
  227. bits.push(str.slice(0, matchPos[0].wholeMatch.start));
  228. }
  229. for (var i = 0; i < lng; ++i) {
  230. bits.push(
  231. replacement(
  232. str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
  233. str.slice(matchPos[i].match.start, matchPos[i].match.end),
  234. str.slice(matchPos[i].left.start, matchPos[i].left.end),
  235. str.slice(matchPos[i].right.start, matchPos[i].right.end)
  236. )
  237. );
  238. if (i < lng - 1) {
  239. bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start));
  240. }
  241. }
  242. if (matchPos[lng - 1].wholeMatch.end < str.length) {
  243. bits.push(str.slice(matchPos[lng - 1].wholeMatch.end));
  244. }
  245. finalStr = bits.join('');
  246. }
  247. return finalStr;
  248. };
  249. /**
  250. * Obfuscate an e-mail address through the use of Character Entities,
  251. * transforming ASCII characters into their equivalent decimal or hex entities.
  252. *
  253. * Since it has a random component, subsequent calls to this function produce different results
  254. *
  255. * @param {string} mail
  256. * @returns {string}
  257. */
  258. showdown.helper.encodeEmailAddress = function (mail) {
  259. 'use strict';
  260. var encode = [
  261. function (ch) {
  262. return '&#' + ch.charCodeAt(0) + ';';
  263. },
  264. function (ch) {
  265. return '&#x' + ch.charCodeAt(0).toString(16) + ';';
  266. },
  267. function (ch) {
  268. return ch;
  269. }
  270. ];
  271. mail = mail.replace(/./g, function (ch) {
  272. if (ch === '@') {
  273. // this *must* be encoded. I insist.
  274. ch = encode[Math.floor(Math.random() * 2)](ch);
  275. } else {
  276. var r = Math.random();
  277. // roughly 10% raw, 45% hex, 45% dec
  278. ch = (
  279. r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
  280. );
  281. }
  282. return ch;
  283. });
  284. return mail;
  285. };
  286. /**
  287. * POLYFILLS
  288. */
  289. // use this instead of builtin is undefined for IE8 compatibility
  290. if (typeof(console) === 'undefined') {
  291. console = {
  292. warn: function (msg) {
  293. 'use strict';
  294. alert(msg);
  295. },
  296. log: function (msg) {
  297. 'use strict';
  298. alert(msg);
  299. },
  300. error: function (msg) {
  301. 'use strict';
  302. throw msg;
  303. }
  304. };
  305. }