motion-markdown-it 0.4.0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +243 -0
  3. data/lib/motion-markdown-it.rb +71 -0
  4. data/lib/motion-markdown-it/common/entities.rb +1084 -0
  5. data/lib/motion-markdown-it/common/html_blocks.rb +60 -0
  6. data/lib/motion-markdown-it/common/html_re.rb +28 -0
  7. data/lib/motion-markdown-it/common/string.rb +14 -0
  8. data/lib/motion-markdown-it/common/url_schemas.rb +173 -0
  9. data/lib/motion-markdown-it/common/utils.rb +216 -0
  10. data/lib/motion-markdown-it/helpers/parse_link_destination.rb +75 -0
  11. data/lib/motion-markdown-it/helpers/parse_link_label.rb +51 -0
  12. data/lib/motion-markdown-it/helpers/parse_link_title.rb +48 -0
  13. data/lib/motion-markdown-it/index.rb +507 -0
  14. data/lib/motion-markdown-it/parser_block.rb +113 -0
  15. data/lib/motion-markdown-it/parser_core.rb +46 -0
  16. data/lib/motion-markdown-it/parser_inline.rb +121 -0
  17. data/lib/motion-markdown-it/presets/commonmark.rb +76 -0
  18. data/lib/motion-markdown-it/presets/default.rb +42 -0
  19. data/lib/motion-markdown-it/presets/zero.rb +59 -0
  20. data/lib/motion-markdown-it/renderer.rb +286 -0
  21. data/lib/motion-markdown-it/ruler.rb +327 -0
  22. data/lib/motion-markdown-it/rules_block/blockquote.rb +138 -0
  23. data/lib/motion-markdown-it/rules_block/code.rb +35 -0
  24. data/lib/motion-markdown-it/rules_block/fence.rb +94 -0
  25. data/lib/motion-markdown-it/rules_block/heading.rb +56 -0
  26. data/lib/motion-markdown-it/rules_block/hr.rb +45 -0
  27. data/lib/motion-markdown-it/rules_block/html_block.rb +73 -0
  28. data/lib/motion-markdown-it/rules_block/lheading.rb +54 -0
  29. data/lib/motion-markdown-it/rules_block/list.rb +242 -0
  30. data/lib/motion-markdown-it/rules_block/paragraph.rb +51 -0
  31. data/lib/motion-markdown-it/rules_block/reference.rb +161 -0
  32. data/lib/motion-markdown-it/rules_block/state_block.rb +184 -0
  33. data/lib/motion-markdown-it/rules_block/table.rb +161 -0
  34. data/lib/motion-markdown-it/rules_core/block.rb +20 -0
  35. data/lib/motion-markdown-it/rules_core/inline.rb +20 -0
  36. data/lib/motion-markdown-it/rules_core/linkify.rb +138 -0
  37. data/lib/motion-markdown-it/rules_core/normalize.rb +44 -0
  38. data/lib/motion-markdown-it/rules_core/replacements.rb +90 -0
  39. data/lib/motion-markdown-it/rules_core/smartquotes.rb +158 -0
  40. data/lib/motion-markdown-it/rules_core/state_core.rb +20 -0
  41. data/lib/motion-markdown-it/rules_inline/autolink.rb +74 -0
  42. data/lib/motion-markdown-it/rules_inline/backticks.rb +51 -0
  43. data/lib/motion-markdown-it/rules_inline/emphasis.rb +172 -0
  44. data/lib/motion-markdown-it/rules_inline/entity.rb +51 -0
  45. data/lib/motion-markdown-it/rules_inline/escape.rb +55 -0
  46. data/lib/motion-markdown-it/rules_inline/html_inline.rb +49 -0
  47. data/lib/motion-markdown-it/rules_inline/image.rb +158 -0
  48. data/lib/motion-markdown-it/rules_inline/link.rb +153 -0
  49. data/lib/motion-markdown-it/rules_inline/newline.rb +47 -0
  50. data/lib/motion-markdown-it/rules_inline/state_inline.rb +57 -0
  51. data/lib/motion-markdown-it/rules_inline/strikethrough.rb +130 -0
  52. data/lib/motion-markdown-it/rules_inline/text.rb +94 -0
  53. data/lib/motion-markdown-it/token.rb +134 -0
  54. data/lib/motion-markdown-it/version.rb +5 -0
  55. data/spec/motion-markdown-it/bench_mark_spec.rb +44 -0
  56. data/spec/motion-markdown-it/commonmark_spec.rb +16 -0
  57. data/spec/motion-markdown-it/markdown_it_spec.rb +18 -0
  58. data/spec/motion-markdown-it/misc_spec.rb +277 -0
  59. data/spec/motion-markdown-it/ruler_spec.rb +153 -0
  60. data/spec/motion-markdown-it/testgen_helper.rb +68 -0
  61. data/spec/motion-markdown-it/token_spec.rb +17 -0
  62. data/spec/motion-markdown-it/utils_spec.rb +82 -0
  63. data/spec/spec_helper.rb +6 -0
  64. metadata +158 -0
@@ -0,0 +1,44 @@
1
+ # Normalize input string
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesCore
5
+ class Normalize
6
+
7
+ TABS_SCAN_RE = /[\n\t]/
8
+ NEWLINES_RE = /\r[\n\u0085]|[\u2424\u2028\u0085]/
9
+ NULL_RE = /\u0000/
10
+
11
+
12
+ #------------------------------------------------------------------------------
13
+ def self.inline(state)
14
+ # Normalize newlines
15
+ str = state.src.gsub(NEWLINES_RE, "\n")
16
+
17
+ # Replace NULL characters
18
+ str = str.gsub(NULL_RE, '\uFFFD')
19
+
20
+ # Replace tabs with proper number of spaces (1..4)
21
+ if str.include?("\t")
22
+ lineStart = 0
23
+ lastTabPos = 0
24
+
25
+ str = str.gsub(TABS_SCAN_RE) do
26
+ md = Regexp.last_match
27
+ match = md.to_s
28
+ offset = md.begin(0)
29
+ if str.charCodeAt(offset) == 0x0A
30
+ lineStart = offset + 1
31
+ lastTabPos = 0
32
+ next match
33
+ end
34
+ result = ' '.slice_to_end((offset - lineStart - lastTabPos) % 4)
35
+ lastTabPos = offset - lineStart + 1
36
+ result
37
+ end
38
+ end
39
+
40
+ state.src = str
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,90 @@
1
+ # Simple typographyc replacements
2
+ #
3
+ # '' → ‘’
4
+ # "" → “”. Set '«»' for Russian, '„“' for German, empty to disable
5
+ # (c) (C) → ©
6
+ # (tm) (TM) → ™
7
+ # (r) (R) → ®
8
+ # +- → ±
9
+ # (p) (P) -> §
10
+ # ... → … (also ?.... → ?.., !.... → !..)
11
+ # ???????? → ???, !!!!! → !!!, `,,` → `,`
12
+ # -- → –, --- → —
13
+ #------------------------------------------------------------------------------
14
+ module MarkdownIt
15
+ module RulesCore
16
+ class Replacements
17
+
18
+ # TODO:
19
+ # - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
20
+ # - miltiplication 2 x 4 -> 2 × 4
21
+
22
+ RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/
23
+
24
+ SCOPED_ABBR_RE = /\((c|tm|r|p)\)/i
25
+ SCOPED_ABBR = {
26
+ 'c' => '©',
27
+ 'r' => '®',
28
+ 'p' => '§',
29
+ 'tm' => '™'
30
+ }
31
+
32
+ #------------------------------------------------------------------------------
33
+ def self.replaceFn(match, name)
34
+ return SCOPED_ABBR[name.downcase]
35
+ end
36
+
37
+ #------------------------------------------------------------------------------
38
+ def self.replace_scoped(inlineTokens)
39
+ (inlineTokens.length - 1).downto(0) do |i|
40
+ token = inlineTokens[i]
41
+ if (token.type == 'text')
42
+ token.content = token.content.gsub(SCOPED_ABBR_RE) {|match| self.replaceFn(match, $1)}
43
+ end
44
+ end
45
+ end
46
+
47
+ #------------------------------------------------------------------------------
48
+ def self.replace_rare(inlineTokens)
49
+ (inlineTokens.length - 1).downto(0) do |i|
50
+ token = inlineTokens[i]
51
+ if (token.type == 'text')
52
+ if (RARE_RE =~ token.content)
53
+ token.content = token.content.
54
+ gsub(/\+-/, '±').
55
+ # .., ..., ....... -> …
56
+ # but ?..... & !..... -> ?.. & !..
57
+ gsub(/\.{2,}/, '…').gsub(/([?!])…/, "\\1..").
58
+ gsub(/([?!]){4,}/, '\\1\\1\\1').gsub(/,{2,}/, ',').
59
+ # em-dash
60
+ gsub(/(^|[^-])---([^-]|$)/m, "\\1\u2014\\2").
61
+ # en-dash
62
+ gsub(/(^|\s)--(\s|$)/m, "\\1\u2013\\2").
63
+ gsub(/(^|[^-\s])--([^-\s]|$)/m, "\\1\u2013\\2")
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ #------------------------------------------------------------------------------
71
+ def self.replace(state)
72
+ return if (!state.md.options[:typographer])
73
+
74
+ (state.tokens.length - 1).downto(0) do |blkIdx|
75
+ next if (state.tokens[blkIdx].type != 'inline')
76
+
77
+ if (SCOPED_ABBR_RE =~ state.tokens[blkIdx].content)
78
+ replace_scoped(state.tokens[blkIdx].children)
79
+ end
80
+
81
+ if (RARE_RE =~ state.tokens[blkIdx].content)
82
+ replace_rare(state.tokens[blkIdx].children)
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,158 @@
1
+ # Convert straight quotation marks to typographic ones
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesCore
5
+ class Smartquotes
6
+ extend Common::Utils
7
+
8
+ QUOTE_TEST_RE = /['"]/
9
+ QUOTE_RE = /['"]/
10
+ APOSTROPHE = "\u2019" # ’
11
+
12
+
13
+ #------------------------------------------------------------------------------
14
+ def self.replaceAt(str, index, ch)
15
+ return str[0, index] + ch + str.slice_to_end(index + 1)
16
+ end
17
+
18
+ #------------------------------------------------------------------------------
19
+ def self.process_inlines(tokens, state)
20
+ stack = []
21
+
22
+ (0...tokens.length).each do |i|
23
+ token = tokens[i]
24
+
25
+ thisLevel = tokens[i].level
26
+
27
+ j = stack.length - 1
28
+ while j >= 0
29
+ break if (stack[j][:level] <= thisLevel)
30
+ j -= 1
31
+ end
32
+
33
+ # stack.length = j + 1
34
+ stack = (j < stack.length ? stack.slice(0, j + 1) : stack.fill(nil, stack.length...(j+1)))
35
+
36
+ next if (token.type != 'text')
37
+
38
+ text = token.content
39
+ pos = 0
40
+ max = text.length
41
+
42
+ # OUTER loop
43
+ while pos < max
44
+ continue_outer_loop = false
45
+ t = QUOTE_RE.match(text, pos)
46
+ break if t.nil?
47
+
48
+ canOpen = true
49
+ canClose = true
50
+ pos = t.begin(0) + 1
51
+ isSingle = (t[0] == "'")
52
+
53
+ # treat begin/end of the line as a whitespace
54
+ lastChar = t.begin(0) - 1 >= 0 ? text.charCodeAt(t.begin(0) - 1) : 0x20
55
+ nextChar = pos < max ? text.charCodeAt(pos) : 0x20
56
+
57
+ isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(lastChar.chr)
58
+ isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(nextChar.chr)
59
+
60
+ isLastWhiteSpace = isWhiteSpace(lastChar)
61
+ isNextWhiteSpace = isWhiteSpace(nextChar)
62
+
63
+ if (isNextWhiteSpace)
64
+ canOpen = false
65
+ elsif (isNextPunctChar)
66
+ if (!(isLastWhiteSpace || isLastPunctChar))
67
+ canOpen = false
68
+ end
69
+ end
70
+
71
+ if (isLastWhiteSpace)
72
+ canClose = false
73
+ elsif (isLastPunctChar)
74
+ if (!(isNextWhiteSpace || isNextPunctChar))
75
+ canClose = false
76
+ end
77
+ end
78
+
79
+ if (nextChar == 0x22 && t[0] == '"') # "
80
+ if (lastChar >= 0x30 && lastChar <= 0x39) # >= 0 && <= 9
81
+ # special case: 1"" - count first quote as an inch
82
+ canClose = canOpen = false
83
+ end
84
+ end
85
+
86
+ if (canOpen && canClose)
87
+ # treat this as the middle of the word
88
+ canOpen = false
89
+ canClose = isNextPunctChar
90
+ end
91
+
92
+ if (!canOpen && !canClose)
93
+ # middle of word
94
+ if (isSingle)
95
+ token.content = replaceAt(token.content, t.begin(0), APOSTROPHE)
96
+ end
97
+ next
98
+ end
99
+
100
+ if (canClose)
101
+ # this could be a closing quote, rewind the stack to get a match
102
+ j = stack.length - 1
103
+ while j >= 0
104
+ item = stack[j]
105
+ break if (stack[j][:level] < thisLevel)
106
+ if (item[:single] == isSingle && stack[j][:level] == thisLevel)
107
+ item = stack[j]
108
+ if (isSingle)
109
+ tokens[item[:token]].content = replaceAt(tokens[item[:token]].content, item[:pos], state.md.options[:quotes][2])
110
+ token.content = replaceAt(token.content, t.begin(0), state.md.options[:quotes][3])
111
+ else
112
+ tokens[item[:token]].content = replaceAt(tokens[item[:token]].content, item[:pos], state.md.options[:quotes][0])
113
+ token.content = replaceAt(token.content, t.begin(0), state.md.options[:quotes][1])
114
+ end
115
+ # stack.length = j
116
+ stack = (j < stack.length ? stack.slice(0, j) : stack.fill(nil, stack.length...(j)))
117
+ continue_outer_loop = true # continue OUTER;
118
+ break
119
+ end
120
+ j -= 1
121
+ end
122
+ end
123
+ next if continue_outer_loop
124
+
125
+ if (canOpen)
126
+ stack.push({
127
+ token: i,
128
+ pos: t.begin(0),
129
+ single: isSingle,
130
+ level: thisLevel
131
+ })
132
+ elsif (canClose && isSingle)
133
+ token.content = replaceAt(token.content, t.begin(0), APOSTROPHE)
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+
140
+ #------------------------------------------------------------------------------
141
+ def self.smartquotes(state)
142
+ return if (!state.md.options[:typographer])
143
+
144
+ blkIdx = state.tokens.length - 1
145
+ while blkIdx >= 0
146
+ if (state.tokens[blkIdx].type != 'inline' || !(QUOTE_TEST_RE =~ state.tokens[blkIdx].content))
147
+ blkIdx -= 1
148
+ next
149
+ end
150
+
151
+ process_inlines(state.tokens[blkIdx].children, state)
152
+ blkIdx -= 1
153
+ end
154
+ end
155
+
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,20 @@
1
+ # Core state object
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesCore
5
+ class StateCore
6
+
7
+ attr_accessor :src, :env, :tokens, :inlineMode, :md
8
+
9
+ #------------------------------------------------------------------------------
10
+ def initialize(src, md, env)
11
+ @src = src
12
+ @env = env
13
+ @tokens = []
14
+ @inlineMode = false
15
+ @md = md # link to parser instance
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,74 @@
1
+ # Process autolinks '<protocol:...>'
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesInline
5
+ class Autolink
6
+
7
+ EMAIL_RE = /^<([a-zA-Z0-9.!#$\%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/
8
+ AUTOLINK_RE = /^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/
9
+
10
+
11
+ #------------------------------------------------------------------------------
12
+ def self.autolink(state, silent)
13
+ pos = state.pos
14
+
15
+ return false if (state.src.charCodeAt(pos) != 0x3C) # <
16
+
17
+ tail = state.src.slice_to_end(pos)
18
+
19
+ return false if !tail.include?('>')
20
+
21
+ if (AUTOLINK_RE =~ tail)
22
+ linkMatch = tail.match(AUTOLINK_RE)
23
+
24
+ return false if !URL_SCHEMAS.include?(linkMatch[1].downcase)
25
+
26
+ url = linkMatch[0].slice(1...-1)
27
+ fullUrl = state.md.normalizeLink.call(url)
28
+ return false if (!state.md.validateLink.call(fullUrl))
29
+
30
+ if (!silent)
31
+ token = state.push('link_open', 'a', 1)
32
+ token.attrs = [ [ 'href', fullUrl ] ]
33
+
34
+ token = state.push('text', '', 0)
35
+ token.content = state.md.normalizeLinkText.call(url)
36
+
37
+ token = state.push('link_close', 'a', -1)
38
+ end
39
+
40
+ state.pos += linkMatch[0].length
41
+ return true
42
+ end
43
+
44
+ if (EMAIL_RE =~ tail)
45
+ emailMatch = tail.match(EMAIL_RE)
46
+
47
+ url = emailMatch[0].slice(1...-1)
48
+ fullUrl = state.md.normalizeLink.call('mailto:' + url)
49
+ return false if (!state.md.validateLink.call(fullUrl))
50
+
51
+ if (!silent)
52
+ token = state.push('link_open', 'a', 1)
53
+ token.attrs = [ [ 'href', fullUrl ] ]
54
+ token.markup = 'autolink'
55
+ token.info = 'auto'
56
+
57
+ token = state.push('text', '', 0)
58
+ token.content = state.md.normalizeLinkText.call(url)
59
+
60
+ token = state.push('link_close', 'a', -1)
61
+ token.markup = 'autolink'
62
+ token.info = 'auto'
63
+ end
64
+
65
+ state.pos += emailMatch[0].length
66
+ return true
67
+ end
68
+
69
+ return false
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,51 @@
1
+ # Parse backticks
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesInline
5
+ class Backticks
6
+
7
+ #------------------------------------------------------------------------------
8
+ def self.backtick(state, silent)
9
+ pos = state.pos
10
+ ch = state.src.charCodeAt(pos)
11
+
12
+ return false if (ch != 0x60) # `
13
+
14
+ start = pos
15
+ pos += 1
16
+ max = state.posMax
17
+
18
+ while (pos < max && state.src.charCodeAt(pos) == 0x60) # `
19
+ pos += 1
20
+ end
21
+
22
+ marker = state.src.slice(start...pos)
23
+
24
+ matchStart = matchEnd = pos
25
+
26
+ while ((matchStart = state.src.index('`', matchEnd)) != nil)
27
+ matchEnd = matchStart + 1
28
+
29
+ while (matchEnd < max && state.src.charCodeAt(matchEnd) == 0x60) # `
30
+ matchEnd += 1
31
+ end
32
+
33
+ if (matchEnd - matchStart == marker.length)
34
+ if (!silent)
35
+ token = state.push('code_inline', 'code', 0)
36
+ token.markup = marker
37
+ token.content = state.src.slice(pos...matchStart).gsub(/[ \n]+/, ' ').strip
38
+ end
39
+ state.pos = matchEnd
40
+ return true
41
+ end
42
+ end
43
+
44
+ state.pending += marker if (!silent)
45
+ state.pos += marker.length
46
+ return true
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,172 @@
1
+ # Process *this* and _that_
2
+ #------------------------------------------------------------------------------
3
+ module MarkdownIt
4
+ module RulesInline
5
+ class Emphasis
6
+ extend MarkdownIt::Common::Utils
7
+
8
+ # parse sequence of emphasis markers,
9
+ # "start" should point at a valid marker
10
+ #------------------------------------------------------------------------------
11
+ def self.scanDelims(state, start)
12
+ pos = start
13
+ can_open = true
14
+ can_close = true
15
+ max = state.posMax
16
+ marker = state.src.charCodeAt(start)
17
+
18
+ # treat beginning of the line as a whitespace
19
+ lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20
20
+
21
+ while (pos < max && state.src.charCodeAt(pos) == marker)
22
+ pos += 1
23
+ end
24
+
25
+ if (pos >= max)
26
+ can_open = false
27
+ end
28
+
29
+ count = pos - start
30
+
31
+ # treat end of the line as a whitespace
32
+ nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20
33
+
34
+ isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(lastChar.chr(Encoding::UTF_8))
35
+ isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(nextChar.chr(Encoding::UTF_8))
36
+
37
+ isLastWhiteSpace = isWhiteSpace(lastChar)
38
+ isNextWhiteSpace = isWhiteSpace(nextChar)
39
+
40
+ if (isNextWhiteSpace)
41
+ can_open = false
42
+ elsif (isNextPunctChar)
43
+ if (!(isLastWhiteSpace || isLastPunctChar))
44
+ can_open = false
45
+ end
46
+ end
47
+
48
+ if (isLastWhiteSpace)
49
+ can_close = false
50
+ elsif (isLastPunctChar)
51
+ if (!(isNextWhiteSpace || isNextPunctChar))
52
+ can_close = false
53
+ end
54
+ end
55
+
56
+ if (marker == 0x5F) # _
57
+ if (can_open && can_close)
58
+ # "_" inside a word can neither open nor close an emphasis
59
+ can_open = false
60
+ can_close = isNextPunctChar
61
+ end
62
+ end
63
+
64
+ return { can_open: can_open, can_close: can_close, delims: count }
65
+ end
66
+
67
+ #------------------------------------------------------------------------------
68
+ def self.emphasis(state, silent)
69
+ max = state.posMax
70
+ start = state.pos
71
+ marker = state.src.charCodeAt(start)
72
+
73
+ return false if (marker != 0x5F && marker != 0x2A) # _ *
74
+ return false if (silent) # don't run any pairs in validation mode
75
+
76
+ res = scanDelims(state, start)
77
+ startCount = res[:delims]
78
+ if (!res[:can_open])
79
+ state.pos += startCount
80
+ # Earlier we checked !silent, but this implementation does not need it
81
+ state.pending += state.src.slice(start...state.pos)
82
+ return true
83
+ end
84
+
85
+ state.pos = start + startCount
86
+ stack = [ startCount ]
87
+
88
+ while (state.pos < max)
89
+ if (state.src.charCodeAt(state.pos) == marker)
90
+ res = scanDelims(state, state.pos)
91
+ count = res[:delims]
92
+ if (res[:can_close])
93
+ oldCount = stack.pop()
94
+ newCount = count
95
+
96
+ while (oldCount != newCount)
97
+ if (newCount < oldCount)
98
+ stack.push(oldCount - newCount)
99
+ break
100
+ end
101
+
102
+ # assert(newCount > oldCount)
103
+ newCount -= oldCount
104
+
105
+ break if (stack.length == 0)
106
+ state.pos += oldCount
107
+ oldCount = stack.pop()
108
+ end
109
+
110
+ if (stack.length == 0)
111
+ startCount = oldCount
112
+ found = true
113
+ break
114
+ end
115
+ state.pos += count
116
+ next
117
+ end
118
+
119
+ stack.push(count) if (res[:can_open])
120
+ state.pos += count
121
+ next
122
+ end
123
+
124
+ state.md.inline.skipToken(state)
125
+ end
126
+
127
+ if (!found)
128
+ # parser failed to find ending tag, so it's not valid emphasis
129
+ state.pos = start
130
+ return false
131
+ end
132
+
133
+ # found!
134
+ state.posMax = state.pos
135
+ state.pos = start + startCount
136
+
137
+ # Earlier we checked !silent, but this implementation does not need it
138
+
139
+ # we have `startCount` starting and ending markers,
140
+ # now trying to serialize them into tokens
141
+ count = startCount
142
+ while count > 1
143
+ token = state.push('strong_open', 'strong', 1)
144
+ token.markup = marker.chr + marker.chr
145
+ count -= 2
146
+ end
147
+ if (count % 2 == 1)
148
+ token = state.push('em_open', 'em', 1)
149
+ token.markup = marker.chr
150
+ end
151
+
152
+ state.md.inline.tokenize(state)
153
+
154
+ if (count % 2 == 1)
155
+ token = state.push('em_close', 'em', -1)
156
+ token.markup = marker.chr
157
+ end
158
+ count = startCount
159
+ while count > 1
160
+ token = state.push('strong_close', 'strong', -1)
161
+ token.markup = marker.chr + marker.chr
162
+ count -= 2
163
+ end
164
+
165
+ state.pos = state.posMax + startCount
166
+ state.posMax = max
167
+ return true
168
+ end
169
+
170
+ end
171
+ end
172
+ end