helpers.js 7.9 KB

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