state_inline.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // Inline parser state
  2. 'use strict';
  3. var Token = require('../token');
  4. var isWhiteSpace = require('../common/utils').isWhiteSpace;
  5. var isPunctChar = require('../common/utils').isPunctChar;
  6. var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct;
  7. function StateInline(src, md, env, outTokens) {
  8. this.src = src;
  9. this.env = env;
  10. this.md = md;
  11. this.tokens = outTokens;
  12. this.tokens_meta = Array(outTokens.length);
  13. this.pos = 0;
  14. this.posMax = this.src.length;
  15. this.level = 0;
  16. this.pending = '';
  17. this.pendingLevel = 0;
  18. // Stores { start: end } pairs. Useful for backtrack
  19. // optimization of pairs parse (emphasis, strikes).
  20. this.cache = {};
  21. // List of emphasis-like delimiters for current tag
  22. this.delimiters = [];
  23. // Stack of delimiter lists for upper level tags
  24. this._prev_delimiters = [];
  25. }
  26. // Flush pending text
  27. //
  28. StateInline.prototype.pushPending = function () {
  29. var token = new Token('text', '', 0);
  30. token.content = this.pending;
  31. token.level = this.pendingLevel;
  32. this.tokens.push(token);
  33. this.pending = '';
  34. return token;
  35. };
  36. // Push new token to "stream".
  37. // If pending text exists - flush it as text token
  38. //
  39. StateInline.prototype.push = function (type, tag, nesting) {
  40. if (this.pending) {
  41. this.pushPending();
  42. }
  43. var token = new Token(type, tag, nesting);
  44. var token_meta = null;
  45. if (nesting < 0) {
  46. // closing tag
  47. this.level--;
  48. this.delimiters = this._prev_delimiters.pop();
  49. }
  50. token.level = this.level;
  51. if (nesting > 0) {
  52. // opening tag
  53. this.level++;
  54. this._prev_delimiters.push(this.delimiters);
  55. this.delimiters = [];
  56. token_meta = { delimiters: this.delimiters };
  57. }
  58. this.pendingLevel = this.level;
  59. this.tokens.push(token);
  60. this.tokens_meta.push(token_meta);
  61. return token;
  62. };
  63. // Scan a sequence of emphasis-like markers, and determine whether
  64. // it can start an emphasis sequence or end an emphasis sequence.
  65. //
  66. // - start - position to scan from (it should point at a valid marker);
  67. // - canSplitWord - determine if these markers can be found inside a word
  68. //
  69. StateInline.prototype.scanDelims = function (start, canSplitWord) {
  70. var pos = start, lastChar, nextChar, count, can_open, can_close,
  71. isLastWhiteSpace, isLastPunctChar,
  72. isNextWhiteSpace, isNextPunctChar,
  73. left_flanking = true,
  74. right_flanking = true,
  75. max = this.posMax,
  76. marker = this.src.charCodeAt(start);
  77. // treat beginning of the line as a whitespace
  78. lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20;
  79. while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; }
  80. count = pos - start;
  81. // treat end of the line as a whitespace
  82. nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20;
  83. isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar));
  84. isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar));
  85. isLastWhiteSpace = isWhiteSpace(lastChar);
  86. isNextWhiteSpace = isWhiteSpace(nextChar);
  87. if (isNextWhiteSpace) {
  88. left_flanking = false;
  89. } else if (isNextPunctChar) {
  90. if (!(isLastWhiteSpace || isLastPunctChar)) {
  91. left_flanking = false;
  92. }
  93. }
  94. if (isLastWhiteSpace) {
  95. right_flanking = false;
  96. } else if (isLastPunctChar) {
  97. if (!(isNextWhiteSpace || isNextPunctChar)) {
  98. right_flanking = false;
  99. }
  100. }
  101. if (!canSplitWord) {
  102. can_open = left_flanking && (!right_flanking || isLastPunctChar);
  103. can_close = right_flanking && (!left_flanking || isNextPunctChar);
  104. } else {
  105. can_open = left_flanking;
  106. can_close = right_flanking;
  107. }
  108. return {
  109. can_open: can_open,
  110. can_close: can_close,
  111. length: count
  112. };
  113. };
  114. // re-export Token class to use in block rules
  115. StateInline.prototype.Token = Token;
  116. module.exports = StateInline;