kramdown-asciidoc 1.0.0.alpha.4 → 1.0.0.alpha.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +15 -0
- data/README.adoc +1 -1
- data/lib/kramdown-asciidoc.rb +1 -0
- data/lib/kramdown-asciidoc/converter.rb +297 -266
- data/lib/kramdown-asciidoc/version.rb +1 -1
- data/lib/kramdown-asciidoc/writer.rb +114 -0
- data/spec/scenarios/blockquote/deep-nested.adoc +3 -0
- data/spec/scenarios/codespan/constrained-triple.adoc +1 -0
- data/spec/scenarios/codespan/constrained-triple.md +1 -0
- data/spec/scenarios/dl/compound.adoc +11 -0
- data/spec/scenarios/{dlist → dl}/compound.md +0 -0
- data/spec/scenarios/dl/nested-mixed.adoc +11 -0
- data/spec/scenarios/{dlist → dl}/nested-mixed.md +0 -0
- data/spec/scenarios/dl/nested.adoc +15 -0
- data/spec/scenarios/{dlist → dl}/nested.md +0 -0
- data/spec/scenarios/dl/simple.adoc +9 -0
- data/spec/scenarios/dl/simple.md +10 -0
- data/spec/scenarios/entity/reverse.adoc +2 -0
- data/spec/scenarios/entity/reverse.md +2 -0
- data/spec/scenarios/p/admonition/in-blockquote.adoc +1 -0
- data/spec/scenarios/p/admonition/in-blockquote.md +1 -0
- data/spec/scenarios/p/admonition/in-compound-blockquote.adoc +5 -0
- data/spec/scenarios/p/admonition/in-compound-blockquote.md +3 -0
- data/spec/scenarios/p/admonition/plain.adoc +2 -0
- data/spec/scenarios/p/admonition/plain.md +2 -0
- data/spec/scenarios/strong/menu.adoc +1 -1
- data/spec/scenarios/table/alignment.adoc +7 -0
- data/spec/scenarios/table/alignment.md +4 -0
- data/spec/scenarios/text/nbsp.adoc +1 -0
- data/spec/scenarios/text/nbsp.md +1 -0
- data/spec/scenarios/xml_comment/line-adjacent-to-text.adoc +2 -0
- data/spec/scenarios/xml_comment/line-adjacent-to-text.md +1 -0
- metadata +39 -18
- data/spec/scenarios/dlist/compound.adoc +0 -13
- data/spec/scenarios/dlist/nested-mixed.adoc +0 -11
- data/spec/scenarios/dlist/nested.adoc +0 -23
- data/spec/scenarios/dlist/simple.adoc +0 -5
- data/spec/scenarios/dlist/simple.md +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 047f74e6cf78d83f0044f72410f8df7e5338800cf1f5796de4c919a0ff79ed36
|
4
|
+
data.tar.gz: ab08640754fff574b51a5ef5f0c400b493ff17f31d54163a33f4af5e115a64ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3170271705bb355ad77d7aa262724f07e789947b4eaf4e0c3ee09fbf48409cce910c445011f825be92c53f90e30b17d9d2562e9c627a4b0b86ca764d05748331
|
7
|
+
data.tar.gz: 6412723030a019d5c039206c5f4ada159b4c6c75f728648a7161ad989cb9990c938e56be6e48986b5c28e80cbff738b522d8c82248de1a6b7564eca4307e9652
|
data/CHANGELOG.adoc
CHANGED
@@ -5,6 +5,21 @@
|
|
5
5
|
This document provides a high-level view of the changes to {project-name} by release.
|
6
6
|
For a detailed view of what has changed, refer to the {uri-repo}/commits/master[commit history] on GitHub.
|
7
7
|
|
8
|
+
== 1.0.0.alpha.5 (2018-06-19) - @mojavelinux
|
9
|
+
|
10
|
+
=== Added
|
11
|
+
|
12
|
+
* recognize Hint as admonition label; map to TIP
|
13
|
+
* replace no-break space with \{nbsp}
|
14
|
+
|
15
|
+
=== Changed
|
16
|
+
|
17
|
+
* rewrite converter to use a structured writer
|
18
|
+
* remove blockquote enclosure around simple admonition block
|
19
|
+
* revert \& back to &
|
20
|
+
* use separate list level for dl
|
21
|
+
* fold description list item to one line if primary text is a single line
|
22
|
+
|
8
23
|
== 1.0.0.alpha.4 (2018-06-12) - @mojavelinux
|
9
24
|
|
10
25
|
=== Added
|
data/README.adoc
CHANGED
data/lib/kramdown-asciidoc.rb
CHANGED
@@ -10,15 +10,6 @@ module Kramdown; module AsciiDoc
|
|
10
10
|
TocDirectiveTip = '<!-- TOC '
|
11
11
|
TocDirectiveRx = /^<!-- TOC .*<!-- \/TOC -->/m
|
12
12
|
|
13
|
-
def self.replace_toc source, attributes
|
14
|
-
if source.include? TocDirectiveTip
|
15
|
-
attributes['toc'] = 'macro'
|
16
|
-
source.gsub TocDirectiveRx, 'toc::[]'
|
17
|
-
else
|
18
|
-
source
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
13
|
# TODO return original source if YAML can't be parsed
|
23
14
|
def self.extract_front_matter source, attributes
|
24
15
|
if (line_i = (lines = source.each_line).first) && line_i.chomp == '---'
|
@@ -42,12 +33,23 @@ module Kramdown; module AsciiDoc
|
|
42
33
|
end
|
43
34
|
end
|
44
35
|
|
36
|
+
def self.replace_toc source, attributes
|
37
|
+
if source.include? TocDirectiveTip
|
38
|
+
attributes['toc'] = 'macro'
|
39
|
+
source.gsub TocDirectiveRx, 'toc::[]'
|
40
|
+
else
|
41
|
+
source
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
45
|
class Converter < ::Kramdown::Converter::Base
|
46
|
-
RESOLVE_ENTITY_TABLE = { 60 => '<', 62 => '>', 124 => '|' }
|
47
|
-
ADMON_LABELS = %w(Note Tip Caution Warning Important Attention).map {|l| [l, l] }.to_h
|
46
|
+
RESOLVE_ENTITY_TABLE = { 38 => '&', 60 => '<', 62 => '>', 124 => '|' }
|
47
|
+
ADMON_LABELS = %w(Note Tip Caution Warning Important Attention Hint).map {|l| [l, l] }.to_h
|
48
48
|
ADMON_MARKERS = ADMON_LABELS.map {|l, _| %(#{l}: ) }
|
49
|
+
ADMON_MARKERS_ASCIIDOC = %w(NOTE TIP CAUTION WARNING IMPORTANT).map {|l| %(#{l}: ) }
|
49
50
|
ADMON_FORMATTED_MARKERS = ADMON_LABELS.map {|l, _| [%(#{l}:), l] }.to_h
|
50
|
-
ADMON_TYPE_MAP = ADMON_LABELS.map {|l, _| [l, l.upcase] }.to_h.merge 'Attention' => 'IMPORTANT'
|
51
|
+
ADMON_TYPE_MAP = ADMON_LABELS.map {|l, _| [l, l.upcase] }.to_h.merge 'Attention' => 'IMPORTANT', 'Hint' => 'TIP'
|
52
|
+
BLOCK_TYPES = [:p, :blockquote, :codeblock]
|
51
53
|
DLIST_MARKERS = %w(:: ;; ::: ::::)
|
52
54
|
# FIXME here we reverse the smart quotes; add option to allow them (needs to be handled carefully)
|
53
55
|
SMART_QUOTE_ENTITY_TO_MARKUP = { ldquo: ?", rdquo: ?", lsquo: ?', rsquo: ?' }
|
@@ -77,12 +79,13 @@ module Kramdown; module AsciiDoc
|
|
77
79
|
right: '>',
|
78
80
|
}
|
79
81
|
|
80
|
-
|
82
|
+
NON_DEFAULT_TABLE_ALIGNMENTS = [:center, :right]
|
83
|
+
|
81
84
|
CommentPrefixRx = /^ *! ?/m
|
82
85
|
CssPropDelimRx = /\s*;\s*/
|
83
86
|
MenuRefRx = /^([\p{Word}&].*?)\s>\s([\p{Word}&].*(?:\s>\s|$))+/
|
84
87
|
ReplaceableTextRx = /[-=]>|<[-=]|\.\.\./
|
85
|
-
|
88
|
+
SmartApostropheRx = /\b’\b/
|
86
89
|
TrailingSpaceRx = / +$/
|
87
90
|
TypographicSymbolRx = /[“”‘’—–…]/
|
88
91
|
XmlCommentRx = /\A<!--(.*)-->\Z/m
|
@@ -90,14 +93,12 @@ module Kramdown; module AsciiDoc
|
|
90
93
|
VoidElement = Element.new nil
|
91
94
|
|
92
95
|
LF = ?\n
|
93
|
-
LFx2 = LF * 2
|
94
96
|
|
95
97
|
def initialize root, opts
|
96
98
|
super
|
97
|
-
@header = []
|
98
99
|
@attributes = opts[:attributes] || {}
|
99
100
|
@imagesdir = (@attributes.delete 'implicit-imagesdir') || @attributes['imagesdir']
|
100
|
-
@
|
101
|
+
@current_heading_level = nil
|
101
102
|
end
|
102
103
|
|
103
104
|
def convert el, opts = {}
|
@@ -105,29 +106,27 @@ module Kramdown; module AsciiDoc
|
|
105
106
|
end
|
106
107
|
|
107
108
|
def convert_root el, opts
|
108
|
-
|
109
|
-
|
109
|
+
writer = Writer.new
|
110
|
+
el = extract_prologue el, (opts.merge writer: writer)
|
111
|
+
traverse el, (opts.merge writer: writer)
|
110
112
|
if (fallback_doctitle = @attributes.delete 'title')
|
111
|
-
|
113
|
+
writer.doctitle ||= fallback_doctitle
|
112
114
|
end
|
113
|
-
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
def convert_blank el, opts
|
118
|
-
nil
|
115
|
+
writer.add_attributes @attributes unless @attributes.empty?
|
116
|
+
writer.to_s.gsub TrailingSpaceRx, ''
|
119
117
|
end
|
120
118
|
|
121
119
|
def convert_heading el, opts
|
122
|
-
|
123
|
-
style = []
|
120
|
+
(writer = opts[:writer]).start_block
|
124
121
|
level = el.options[:level]
|
125
|
-
|
122
|
+
style = []
|
123
|
+
# Q: should writer track last heading level?
|
124
|
+
if (discrete = @current_heading_level && level > @current_heading_level + 1)
|
126
125
|
# TODO make block title promotion an option (allow certain levels and titles)
|
127
|
-
#if ((raw_text = el.options[:raw_text]) == 'Example' || raw_text == 'Examples') &&
|
128
126
|
if level == 5 && (next_2_siblings = (siblings = opts[:parent].children).slice (siblings.index el) + 1, 2) &&
|
129
127
|
next_2_siblings.any? {|sibling| sibling.type == :codeblock }
|
130
|
-
|
128
|
+
writer.add_line %(.#{compose_text el, strip: true})
|
129
|
+
return
|
131
130
|
end
|
132
131
|
style << 'discrete'
|
133
132
|
end
|
@@ -139,39 +138,36 @@ module Kramdown; module AsciiDoc
|
|
139
138
|
elsif (role = el.attr['class'])
|
140
139
|
style << %(.#{role.tr ' ', '.'})
|
141
140
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
if level == 1 &&
|
146
|
-
|
141
|
+
lines = []
|
142
|
+
lines << %([#{style.join}]) unless style.empty?
|
143
|
+
lines << %(#{'=' * level} #{compose_text el, strip: true})
|
144
|
+
if level == 1 && writer.empty? && @current_heading_level != 1
|
145
|
+
writer.header.push(*lines)
|
147
146
|
nil
|
148
147
|
else
|
149
148
|
@attributes['doctype'] = 'book' if level == 1
|
150
|
-
|
149
|
+
writer.add_lines lines
|
151
150
|
end
|
151
|
+
@current_heading_level = level unless discrete
|
152
|
+
nil
|
152
153
|
end
|
153
154
|
|
154
155
|
# Kramdown incorrectly uses the term header for headings
|
155
156
|
alias convert_header convert_heading
|
156
157
|
|
158
|
+
def convert_blank el, opts
|
159
|
+
end
|
160
|
+
|
157
161
|
def convert_p el, opts
|
158
|
-
|
159
|
-
# NOTE :prev option not set indicates primary text; convert_li appends LF
|
160
|
-
return inner el, opts unless opts[:prev]
|
161
|
-
parent.options[:compound] = true
|
162
|
-
opts[:result].pop unless opts[:result][-1]
|
163
|
-
prefix, suffix = %(#{LF}+#{LF}), ''
|
164
|
-
else
|
165
|
-
prefix, suffix = '', LFx2
|
166
|
-
end
|
162
|
+
(writer = opts[:writer]).start_block
|
167
163
|
if (children = el.children).empty?
|
168
|
-
|
164
|
+
lines = ['{blank}']
|
169
165
|
# NOTE detect plain admonition marker (e.g, Note: ...)
|
166
|
+
# TODO these conditionals could be optimized
|
170
167
|
elsif (child_i = children[0]).type == :text && (child_i_text = child_i.value).start_with?(*ADMON_MARKERS)
|
171
168
|
marker, child_i_text = child_i_text.split ': ', 2
|
172
|
-
|
173
|
-
|
174
|
-
contents = inner el, opts
|
169
|
+
children = [(clone child_i, value: %(#{ADMON_TYPE_MAP[marker]}: #{child_i_text}))] + (children.drop 1)
|
170
|
+
lines = compose_text children, parent: el, strip: true, split: true
|
175
171
|
# NOTE detect formatted admonition marker (e.g., *Note:* ...)
|
176
172
|
elsif (child_i.type == :strong || child_i.type == :em) &&
|
177
173
|
(marker_el = child_i.children[0]) && ((marker = ADMON_FORMATTED_MARKERS[marker_el.value]) ||
|
@@ -179,329 +175,345 @@ module Kramdown; module AsciiDoc
|
|
179
175
|
((child_ii_text = child_ii.value).start_with? ': ')))
|
180
176
|
children = children.drop 1
|
181
177
|
children[0] = clone child_ii, value: (child_ii_text.slice 1, child_ii_text.length) if child_ii
|
182
|
-
|
183
|
-
|
178
|
+
# Q: should we only rstrip?
|
179
|
+
lines = compose_text children, parent: el, strip: true, split: true
|
180
|
+
lines.unshift %(#{ADMON_TYPE_MAP[marker]}: #{lines.shift})
|
184
181
|
else
|
185
|
-
|
182
|
+
lines = compose_text el, strip: true, split: true
|
186
183
|
end
|
187
|
-
|
184
|
+
writer.add_lines lines
|
188
185
|
end
|
189
186
|
|
190
|
-
#
|
187
|
+
# Q: should we delete blank line between blocks in a nested conversation?
|
188
|
+
# TODO use shorthand for blockquote when contents is paragraph only; or always?
|
191
189
|
def convert_blockquote el, opts
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
190
|
+
(writer = opts[:writer]).start_block
|
191
|
+
traverse el, (opts.merge writer: (block_writer = Writer.new), blockquote_depth: (depth = opts[:blockquote_depth] || 0) + 1)
|
192
|
+
contents = block_writer.body
|
193
|
+
if contents[0].start_with?(*ADMON_MARKERS_ASCIIDOC) && !(contents.include? '')
|
194
|
+
writer.add_lines contents
|
197
195
|
else
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
contents = lines.join LF
|
210
|
-
end
|
211
|
-
result << boundary
|
212
|
-
result << contents
|
213
|
-
result << boundary
|
214
|
-
result.unshift list_continuation if list_continuation
|
215
|
-
%(#{result.join LF}#{suffix})
|
196
|
+
if contents.size > 1 && (contents[-1].start_with? '-- ')
|
197
|
+
attribution = (attribution_line = contents.pop).slice 3, attribution_line.length
|
198
|
+
writer.add_line %([,#{attribution}])
|
199
|
+
contents.pop while contents.size > 0 && contents[-1].empty?
|
200
|
+
end
|
201
|
+
# Q: should writer handle delimited block nesting?
|
202
|
+
delimiter = '____' + (depth > 0 ? '__' * depth : '')
|
203
|
+
writer.start_delimited_block delimiter
|
204
|
+
writer.add_lines contents
|
205
|
+
writer.end_delimited_block
|
206
|
+
end
|
216
207
|
end
|
217
208
|
|
209
|
+
# TODO match logic from ditarx
|
218
210
|
def convert_codeblock el, opts
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
opts[:result] << current_line.chomp
|
224
|
-
end unless opts[:result].empty?
|
225
|
-
list_continuation = %(#{LF}+)
|
226
|
-
suffix = ''
|
227
|
-
else
|
228
|
-
suffix = LFx2
|
211
|
+
writer = opts[:writer]
|
212
|
+
# NOTE hack to down-convert level-5 heading to block title
|
213
|
+
if (current_line = writer.current_line) && (!(current_line.start_with? '.') || (current_line.start_with? '. '))
|
214
|
+
writer.start_block
|
229
215
|
end
|
230
|
-
|
216
|
+
lines = el.value.rstrip.split LF
|
231
217
|
if (lang = el.attr['class'])
|
232
218
|
# NOTE Kramdown always prefixes class with language-
|
233
219
|
# TODO remap lang if requested
|
234
|
-
|
235
|
-
elsif (prompt =
|
236
|
-
|
220
|
+
writer.add_line %([source,#{lang = lang.slice 9, lang.length}])
|
221
|
+
elsif (prompt = lines[0].start_with? '$ ')
|
222
|
+
writer.add_line %([source,#{lang = 'console'}]) if lines.include? ''
|
237
223
|
end
|
238
224
|
if lang || (el.options[:fenced] && !prompt)
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
elsif !prompt && (
|
243
|
-
|
244
|
-
|
245
|
-
|
225
|
+
writer.add_line '----'
|
226
|
+
writer.add_lines lines
|
227
|
+
writer.add_line '----'
|
228
|
+
elsif !prompt && (lines.include? '')
|
229
|
+
writer.add_line '....'
|
230
|
+
writer.add_lines lines
|
231
|
+
writer.add_line '....'
|
246
232
|
else
|
247
|
-
|
248
|
-
|
233
|
+
# NOTE clear the list continuation (is the condition necessary?)
|
234
|
+
writer.clear_line if writer.current_line == '+'
|
235
|
+
writer.add_line lines.map {|l| %( #{l}) }
|
249
236
|
end
|
250
|
-
result.unshift list_continuation if list_continuation
|
251
|
-
%(#{result.join LF}#{suffix})
|
252
237
|
end
|
253
238
|
|
254
|
-
def
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
239
|
+
def convert_img el, opts
|
240
|
+
if !(parent = opts[:parent]) || parent.type == :p && parent.children.size == 1
|
241
|
+
style = []
|
242
|
+
if (id = el.attr['id'])
|
243
|
+
style << %(##{id})
|
244
|
+
end
|
245
|
+
if (role = el.attr['class'])
|
246
|
+
style << %(.#{role.tr ' ', '.'})
|
247
|
+
end
|
248
|
+
block_attributes_line = %([#{style.join}]) unless style.empty?
|
249
|
+
block = true
|
250
|
+
end
|
251
|
+
macro_attrs = [nil]
|
252
|
+
if (alt_text = el.attr['alt'])
|
253
|
+
macro_attrs[0] = alt_text unless alt_text.empty?
|
254
|
+
end
|
255
|
+
if (width = el.attr['width'])
|
256
|
+
macro_attrs << width
|
257
|
+
elsif (css = el.attr['style']) && (width_css = (css.split CssPropDelimRx).find {|p| p.start_with? 'width:' })
|
258
|
+
width = (width_css.slice (width_css.index ':') + 1, width_css.length).strip
|
259
|
+
width = width.to_f.round unless width.end_with? '%'
|
260
|
+
macro_attrs << width
|
261
|
+
end
|
262
|
+
if macro_attrs.size == 1 && (alt_text = macro_attrs.pop)
|
263
|
+
macro_attrs << alt_text
|
264
|
+
end
|
265
|
+
if (url = opts[:url])
|
266
|
+
macro_attrs << %(link=#{url})
|
267
|
+
end
|
268
|
+
src = el.attr['src']
|
269
|
+
if (imagesdir = @imagesdir) && (src.start_with? %(#{imagesdir}/))
|
270
|
+
src = src.slice imagesdir.length + 1, src.length
|
262
271
|
end
|
263
|
-
|
264
|
-
if
|
265
|
-
|
266
|
-
|
272
|
+
writer = opts[:writer]
|
273
|
+
if block
|
274
|
+
writer.start_block
|
275
|
+
writer.add_line block_attributes_line if block_attributes_line
|
276
|
+
writer.add_line %(image::#{src}[#{macro_attrs.join ','}])
|
267
277
|
else
|
268
|
-
|
269
|
-
opts[:list_level] -= 1
|
278
|
+
writer.append %(image:#{src}[#{macro_attrs.join ','}])
|
270
279
|
end
|
271
|
-
|
280
|
+
end
|
281
|
+
|
282
|
+
def convert_ul el, opts
|
283
|
+
nested = (parent = opts[:parent]) && (parent.type == :li || parent.type == :dd)
|
284
|
+
(writer = opts[:writer]).start_list nested && parent.type != :dd && !parent.options[:compound]
|
285
|
+
level_opt = el.type == :dl ? :dlist_level : :list_level
|
286
|
+
level = opts[level_opt] ? (opts[level_opt] += 1) : (opts[level_opt] = 1)
|
287
|
+
traverse el, opts
|
288
|
+
opts.delete level_opt if (opts[level_opt] -= 1) < 1
|
289
|
+
writer.end_list nested
|
272
290
|
end
|
273
291
|
|
274
292
|
alias convert_ol convert_ul
|
275
293
|
alias convert_dl convert_ul
|
276
294
|
|
277
295
|
def convert_li el, opts
|
278
|
-
|
296
|
+
writer = opts[:writer]
|
297
|
+
writer.add_blank_line if (prev = opts[:prev]) && prev.options[:compound]
|
279
298
|
marker = opts[:parent].type == :ol ? '.' : '*'
|
280
299
|
indent = (level = opts[:list_level]) - 1
|
281
|
-
|
300
|
+
primary, remaining = [(children = el.children.dup).shift, children]
|
301
|
+
lines = compose_text [primary], parent: el, strip: true, split: true
|
302
|
+
lines.unshift %(#{indent > 0 ? ' ' * indent : ''}#{marker * level} #{lines.shift})
|
303
|
+
writer.add_lines lines
|
304
|
+
unless remaining.empty?
|
305
|
+
next_node = remaining.find {|n| n.type != :blank }
|
306
|
+
el.options[:compound] = true if next_node && (BLOCK_TYPES.include? next_node.type)
|
307
|
+
traverse remaining, (opts.merge parent: el)
|
308
|
+
end
|
282
309
|
end
|
283
310
|
|
284
311
|
def convert_dt el, opts
|
285
|
-
|
286
|
-
marker = DLIST_MARKERS[opts[:
|
287
|
-
|
312
|
+
term = compose_text el, strip: true
|
313
|
+
marker = DLIST_MARKERS[opts[:dlist_level] - 1]
|
314
|
+
#opts[:writer].add_blank_line if (prev = opts[:prev]) && prev.options[:compound]
|
315
|
+
opts[:writer].add_blank_line if opts[:prev]
|
316
|
+
opts[:writer].add_line %(#{term}#{marker})
|
288
317
|
end
|
289
318
|
|
290
319
|
def convert_dd el, opts
|
291
|
-
|
320
|
+
primary, remaining = [(children = el.children.dup).shift, children]
|
321
|
+
primary_lines = compose_text [primary], parent: el, strip: true, split: true
|
322
|
+
if primary_lines.size == 1
|
323
|
+
opts[:writer].append %( #{primary_lines[0]})
|
324
|
+
else
|
325
|
+
el.options[:compound] = true
|
326
|
+
opts[:writer].add_lines primary_lines
|
327
|
+
end
|
328
|
+
unless remaining.empty?
|
329
|
+
next_node = remaining.find {|n| n.type != :blank }
|
330
|
+
el.options[:compound] = true if next_node && (BLOCK_TYPES.include? next_node.type)
|
331
|
+
traverse remaining, (opts.merge parent: el)
|
332
|
+
end
|
292
333
|
end
|
293
334
|
|
294
335
|
def convert_table el, opts
|
295
336
|
head = nil
|
296
337
|
cols = (alignments = el.options[:alignment]).size
|
297
|
-
if alignments.any? {|align|
|
338
|
+
if alignments.any? {|align| NON_DEFAULT_TABLE_ALIGNMENTS.include? align }
|
298
339
|
colspecs = alignments.map {|align| TABLE_ALIGNMENTS[align] }.join ','
|
299
340
|
colspecs = %("#{colspecs}") if cols > 1
|
300
341
|
end
|
301
|
-
|
342
|
+
table_buffer = ['|===']
|
302
343
|
el.children.each do |container|
|
303
344
|
container.children.each do |row|
|
304
|
-
|
345
|
+
row_buffer = []
|
305
346
|
row.children.each do |cell|
|
306
|
-
|
347
|
+
# TODO if using sentence-per-line, append to row_buffer as separate lines
|
348
|
+
cell_contents = compose_text cell
|
307
349
|
cell_contents = cell_contents.gsub '|', '\|' if cell_contents.include? '|'
|
308
|
-
|
350
|
+
row_buffer << %(| #{cell_contents})
|
309
351
|
end
|
310
352
|
if container.type == :thead
|
311
353
|
head = true
|
312
|
-
|
354
|
+
row_buffer = [row_buffer * ' ', '']
|
313
355
|
elsif cols > 1
|
314
|
-
|
356
|
+
row_buffer << ''
|
315
357
|
end
|
316
|
-
|
358
|
+
table_buffer.concat row_buffer
|
317
359
|
end
|
318
360
|
end
|
361
|
+
table_buffer.pop if table_buffer[-1] == ''
|
362
|
+
table_buffer << '|==='
|
363
|
+
(writer = opts[:writer]).start_block
|
319
364
|
if colspecs
|
320
|
-
|
365
|
+
writer.add_line %([cols=#{colspecs}])
|
321
366
|
elsif !head && cols > 1
|
322
|
-
|
367
|
+
writer.add_line %([cols=#{cols}*])
|
323
368
|
end
|
324
|
-
|
325
|
-
table_buf << '|==='
|
326
|
-
%(#{table_buf * LF}#{LFx2})
|
369
|
+
opts[:writer].add_lines table_buffer
|
327
370
|
end
|
328
371
|
|
329
372
|
def convert_hr el, opts
|
330
|
-
|
373
|
+
(writer = opts[:writer]).start_block
|
374
|
+
writer.add_line '\'\'\''
|
331
375
|
end
|
332
376
|
|
333
|
-
def
|
334
|
-
if (
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
377
|
+
def convert_a el, opts
|
378
|
+
if (url = el.attr['href']).start_with? '#'
|
379
|
+
opts[:writer].append %(<<#{url.slice 1, url.length},#{compose_text el, strip: true}>>)
|
380
|
+
elsif url.start_with? 'https://', 'http://'
|
381
|
+
if (children = el.children).size == 1 && (child_i = el.children[0]).type == :img
|
382
|
+
convert_img child_i, parent: opts[:parent], index: 0, url: url, writer: opts[:writer]
|
383
|
+
else
|
384
|
+
bare = ((text = compose_text el, strip: true).chomp '/') == (url.chomp '/')
|
385
|
+
url = url.gsub '__', '%5F%5F' if (url.include? '__')
|
386
|
+
opts[:writer].append bare ? url : %(#{url}[#{text.gsub ']', '\]'}])
|
387
|
+
end
|
388
|
+
elsif url.end_with? '.md'
|
389
|
+
opts[:writer].append %(xref:#{url.slice 0, url.length - 3}.adoc[#{(compose_text el, strip: true).gsub ']', '\]'}])
|
342
390
|
else
|
343
|
-
|
391
|
+
opts[:writer].append %(link:#{url}[#{(compose_text el, strip: true).gsub ']', '\]'}])
|
344
392
|
end
|
345
|
-
end
|
393
|
+
end
|
346
394
|
|
347
395
|
def convert_codespan el, opts
|
348
|
-
(val = el.value) =~ ReplaceableTextRx ? %(`+#{val}+`) : %(`#{val}`)
|
396
|
+
opts[:writer].append (val = el.value) =~ ReplaceableTextRx ? %(`+#{val}+`) : %(`#{val}`)
|
349
397
|
end
|
350
398
|
|
351
399
|
def convert_em el, opts
|
352
|
-
%(_#{
|
400
|
+
opts[:writer].append %(_#{compose_text el}_)
|
353
401
|
end
|
354
402
|
|
355
403
|
def convert_strong el, opts
|
356
|
-
|
357
|
-
if (
|
404
|
+
text = compose_text el
|
405
|
+
if (text.include? ' > ') && MenuRefRx =~ text
|
358
406
|
@attributes['experimental'] = ''
|
359
|
-
%(menu:#{$1}[#{$2}])
|
407
|
+
opts[:writer].append %(menu:#{$1}[#{$2}])
|
360
408
|
else
|
361
|
-
%(*#{
|
409
|
+
opts[:writer].append %(*#{text}*)
|
362
410
|
end
|
363
411
|
end
|
364
412
|
|
413
|
+
def convert_text el, opts
|
414
|
+
if (text = el.value).include? '++'
|
415
|
+
@attributes['pp'] = '{plus}{plus}'
|
416
|
+
text = text.gsub '++', '{pp}'
|
417
|
+
end
|
418
|
+
# Q: should we replace with single space instead?
|
419
|
+
text = text.gsub ' ', '{nbsp}' if text.include? ' '
|
420
|
+
text = text.gsub '^', '{caret}' if (text.include? '^') && text != '^'
|
421
|
+
text = text.gsub '<=', '\<=' if text.include? '<='
|
422
|
+
unless text.ascii_only?
|
423
|
+
text = (text.gsub SmartApostropheRx, ?').gsub TypographicSymbolRx, TYPOGRAPHIC_SYMBOL_TO_MARKUP
|
424
|
+
end
|
425
|
+
opts[:writer].append text
|
426
|
+
end
|
427
|
+
|
365
428
|
# NOTE this logic assumes the :hard_wrap option is disabled in the parser
|
366
429
|
def convert_br el, opts
|
367
|
-
|
368
|
-
|
430
|
+
writer = opts[:writer]
|
431
|
+
if writer.empty?
|
432
|
+
writer.append '{blank} +'
|
369
433
|
else
|
370
|
-
|
434
|
+
writer.append %(#{(writer.current_line.end_with? ' ') ? '' : ' '}+)
|
371
435
|
end
|
372
436
|
if el.options[:html_tag]
|
373
437
|
siblings = opts[:parent].children
|
374
|
-
|
375
|
-
|
376
|
-
|
438
|
+
unless (next_el = siblings[(siblings.index el) + 1] || VoidElement).type == :text && (next_el.value.start_with? LF)
|
439
|
+
writer.add_blank_line
|
440
|
+
end
|
377
441
|
end
|
378
|
-
%(#{prefix}+#{suffix})
|
379
|
-
end
|
380
|
-
|
381
|
-
def convert_smart_quote el, opts
|
382
|
-
SMART_QUOTE_ENTITY_TO_MARKUP[el.value]
|
383
442
|
end
|
384
443
|
|
385
444
|
def convert_entity el, opts
|
386
|
-
RESOLVE_ENTITY_TABLE[el.value.code_point] || el.options[:original]
|
445
|
+
opts[:writer].append RESOLVE_ENTITY_TABLE[el.value.code_point] || el.options[:original]
|
387
446
|
end
|
388
447
|
|
389
|
-
def
|
390
|
-
|
391
|
-
%(<<#{url.slice 1, url.length},#{inner el, opts}>>)
|
392
|
-
elsif url.start_with? 'https://', 'http://'
|
393
|
-
if (children = el.children).size == 1 && (child_i = el.children[0]).type == :img
|
394
|
-
convert_img child_i, parent: opts[:parent], index: 0, url: url
|
395
|
-
else
|
396
|
-
bare = ((contents = inner el, opts).chomp '/') == (url.chomp '/')
|
397
|
-
url = url.gsub '__', '%5F%5F' if (url.include? '__')
|
398
|
-
bare ? url : %(#{url}[#{contents.gsub ']', '\]'}])
|
399
|
-
end
|
400
|
-
elsif url.end_with? '.md'
|
401
|
-
%(xref:#{url.slice 0, url.length - 3}.adoc[#{(inner el, opts).gsub ']', '\]'}])
|
402
|
-
else
|
403
|
-
%(link:#{url}[#{(inner el, opts).gsub ']', '\]'}])
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
def convert_img el, opts
|
408
|
-
if !(parent = opts[:parent]) || parent.type == :p && parent.children.size == 1
|
409
|
-
style = []
|
410
|
-
if (id = el.attr['id'])
|
411
|
-
style << %(##{id})
|
412
|
-
end
|
413
|
-
if (role = el.attr['class'])
|
414
|
-
style << %(.#{role.tr ' ', '.'})
|
415
|
-
end
|
416
|
-
macro_prefix = style.empty? ? 'image::' : ([%([#{style.join}]), 'image::'].join LF)
|
417
|
-
else
|
418
|
-
macro_prefix = 'image:'
|
419
|
-
end
|
420
|
-
macro_attrs = [nil]
|
421
|
-
if (alt_text = el.attr['alt'])
|
422
|
-
macro_attrs[0] = alt_text unless alt_text.empty?
|
423
|
-
end
|
424
|
-
if (width = el.attr['width'])
|
425
|
-
macro_attrs << width
|
426
|
-
elsif (css = el.attr['style']) && (width_css = (css.split CssPropDelimRx).find {|p| p.start_with? 'width:' })
|
427
|
-
width = (width_css.slice (width_css.index ':') + 1, width_css.length).strip
|
428
|
-
width = width.to_f.round unless width.end_with? '%'
|
429
|
-
macro_attrs << width
|
430
|
-
end
|
431
|
-
if macro_attrs.size == 1 && (alt_text = macro_attrs.pop)
|
432
|
-
macro_attrs << alt_text
|
433
|
-
end
|
434
|
-
if (url = opts[:url])
|
435
|
-
macro_attrs << %(link=#{url})
|
436
|
-
end
|
437
|
-
src = el.attr['src']
|
438
|
-
if (imagesdir = @imagesdir) && (src.start_with? %(#{imagesdir}/))
|
439
|
-
src = src.slice imagesdir.length + 1, src.length
|
440
|
-
end
|
441
|
-
%(#{macro_prefix}#{src}[#{macro_attrs.join ','}])
|
448
|
+
def convert_smart_quote el, opts
|
449
|
+
opts[:writer].append SMART_QUOTE_ENTITY_TO_MARKUP[el.value]
|
442
450
|
end
|
443
451
|
|
444
452
|
# NOTE leave enabled so we can down-convert mdash to --
|
445
453
|
def convert_typographic_sym el, opts
|
446
|
-
TYPOGRAPHIC_ENTITY_TO_MARKUP[el.value]
|
454
|
+
opts[:writer].append TYPOGRAPHIC_ENTITY_TO_MARKUP[el.value]
|
447
455
|
end
|
448
456
|
|
449
457
|
def convert_html_element el, opts
|
450
|
-
if (
|
451
|
-
(child_i_i = child_i.children[0])
|
458
|
+
if (tag = el.value) == 'div' && (child_i = el.children[0]) && child_i.options[:transparent] && (child_i_i = child_i.children[0])
|
452
459
|
if child_i_i.type == :img
|
453
|
-
|
454
|
-
|
455
|
-
|
460
|
+
convert_img child_i_i, (opts.merge parent: child_i, index: 0) if child_i.children.size == 1
|
461
|
+
return
|
462
|
+
elsif child_i_i.value == 'span' && ((role = el.attr['class'] || '').start_with? 'note') && child_i_i.attr['class'] == 'notetitle'
|
456
463
|
marker = ADMON_FORMATTED_MARKERS[(child_i_i.children[0] || VoidElement).value] || 'Note'
|
457
|
-
|
458
|
-
|
464
|
+
lines = compose_text (child_i.children.drop 1), parent: child_i, strip: true, split: true
|
465
|
+
lines.unshift %(#{ADMON_TYPE_MAP[marker]}: #{lines.shift})
|
466
|
+
opts[:writer].start_block
|
467
|
+
opts[:writer].add_lines lines
|
468
|
+
return
|
459
469
|
end
|
460
470
|
end
|
461
|
-
|
471
|
+
|
472
|
+
contents = compose_text el, (opts.merge strip: el.options[:category] == :block)
|
462
473
|
attrs = (attrs = el.attr).empty? ? '' : attrs.map {|k, v| %( #{k}="#{v}") }.join
|
463
|
-
case
|
474
|
+
case tag
|
464
475
|
when 'del'
|
465
|
-
%([.line-through]##{contents}#)
|
476
|
+
opts[:writer].append %([.line-through]##{contents}#)
|
466
477
|
when 'sup'
|
467
|
-
%(^#{contents}^)
|
478
|
+
opts[:writer].append %(^#{contents}^)
|
468
479
|
when 'sub'
|
469
|
-
%(~#{contents}~)
|
480
|
+
opts[:writer].append %(~#{contents}~)
|
470
481
|
else
|
471
|
-
%(+++<#{
|
482
|
+
opts[:writer].append %(+++<#{tag}#{attrs}>+++#{contents}+++</#{tag}>+++)
|
472
483
|
end
|
473
484
|
end
|
474
485
|
|
475
486
|
def convert_xml_comment el, opts
|
487
|
+
writer = opts[:writer]
|
476
488
|
XmlCommentRx =~ el.value
|
477
|
-
|
489
|
+
lines = (($1.include? ' !') ? ($1.gsub CommentPrefixRx, '').strip : $1.strip).split LF
|
478
490
|
#siblings = (parent = opts[:parent]) ? parent.children : []
|
479
491
|
if (el.options[:category] == :block)# || (!opts[:result][-1] && siblings[-1] == el)
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
492
|
+
writer.start_block
|
493
|
+
if lines.empty?
|
494
|
+
writer.add_line '//'
|
495
|
+
# Q: should we only use block form if empty line is present?
|
496
|
+
elsif lines.size > 1
|
497
|
+
writer.add_line '////'
|
498
|
+
writer.add_lines lines
|
499
|
+
writer.add_line '////'
|
484
500
|
else
|
485
|
-
%(// #{
|
501
|
+
writer.add_line %(// #{lines[0]})
|
486
502
|
end
|
487
503
|
else
|
488
|
-
if (current_line =
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
prefix = LF
|
493
|
-
opts[:result][-1] = (current_line = current_line.rstrip) if current_line.end_with? ' '
|
494
|
-
end
|
495
|
-
else
|
496
|
-
prefix = ''
|
504
|
+
if (current_line = writer.current_line) && !(current_line.end_with? LF)
|
505
|
+
start_new_line = true
|
506
|
+
# FIXME cleaner API here (writer#strip_line?)
|
507
|
+
writer.current_line.rstrip! if current_line.end_with? ' '
|
497
508
|
end
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
%(#{prefix}#{comment_text.gsub StartOfLinesRx, '// '}#{suffix})
|
509
|
+
lines = lines.map {|l| %(// #{l}) }
|
510
|
+
if start_new_line
|
511
|
+
writer.add_lines lines
|
502
512
|
else
|
503
|
-
|
513
|
+
writer.append lines.shift
|
514
|
+
writer.add_lines lines unless lines.empty?
|
504
515
|
end
|
516
|
+
writer.add_blank_line
|
505
517
|
end
|
506
518
|
end
|
507
519
|
|
@@ -509,22 +521,12 @@ module Kramdown; module AsciiDoc
|
|
509
521
|
if (child_i = (children = el.children)[0] || VoidElement).type == :xml_comment
|
510
522
|
(prologue_el = el.dup).children = children.take_while {|child| child.type == :xml_comment || child.type == :blank }
|
511
523
|
(el = el.dup).children = children.drop prologue_el.children.size
|
512
|
-
|
524
|
+
traverse prologue_el, (opts.merge writer: (prologue_writer = Writer.new))
|
525
|
+
opts[:writer].header.push(*prologue_writer.body)
|
513
526
|
end
|
514
527
|
el
|
515
528
|
end
|
516
529
|
|
517
|
-
def inner el, opts
|
518
|
-
rstrip = opts.delete :rstrip
|
519
|
-
result = []
|
520
|
-
prev = nil
|
521
|
-
el.children.each_with_index do |child, idx|
|
522
|
-
result << (send %(convert_#{child.type}), child, (opts.merge parent: el, index: idx, result: result, prev: prev))
|
523
|
-
prev = child
|
524
|
-
end
|
525
|
-
rstrip ? result.join.rstrip : result.join
|
526
|
-
end
|
527
|
-
|
528
530
|
def clone el, properties
|
529
531
|
el = el.dup
|
530
532
|
properties.each do |name, value|
|
@@ -532,6 +534,35 @@ module Kramdown; module AsciiDoc
|
|
532
534
|
end
|
533
535
|
el
|
534
536
|
end
|
537
|
+
|
538
|
+
def traverse el, opts = {}
|
539
|
+
prev = nil
|
540
|
+
if ::Array === el
|
541
|
+
nodes = el
|
542
|
+
parent = opts[:parent]
|
543
|
+
else
|
544
|
+
nodes = (parent = el).children
|
545
|
+
end
|
546
|
+
nodes.each_with_index do |child, idx|
|
547
|
+
convert child, (opts.merge parent: parent, index: idx, prev: prev)
|
548
|
+
prev = child
|
549
|
+
end
|
550
|
+
nil
|
551
|
+
end
|
552
|
+
|
553
|
+
# Q: should we support rstrip in addition to strip?
|
554
|
+
# TODO add escaping of closing square bracket
|
555
|
+
# TODO reflow text
|
556
|
+
def compose_text el, opts = {}
|
557
|
+
strip = opts.delete :strip
|
558
|
+
split = opts.delete :split
|
559
|
+
# Q: do we want to merge or just start fresh?
|
560
|
+
traverse el, (opts.merge writer: (span_writer = Writer.new))
|
561
|
+
# NOTE there should only ever be one line
|
562
|
+
text = span_writer.body.join LF
|
563
|
+
text = text.strip if strip
|
564
|
+
split ? (text.split LF) : text
|
565
|
+
end
|
535
566
|
end
|
536
567
|
end; end
|
537
568
|
|