state_inline.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. // backtick length => last seen position
  26. this.backticks = {};
  27. this.backticksScanned = false;
  28. }
  29. // Flush pending text
  30. //
  31. StateInline.prototype.pushPending = function () {
  32. var token = new Token('text', '', 0);
  33. token.content = this.pending;
  34. token.level = this.pendingLevel;
  35. this.tokens.push(token);
  36. this.pending = '';
  37. return token;
  38. };
  39. // Push new token to "stream".
  40. // If pending text exists - flush it as text token
  41. //
  42. StateInline.prototype.push = function (type, tag, nesting) {
  43. if (this.pending) {
  44. this.pushPending();
  45. }
  46. var token = new Token(type, tag, nesting);
  47. var token_meta = null;
  48. if (nesting < 0) {
  49. // closing tag
  50. this.level--;
  51. this.delimiters = this._prev_delimiters.pop();
  52. }
  53. token.level = this.level;
  54. if (nesting > 0) {
  55. // opening tag
  56. this.level++;
  57. this._prev_delimiters.push(this.delimiters);
  58. this.delimiters = [];
  59. token_meta = { delimiters: this.delimiters };
  60. }
  61. this.pendingLevel = this.level;
  62. this.tokens.push(token);
  63. this.tokens_meta.push(token_meta);
  64. return token;
  65. };
  66. // Scan a sequence of emphasis-like markers, and determine whether
  67. // it can start an emphasis sequence or end an emphasis sequence.
  68. //
  69. // - start - position to scan from (it should point at a valid marker);
  70. // - canSplitWord - determine if these markers can be found inside a word
  71. //
  72. StateInline.prototype.scanDelims = function (start, canSplitWord) {
  73. var pos = start, lastChar, nextChar, count, can_open, can_close,
  74. isLastWhiteSpace, isLastPunctChar,
  75. isNextWhiteSpace, isNextPunctChar,
  76. left_flanking = true,
  77. right_flanking = true,
  78. max = this.posMax,
  79. marker = this.src.charCodeAt(start);
  80. // treat beginning of the line as a whitespace
  81. lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20;
  82. while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; }
  83. count = pos - start;
  84. // treat end of the line as a whitespace
  85. nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20;
  86. isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar));
  87. isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar));
  88. isLastWhiteSpace = isWhiteSpace(lastChar);
  89. isNextWhiteSpace = isWhiteSpace(nextChar);
  90. if (isNextWhiteSpace) {
  91. left_flanking = false;
  92. } else if (isNextPunctChar) {
  93. if (!(isLastWhiteSpace || isLastPunctChar)) {
  94. left_flanking = false;
  95. }
  96. }
  97. if (isLastWhiteSpace) {
  98. right_flanking = false;
  99. } else if (isLastPunctChar) {
  100. if (!(isNextWhiteSpace || isNextPunctChar)) {
  101. right_flanking = false;
  102. }
  103. }
  104. if (!canSplitWord) {
  105. can_open = left_flanking && (!right_flanking || isLastPunctChar);
  106. can_close = right_flanking && (!left_flanking || isNextPunctChar);
  107. } else {
  108. can_open = left_flanking;
  109. can_close = right_flanking;
  110. }
  111. return {
  112. can_open: can_open,
  113. can_close: can_close,
  114. length: count
  115. };
  116. };
  117. // re-export Token class to use in block rules
  118. StateInline.prototype.Token = Token;
  119. module.exports = StateInline;