motion-markdown-it 0.4.0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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