asciidoctor 2.0.10 → 2.0.15
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 +211 -22
- data/LICENSE +1 -1
- data/README-de.adoc +12 -13
- data/README-fr.adoc +11 -15
- data/README-jp.adoc +9 -17
- data/README-zh_CN.adoc +17 -18
- data/README.adoc +140 -132
- data/asciidoctor.gemspec +6 -6
- data/data/locale/attributes-ar.adoc +4 -3
- data/data/locale/attributes-be.adoc +23 -0
- data/data/locale/attributes-bg.adoc +4 -3
- data/data/locale/attributes-ca.adoc +6 -5
- data/data/locale/attributes-cs.adoc +4 -3
- data/data/locale/attributes-da.adoc +6 -5
- data/data/locale/attributes-de.adoc +4 -4
- data/data/locale/attributes-en.adoc +4 -4
- data/data/locale/attributes-es.adoc +6 -5
- data/data/locale/attributes-fa.adoc +4 -3
- data/data/locale/attributes-fi.adoc +4 -3
- data/data/locale/attributes-fr.adoc +6 -5
- data/data/locale/attributes-hu.adoc +4 -3
- data/data/locale/attributes-id.adoc +4 -3
- data/data/locale/attributes-it.adoc +6 -5
- data/data/locale/attributes-ja.adoc +4 -3
- data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
- data/data/locale/attributes-nb.adoc +4 -3
- data/data/locale/attributes-nl.adoc +6 -5
- data/data/locale/attributes-nn.adoc +4 -3
- data/data/locale/attributes-pl.adoc +8 -7
- data/data/locale/attributes-pt.adoc +6 -5
- data/data/locale/attributes-pt_BR.adoc +6 -5
- data/data/locale/attributes-ro.adoc +4 -3
- data/data/locale/attributes-ru.adoc +6 -5
- data/data/locale/attributes-sr.adoc +4 -4
- data/data/locale/attributes-sr_Latn.adoc +4 -4
- data/data/locale/attributes-sv.adoc +4 -4
- data/data/locale/attributes-tr.adoc +4 -3
- data/data/locale/attributes-uk.adoc +6 -5
- data/data/locale/attributes-zh_CN.adoc +4 -3
- data/data/locale/attributes-zh_TW.adoc +4 -3
- data/data/reference/syntax.adoc +14 -7
- data/data/stylesheets/asciidoctor-default.css +27 -28
- data/lib/asciidoctor.rb +38 -12
- data/lib/asciidoctor/abstract_block.rb +9 -4
- data/lib/asciidoctor/abstract_node.rb +16 -6
- data/lib/asciidoctor/attribute_list.rb +64 -72
- data/lib/asciidoctor/cli/invoker.rb +2 -0
- data/lib/asciidoctor/cli/options.rb +10 -9
- data/lib/asciidoctor/convert.rb +167 -162
- data/lib/asciidoctor/converter.rb +13 -12
- data/lib/asciidoctor/converter/docbook5.rb +29 -12
- data/lib/asciidoctor/converter/html5.rb +69 -46
- data/lib/asciidoctor/converter/manpage.rb +68 -49
- data/lib/asciidoctor/converter/template.rb +3 -0
- data/lib/asciidoctor/document.rb +43 -50
- data/lib/asciidoctor/extensions.rb +2 -4
- data/lib/asciidoctor/helpers.rb +20 -15
- data/lib/asciidoctor/load.rb +102 -101
- data/lib/asciidoctor/parser.rb +40 -32
- data/lib/asciidoctor/path_resolver.rb +14 -12
- data/lib/asciidoctor/reader.rb +22 -13
- data/lib/asciidoctor/rx.rb +7 -6
- data/lib/asciidoctor/substitutors.rb +78 -57
- data/lib/asciidoctor/syntax_highlighter.rb +8 -5
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
- data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -7
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -19
- data/lib/asciidoctor/table.rb +52 -23
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +8 -8
- data/man/asciidoctor.adoc +4 -4
- metadata +16 -15
data/lib/asciidoctor/parser.rb
CHANGED
@@ -119,7 +119,7 @@ class Parser
|
|
119
119
|
# returns the Hash of orphan block attributes captured above the header
|
120
120
|
def self.parse_document_header(reader, document)
|
121
121
|
# capture lines of block-level metadata and plow away comment lines that precede first block
|
122
|
-
block_attrs = parse_block_metadata_lines reader, document
|
122
|
+
block_attrs = reader.skip_blank_lines ? (parse_block_metadata_lines reader, document) : {}
|
123
123
|
doc_attrs = document.attributes
|
124
124
|
|
125
125
|
# special case, block title is not allowed above document title,
|
@@ -144,7 +144,10 @@ class Parser
|
|
144
144
|
l0_section_title = nil
|
145
145
|
else
|
146
146
|
document.title = l0_section_title
|
147
|
-
doc_attrs['doctitle'] = doctitle_attr_val = document.
|
147
|
+
if (doc_attrs['doctitle'] = doctitle_attr_val = document.sub_specialchars l0_section_title).include? ATTR_REF_HEAD
|
148
|
+
# QUESTION should we defer substituting attributes until the end of the header? or should we substitute again if necessary?
|
149
|
+
doc_attrs['doctitle'] = doctitle_attr_val = document.sub_attributes doctitle_attr_val, attribute_missing: 'skip'
|
150
|
+
end
|
148
151
|
end
|
149
152
|
document.header.source_location = source_location if source_location
|
150
153
|
# default to compat-mode if document has setext doctitle
|
@@ -215,9 +218,6 @@ class Parser
|
|
215
218
|
name_section = initialize_section reader, document, {}
|
216
219
|
name_section_buffer = (reader.read_lines_until break_on_blank_lines: true, skip_line_comments: true).map {|l| l.lstrip }.join ' '
|
217
220
|
if ManpageNamePurposeRx =~ name_section_buffer
|
218
|
-
doc_attrs['manname-title'] ||= name_section.title
|
219
|
-
doc_attrs['manname-id'] = name_section.id if name_section.id
|
220
|
-
doc_attrs['manpurpose'] = $2
|
221
221
|
if (manname = $1).include? ATTR_REF_HEAD
|
222
222
|
manname = document.sub_attributes manname
|
223
223
|
end
|
@@ -226,8 +226,14 @@ class Parser
|
|
226
226
|
else
|
227
227
|
mannames = [manname]
|
228
228
|
end
|
229
|
+
if (manpurpose = $2).include? ATTR_REF_HEAD
|
230
|
+
manpurpose = document.sub_attributes manpurpose
|
231
|
+
end
|
232
|
+
doc_attrs['manname-title'] ||= name_section.title
|
233
|
+
doc_attrs['manname-id'] = name_section.id if name_section.id
|
229
234
|
doc_attrs['manname'] = manname
|
230
235
|
doc_attrs['mannames'] = mannames
|
236
|
+
doc_attrs['manpurpose'] = manpurpose
|
231
237
|
if document.backend == 'manpage'
|
232
238
|
doc_attrs['docname'] = manname
|
233
239
|
doc_attrs['outfilesuffix'] = %(.#{manvolnum})
|
@@ -327,7 +333,7 @@ class Parser
|
|
327
333
|
if current_level == 0
|
328
334
|
part = book
|
329
335
|
elsif current_level == 1 && section.special
|
330
|
-
# NOTE technically preface
|
336
|
+
# NOTE technically preface sections are only permitted in the book doctype
|
331
337
|
unless (sectname = section.sectname) == 'appendix' || sectname == 'preface' || sectname == 'abstract'
|
332
338
|
expected_next_level = nil
|
333
339
|
end
|
@@ -433,8 +439,10 @@ class Parser
|
|
433
439
|
# is treated like an untitled section
|
434
440
|
elsif preamble # implies parent == document
|
435
441
|
if preamble.blocks?
|
442
|
+
if book || document.blocks[1] || !Compliance.unwrap_standalone_preamble
|
443
|
+
preamble.source_location = preamble.blocks[0].source_location if document.sourcemap
|
436
444
|
# unwrap standalone preamble (i.e., document has no sections) except for books, if permissible
|
437
|
-
|
445
|
+
else
|
438
446
|
document.blocks.shift
|
439
447
|
while (child_block = preamble.blocks.shift)
|
440
448
|
document << child_block
|
@@ -858,6 +866,7 @@ class Parser
|
|
858
866
|
when :literal
|
859
867
|
block = build_block(block_context, :verbatim, terminator, parent, reader, attributes)
|
860
868
|
when :example
|
869
|
+
attributes['caption'] = '' if attributes['collapsible-option']
|
861
870
|
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
|
862
871
|
when :quote, :verse
|
863
872
|
AttributeList.rekey(attributes, [nil, 'attribution', 'citetitle'])
|
@@ -899,9 +908,7 @@ class Parser
|
|
899
908
|
# FIXME title and caption should be assigned when block is constructed (though we need to handle all cases)
|
900
909
|
if attributes['title']
|
901
910
|
block.title = block_title = attributes.delete 'title'
|
902
|
-
|
903
|
-
block.assign_caption (attributes.delete 'caption')
|
904
|
-
end
|
911
|
+
block.assign_caption (attributes.delete 'caption') if CAPTION_ATTRIBUTE_NAMES[block.context]
|
905
912
|
end
|
906
913
|
# TODO eventually remove the style attribute from the attributes hash
|
907
914
|
#block.style = attributes.delete 'style'
|
@@ -1148,14 +1155,16 @@ class Parser
|
|
1148
1155
|
def self.catalog_inline_anchors text, block, document, reader
|
1149
1156
|
text.scan InlineAnchorScanRx do
|
1150
1157
|
if (id = $1)
|
1151
|
-
if (reftext = $2)
|
1152
|
-
next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
|
1153
|
-
end
|
1158
|
+
next if (reftext = $2) && (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
|
1154
1159
|
else
|
1155
1160
|
id = $3
|
1156
1161
|
if (reftext = $4)
|
1157
|
-
|
1158
|
-
|
1162
|
+
if reftext.include? ']'
|
1163
|
+
reftext = reftext.gsub '\]', ']'
|
1164
|
+
reftext = document.sub_attributes reftext if reftext.include? ATTR_REF_HEAD
|
1165
|
+
elsif (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
|
1166
|
+
next
|
1167
|
+
end
|
1159
1168
|
end
|
1160
1169
|
end
|
1161
1170
|
unless document.register :refs, [id, (Inline.new block, :anchor, reftext, type: :ref, id: id)]
|
@@ -1311,7 +1320,7 @@ class Parser
|
|
1311
1320
|
list_item.marker = sibling_trait
|
1312
1321
|
if ordinal == 0 && !style
|
1313
1322
|
# using list level makes more sense, but we don't track it
|
1314
|
-
# basing style on marker level is compliant with AsciiDoc
|
1323
|
+
# basing style on marker level is compliant with AsciiDoc.py
|
1315
1324
|
list_block.style = implicit_style || ((ORDERED_LIST_STYLES[sibling_trait.length - 1] || 'arabic').to_s)
|
1316
1325
|
end
|
1317
1326
|
if item_text.start_with?('[[') && LeadingInlineAnchorRx =~ item_text
|
@@ -2119,6 +2128,8 @@ class Parser
|
|
2119
2128
|
name = 'sectnums'
|
2120
2129
|
elsif name == 'hardbreaks'
|
2121
2130
|
name = 'hardbreaks-option'
|
2131
|
+
elsif name == 'showtitle'
|
2132
|
+
store_attribute 'notitle', (value ? nil : ''), doc, attrs
|
2122
2133
|
end
|
2123
2134
|
|
2124
2135
|
if doc
|
@@ -2273,9 +2284,15 @@ class Parser
|
|
2273
2284
|
end
|
2274
2285
|
|
2275
2286
|
skipped = table_reader.skip_blank_lines || 0
|
2287
|
+
if attributes['header-option']
|
2288
|
+
table.has_header_option = true
|
2289
|
+
elsif skipped == 0 && !attributes['noheader-option']
|
2290
|
+
# NOTE: assume table has header until we know otherwise; if it doesn't (nil), cells in first row get reprocessed
|
2291
|
+
table.has_header_option = :implicit
|
2292
|
+
implicit_header = true
|
2293
|
+
end
|
2276
2294
|
parser_ctx = Table::ParserContext.new table_reader, table, attributes
|
2277
2295
|
format, loop_idx, implicit_header_boundary = parser_ctx.format, -1, nil
|
2278
|
-
implicit_header = true unless skipped > 0 || attributes['header-option'] || attributes['noheader-option']
|
2279
2296
|
|
2280
2297
|
while (line = table_reader.read_line)
|
2281
2298
|
if (beyond_first = (loop_idx += 1) > 0) && line.empty?
|
@@ -2295,7 +2312,7 @@ class Parser
|
|
2295
2312
|
implicit_header_boundary = nil if implicit_header_boundary
|
2296
2313
|
# otherwise, the cell continues from previous line
|
2297
2314
|
elsif implicit_header_boundary && implicit_header_boundary == loop_idx
|
2298
|
-
implicit_header
|
2315
|
+
table.has_header_option = implicit_header = implicit_header_boundary = nil
|
2299
2316
|
end
|
2300
2317
|
end
|
2301
2318
|
end
|
@@ -2307,7 +2324,7 @@ class Parser
|
|
2307
2324
|
if table_reader.has_more_lines? && table_reader.peek_line.empty?
|
2308
2325
|
implicit_header_boundary = 1
|
2309
2326
|
else
|
2310
|
-
implicit_header =
|
2327
|
+
table.has_header_option = implicit_header = nil
|
2311
2328
|
end
|
2312
2329
|
end
|
2313
2330
|
end
|
@@ -2358,7 +2375,7 @@ class Parser
|
|
2358
2375
|
case format
|
2359
2376
|
when 'csv'
|
2360
2377
|
if parser_ctx.buffer_has_unclosed_quotes?
|
2361
|
-
implicit_header
|
2378
|
+
table.has_header_option = implicit_header = implicit_header_boundary = nil if implicit_header_boundary && loop_idx == 0
|
2362
2379
|
parser_ctx.keep_cell_open
|
2363
2380
|
else
|
2364
2381
|
parser_ctx.close_cell true
|
@@ -2380,15 +2397,8 @@ class Parser
|
|
2380
2397
|
end
|
2381
2398
|
end
|
2382
2399
|
|
2383
|
-
unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
|
2384
|
-
|
2385
|
-
end
|
2386
|
-
|
2387
|
-
if implicit_header
|
2388
|
-
table.has_header_option = true
|
2389
|
-
attributes['header-option'] = ''
|
2390
|
-
end
|
2391
|
-
|
2400
|
+
table.assign_column_widths unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
|
2401
|
+
table.has_header_option = true if implicit_header
|
2392
2402
|
table.partition_header_footer attributes
|
2393
2403
|
|
2394
2404
|
table
|
@@ -2579,9 +2589,7 @@ class Parser
|
|
2579
2589
|
attributes['role'] = (existing_role = attributes['role']).nil_or_empty? ? (parsed_attrs[:role].join ' ') : %(#{existing_role} #{parsed_attrs[:role].join ' '})
|
2580
2590
|
end
|
2581
2591
|
|
2582
|
-
if parsed_attrs.key? :option
|
2583
|
-
(opts = parsed_attrs[:option]).each {|opt| attributes[%(#{opt}-option)] = '' }
|
2584
|
-
end
|
2592
|
+
parsed_attrs[:option].each {|opt| attributes[%(#{opt}-option)] = '' } if parsed_attrs.key? :option
|
2585
2593
|
|
2586
2594
|
parsed_style
|
2587
2595
|
else
|
@@ -331,11 +331,12 @@ class PathResolver
|
|
331
331
|
|
332
332
|
# Public: Securely resolve a system path
|
333
333
|
#
|
334
|
-
#
|
335
|
-
#
|
336
|
-
# that
|
337
|
-
# path
|
338
|
-
#
|
334
|
+
# Resolves the target to an absolute path on the current filesystem. The target is assumed to be
|
335
|
+
# relative to the start path, jail path, or working directory (specified in the constructor), in
|
336
|
+
# that order. If a jail path is specified, the resolved path is forced to descend from the jail
|
337
|
+
# path. If a jail path is not provided, the resolved path may be any location on the system. If
|
338
|
+
# the target is an absolute path, use it as is (unless it breaches the jail path). Expands all
|
339
|
+
# parent and self references in the resolved path.
|
339
340
|
#
|
340
341
|
# target - the String target path
|
341
342
|
# start - the String start path from which to resolve a relative target; falls back to jail, if
|
@@ -347,8 +348,9 @@ class PathResolver
|
|
347
348
|
# automatically recover when an illegal path is encountered
|
348
349
|
# * :target_name is used in messages to refer to the path being resolved
|
349
350
|
#
|
350
|
-
#
|
351
|
-
# if specified. The path is posixified and all parent and self references in the path
|
351
|
+
# Returns an absolute String path relative to the start path, if specified, and confined to the
|
352
|
+
# jail path, if specified. The path is posixified and all parent and self references in the path
|
353
|
+
# are expanded.
|
352
354
|
def system_path target, start = nil, jail = nil, opts = {}
|
353
355
|
if jail
|
354
356
|
raise ::SecurityError, %(Jail is not an absolute path: #{jail}) unless root? jail
|
@@ -362,7 +364,7 @@ class PathResolver
|
|
362
364
|
if jail && !(descends_from? target_path, jail)
|
363
365
|
if opts.fetch :recover, true
|
364
366
|
logger.warn %(#{opts[:target_name] || 'path'} is outside of jail; recovering automatically)
|
365
|
-
target_segments,
|
367
|
+
target_segments, = partition_path target_path
|
366
368
|
jail_segments, jail_root = partition_path jail
|
367
369
|
return join_path jail_segments + target_segments, jail_root
|
368
370
|
else
|
@@ -371,7 +373,7 @@ class PathResolver
|
|
371
373
|
end
|
372
374
|
return target_path
|
373
375
|
else
|
374
|
-
target_segments,
|
376
|
+
target_segments, = partition_path target
|
375
377
|
end
|
376
378
|
else
|
377
379
|
target_segments = []
|
@@ -387,7 +389,7 @@ class PathResolver
|
|
387
389
|
return expand_path start
|
388
390
|
end
|
389
391
|
else
|
390
|
-
target_segments,
|
392
|
+
target_segments, = partition_path start
|
391
393
|
start = jail || @working_dir
|
392
394
|
end
|
393
395
|
elsif start.nil_or_empty?
|
@@ -419,7 +421,7 @@ class PathResolver
|
|
419
421
|
if (resolved_segments = start_segments + target_segments).include? DOT_DOT
|
420
422
|
unresolved_segments, resolved_segments = resolved_segments, []
|
421
423
|
if jail
|
422
|
-
jail_segments,
|
424
|
+
jail_segments, = partition_path jail unless jail_segments
|
423
425
|
warned = false
|
424
426
|
unresolved_segments.each do |segment|
|
425
427
|
if segment == DOT_DOT
|
@@ -450,7 +452,7 @@ class PathResolver
|
|
450
452
|
target_path
|
451
453
|
elsif opts.fetch :recover, true
|
452
454
|
logger.warn %(#{opts[:target_name] || 'path'} is outside of jail; recovering automatically)
|
453
|
-
jail_segments,
|
455
|
+
jail_segments, = partition_path jail unless jail_segments
|
454
456
|
join_path jail_segments + target_segments, jail_root
|
455
457
|
else
|
456
458
|
raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} is outside of jail: #{jail} (disallowed in safe mode))
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -44,11 +44,11 @@ class Reader
|
|
44
44
|
@file = nil
|
45
45
|
@dir = '.'
|
46
46
|
@path = '<stdin>'
|
47
|
-
@lineno = 1
|
47
|
+
@lineno = 1
|
48
48
|
elsif ::String === cursor
|
49
49
|
@file = cursor
|
50
50
|
@dir, @path = ::File.split @file
|
51
|
-
@lineno = 1
|
51
|
+
@lineno = 1
|
52
52
|
else
|
53
53
|
if (@file = cursor.file)
|
54
54
|
@dir = cursor.dir || (::File.dirname @file)
|
@@ -57,7 +57,7 @@ class Reader
|
|
57
57
|
@dir = cursor.dir || '.'
|
58
58
|
@path = cursor.path || '<stdin>'
|
59
59
|
end
|
60
|
-
@lineno = cursor.lineno || 1
|
60
|
+
@lineno = cursor.lineno || 1
|
61
61
|
end
|
62
62
|
@lines = prepare_lines data, opts
|
63
63
|
@source_lines = @lines.drop 0
|
@@ -570,17 +570,18 @@ class Reader
|
|
570
570
|
#
|
571
571
|
# data - A String Array or String of source data to be normalized.
|
572
572
|
# opts - A Hash of options to control how lines are prepared.
|
573
|
-
# :normalize - Enables line normalization, which coerces the encoding to UTF-8 and removes trailing whitespace
|
574
|
-
# (optional,
|
573
|
+
# :normalize - Enables line normalization, which coerces the encoding to UTF-8 and removes trailing whitespace;
|
574
|
+
# :rstrip removes all trailing whitespace; :chomp removes trailing newline only (optional, not set).
|
575
575
|
#
|
576
576
|
# Returns A String Array of source lines. If the source data is an Array, this method returns a copy.
|
577
577
|
def prepare_lines data, opts = {}
|
578
|
-
if opts[:normalize]
|
579
|
-
|
578
|
+
if (normalize = opts[:normalize])
|
579
|
+
trim_end = normalize == :chomp ? false : true
|
580
|
+
::Array === data ? (Helpers.prepare_source_array data, trim_end) : (Helpers.prepare_source_string data, trim_end)
|
580
581
|
elsif ::Array === data
|
581
582
|
data.drop 0
|
582
583
|
elsif data
|
583
|
-
data.split LF, -1
|
584
|
+
data.chomp.split LF, -1
|
584
585
|
else
|
585
586
|
[]
|
586
587
|
end
|
@@ -689,6 +690,7 @@ class PreprocessorReader < Reader
|
|
689
690
|
@path = (path ||= ::File.basename file)
|
690
691
|
# only process lines in AsciiDoc files
|
691
692
|
if (@process_lines = file.end_with?(*ASCIIDOC_EXTENSIONS.keys))
|
693
|
+
# NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
|
692
694
|
@includes[path.slice 0, (path.rindex '.')] = attributes['partial-option'] ? nil : true
|
693
695
|
end
|
694
696
|
else
|
@@ -696,6 +698,7 @@ class PreprocessorReader < Reader
|
|
696
698
|
# we don't know what file type we have, so assume AsciiDoc
|
697
699
|
@process_lines = true
|
698
700
|
if (@path = path)
|
701
|
+
# NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
|
699
702
|
@includes[Helpers.rootname path] = attributes['partial-option'] ? nil : true
|
700
703
|
else
|
701
704
|
@path = '<stdin>'
|
@@ -717,7 +720,7 @@ class PreprocessorReader < Reader
|
|
717
720
|
end
|
718
721
|
|
719
722
|
# effectively fill the buffer
|
720
|
-
if (@lines = prepare_lines data, normalize:
|
723
|
+
if (@lines = prepare_lines data, normalize: @process_lines || :chomp, condense: false, indent: attributes['indent']).empty?
|
721
724
|
pop_include
|
722
725
|
else
|
723
726
|
# FIXME we eventually want to handle leveloffset without affecting the lines
|
@@ -808,7 +811,6 @@ class PreprocessorReader < Reader
|
|
808
811
|
end
|
809
812
|
|
810
813
|
if opts.fetch :condense, true
|
811
|
-
result.shift && @lineno += 1 while (first = result[0]) && first.empty?
|
812
814
|
result.pop while (last = result[-1]) && last.empty?
|
813
815
|
end
|
814
816
|
|
@@ -1060,7 +1062,13 @@ class PreprocessorReader < Reader
|
|
1060
1062
|
return inc_path
|
1061
1063
|
end
|
1062
1064
|
|
1065
|
+
if (enc = parsed_attrs['encoding']) && (::Encoding.find enc rescue nil)
|
1066
|
+
(read_mode_params = read_mode.split ':')[1] = enc
|
1067
|
+
read_mode = read_mode_params.join ':'
|
1068
|
+
end unless RUBY_ENGINE_OPAL
|
1069
|
+
|
1063
1070
|
inc_linenos = inc_tags = nil
|
1071
|
+
# NOTE attrlist is nil if missing from include directive
|
1064
1072
|
if attrlist
|
1065
1073
|
if parsed_attrs.key? 'lines'
|
1066
1074
|
inc_linenos = []
|
@@ -1131,9 +1139,10 @@ class PreprocessorReader < Reader
|
|
1131
1139
|
else
|
1132
1140
|
select = base_select = wildcard = inc_tags.delete '**'
|
1133
1141
|
end
|
1142
|
+
elsif inc_tags.key? '*'
|
1143
|
+
select = base_select = !(wildcard = inc_tags.delete '*')
|
1134
1144
|
else
|
1135
|
-
select = base_select =
|
1136
|
-
wildcard = inc_tags.delete '*'
|
1145
|
+
select = base_select = false
|
1137
1146
|
end
|
1138
1147
|
begin
|
1139
1148
|
reader.call inc_path, read_mode do |f|
|
@@ -1148,7 +1157,7 @@ class PreprocessorReader < Reader
|
|
1148
1157
|
active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
|
1149
1158
|
elsif inc_tags.key? this_tag
|
1150
1159
|
include_cursor = create_include_cursor inc_path, expanded_target, inc_lineno
|
1151
|
-
if (idx = tag_stack.rindex {|key
|
1160
|
+
if (idx = tag_stack.rindex {|key,| key == this_tag })
|
1152
1161
|
idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
|
1153
1162
|
logger.warn message_with_context %(mismatched end tag (expected '#{active_tag}' but found '#{this_tag}') at line #{inc_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: include_cursor
|
1154
1163
|
else
|
data/lib/asciidoctor/rx.rb
CHANGED
@@ -269,7 +269,7 @@ module Asciidoctor
|
|
269
269
|
#
|
270
270
|
# NOTE we only have to check as far as the blank character because we know it means non-whitespace follows.
|
271
271
|
# IMPORTANT if this regexp does not agree with the regexp for each list type, the parser will hang.
|
272
|
-
AnyListRx = %r(^(?:[ \t]*(?:-|\*\**|\.\.*|\u2022|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|(?!//[^/])[ \t]*[^ \t]#{CC_ANY}*?(?::::{0,2}|;;)(?:$|[ \t])
|
272
|
+
AnyListRx = %r(^(?:[ \t]*(?:-|\*\**|\.\.*|\u2022|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|(?!//[^/])[ \t]*[^ \t]#{CC_ANY}*?(?::::{0,2}|;;)(?:$|[ \t])|<(?:\d+|\.)>[ \t]))
|
273
273
|
|
274
274
|
# Matches an unordered list item (one level for hyphens, up to 5 levels for asterisks).
|
275
275
|
#
|
@@ -469,7 +469,7 @@ module Asciidoctor
|
|
469
469
|
# footnoteref:[id,text] (legacy)
|
470
470
|
# footnoteref:[id] (legacy)
|
471
471
|
#
|
472
|
-
InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\]/m
|
472
|
+
InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!<\/a>)/m
|
473
473
|
|
474
474
|
# Matches an image or icon inline macro.
|
475
475
|
#
|
@@ -514,9 +514,10 @@ module Asciidoctor
|
|
514
514
|
# https://github.com[GitHub]
|
515
515
|
# <https://github.com>
|
516
516
|
# link:https://github.com[]
|
517
|
+
# "https://github.com[]"
|
517
518
|
#
|
518
519
|
# FIXME revisit! the main issue is we need different rules for implicit vs explicit
|
519
|
-
InlineLinkRx = %r((^|link:|#{CG_BLANK}|<|[>\(\)\[\];])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
|
520
|
+
InlineLinkRx = %r((^|link:|#{CG_BLANK}|<|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
|
520
521
|
|
521
522
|
# Match a link or e-mail inline macro.
|
522
523
|
#
|
@@ -551,7 +552,7 @@ module Asciidoctor
|
|
551
552
|
# menu:View[Page Style > No Style]
|
552
553
|
# menu:View[Page Style, No Style]
|
553
554
|
#
|
554
|
-
InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))
|
555
|
+
InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))\]/m
|
555
556
|
|
556
557
|
# Matches an implicit menu inline macro.
|
557
558
|
#
|
@@ -591,7 +592,7 @@ module Asciidoctor
|
|
591
592
|
# $$text$$
|
592
593
|
# pass:quotes[text]
|
593
594
|
#
|
594
|
-
# NOTE we have to support an empty pass:[] for compatibility with AsciiDoc
|
595
|
+
# NOTE we have to support an empty pass:[] for compatibility with AsciiDoc.py
|
595
596
|
InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
|
596
597
|
|
597
598
|
# Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
|
@@ -610,7 +611,7 @@ module Asciidoctor
|
|
610
611
|
# Matches a trailing + preceded by at least one space character,
|
611
612
|
# which forces a hard line break (<br> tag in HTML output).
|
612
613
|
#
|
613
|
-
# NOTE AsciiDoc
|
614
|
+
# NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not
|
614
615
|
#
|
615
616
|
# Examples
|
616
617
|
#
|
@@ -331,8 +331,8 @@ module Substitutors
|
|
331
331
|
target ||= ext_config[:format] == :short ? content : target
|
332
332
|
end
|
333
333
|
if (Inline === (replacement = extension.process_method[self, target, attributes]))
|
334
|
-
if (inline_subs = replacement.attributes.delete 'subs')
|
335
|
-
replacement.text = apply_subs replacement.text,
|
334
|
+
if (inline_subs = replacement.attributes.delete 'subs') && (inline_subs = expand_subs inline_subs, 'custom inline macro')
|
335
|
+
replacement.text = apply_subs replacement.text, inline_subs
|
336
336
|
end
|
337
337
|
replacement.convert
|
338
338
|
elsif replacement
|
@@ -542,20 +542,25 @@ module Substitutors
|
|
542
542
|
end
|
543
543
|
|
544
544
|
prefix, suffix = $1, ''
|
545
|
-
# NOTE if $4 is set,
|
545
|
+
# NOTE if $4 is set, we're looking at a formal macro (e.g., https://example.org[])
|
546
546
|
if $4
|
547
547
|
prefix = '' if prefix == 'link:'
|
548
548
|
text = $4
|
549
549
|
else
|
550
|
-
# invalid macro syntax (link: prefix w/o trailing square brackets)
|
551
|
-
# FIXME we probably shouldn't even get here
|
552
|
-
|
550
|
+
# invalid macro syntax (link: prefix w/o trailing square brackets or enclosed in double quotes)
|
551
|
+
# FIXME we probably shouldn't even get here when the link: prefix is present; the regex is doing too much
|
552
|
+
case prefix
|
553
|
+
when 'link:', ?", ?'
|
554
|
+
next $&
|
555
|
+
end
|
553
556
|
text = ''
|
554
557
|
case $3
|
555
|
-
when ')'
|
556
|
-
# move trailing ) out of URL
|
558
|
+
when ')', '?', '!'
|
557
559
|
target = target.chop
|
558
|
-
suffix = ')'
|
560
|
+
if (suffix = $3) == ')' && (target.end_with? '.', '?', '!')
|
561
|
+
suffix = target[-1] + suffix
|
562
|
+
target = target.chop
|
563
|
+
end
|
559
564
|
# NOTE handle case when modified target is a URI scheme (e.g., http://)
|
560
565
|
next $& if target.end_with? '://'
|
561
566
|
when ';'
|
@@ -591,7 +596,8 @@ module Substitutors
|
|
591
596
|
unless text.empty?
|
592
597
|
text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
|
593
598
|
if !doc.compat_mode && (text.include? '=')
|
594
|
-
|
599
|
+
# NOTE if an equals sign (=) is present, extract attributes from text
|
600
|
+
text, attrs = extract_attributes_from_text text, ''
|
595
601
|
link_opts[:id] = attrs['id']
|
596
602
|
end
|
597
603
|
|
@@ -637,7 +643,8 @@ module Substitutors
|
|
637
643
|
text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
|
638
644
|
if mailto
|
639
645
|
if !doc.compat_mode && (text.include? ',')
|
640
|
-
|
646
|
+
# NOTE if a comma (,) is present, extract attributes from text
|
647
|
+
text, attrs = extract_attributes_from_text text, ''
|
641
648
|
link_opts[:id] = attrs['id']
|
642
649
|
if attrs.key? 2
|
643
650
|
if attrs.key? 3
|
@@ -648,7 +655,8 @@ module Substitutors
|
|
648
655
|
end
|
649
656
|
end
|
650
657
|
elsif !doc.compat_mode && (text.include? '=')
|
651
|
-
|
658
|
+
# NOTE if an equals sign (=) is present, extract attributes from text
|
659
|
+
text, attrs = extract_attributes_from_text text, ''
|
652
660
|
link_opts[:id] = attrs['id']
|
653
661
|
end
|
654
662
|
|
@@ -739,8 +747,8 @@ module Substitutors
|
|
739
747
|
refid = $2
|
740
748
|
if (text = $3)
|
741
749
|
text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
|
742
|
-
# NOTE if an
|
743
|
-
text =
|
750
|
+
# NOTE if an equals sign (=) is present, extract attributes from text
|
751
|
+
text, attrs = extract_attributes_from_text text if !doc.compat_mode && (text.include? '=')
|
744
752
|
end
|
745
753
|
end
|
746
754
|
|
@@ -794,7 +802,7 @@ module Substitutors
|
|
794
802
|
refid, path, target = nil, nil, '#'
|
795
803
|
end
|
796
804
|
else
|
797
|
-
refid, path = path, %(#{doc.attributes['relfileprefix']}#{path}#{src2src ? (doc.attributes.fetch 'relfilesuffix', doc.outfilesuffix) : ''})
|
805
|
+
refid, path = path, %(#{doc.attributes['relfileprefix'] || ''}#{path}#{src2src ? (doc.attributes.fetch 'relfilesuffix', doc.outfilesuffix) : ''})
|
798
806
|
if fragment
|
799
807
|
refid, target = %(#{refid}##{fragment}), %(#{path}##{fragment})
|
800
808
|
else
|
@@ -804,7 +812,7 @@ module Substitutors
|
|
804
812
|
# handles: id (in compat mode or when natural xrefs are disabled)
|
805
813
|
elsif doc.compat_mode || !Compliance.natural_xrefs
|
806
814
|
refid, target = fragment, %(##{fragment})
|
807
|
-
logger.info %(possible invalid reference: #{refid}) if logger.info? && doc.catalog[:refs][refid]
|
815
|
+
logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
|
808
816
|
# handles: id
|
809
817
|
elsif doc.catalog[:refs][fragment]
|
810
818
|
refid, target = fragment, %(##{fragment})
|
@@ -843,19 +851,17 @@ module Substitutors
|
|
843
851
|
end
|
844
852
|
|
845
853
|
if id
|
846
|
-
if
|
854
|
+
if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
|
855
|
+
index, text = footnote.index, footnote.text
|
856
|
+
type, target, id = :xref, id, nil
|
857
|
+
elsif text
|
847
858
|
text = restore_passthroughs(normalize_text text, true, true)
|
848
859
|
index = doc.counter('footnote-number')
|
849
860
|
doc.register(:footnotes, Document::Footnote.new(index, id, text))
|
850
861
|
type, target = :ref, nil
|
851
862
|
else
|
852
|
-
|
853
|
-
|
854
|
-
else
|
855
|
-
logger.warn %(invalid footnote reference: #{id})
|
856
|
-
index, text = nil, id
|
857
|
-
end
|
858
|
-
type, target, id = :xref, id, nil
|
863
|
+
logger.warn %(invalid footnote reference: #{id})
|
864
|
+
type, target, text, id = :xref, id, id, nil
|
859
865
|
end
|
860
866
|
elsif text
|
861
867
|
text = restore_passthroughs(normalize_text text, true, true)
|
@@ -917,7 +923,7 @@ module Substitutors
|
|
917
923
|
# use sub since it might be behind a line comment
|
918
924
|
$&.sub RS, ''
|
919
925
|
else
|
920
|
-
Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 }).convert
|
926
|
+
Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 || ($3 == '--' ? ['<!--', '-->'] : nil) }).convert
|
921
927
|
end
|
922
928
|
end
|
923
929
|
end
|
@@ -945,7 +951,7 @@ module Substitutors
|
|
945
951
|
if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil)
|
946
952
|
start_line_number = 1 if (start_line_number = (attr 'start', 1).to_i) < 1
|
947
953
|
end
|
948
|
-
highlight_lines = resolve_lines_to_highlight source, (attr 'highlight') if attr? 'highlight'
|
954
|
+
highlight_lines = resolve_lines_to_highlight source, (attr 'highlight'), start_line_number if attr? 'highlight'
|
949
955
|
|
950
956
|
highlighted, source_offset = syntax_hl.highlight self, source, (attr 'language'),
|
951
957
|
callouts: callout_marks,
|
@@ -968,9 +974,10 @@ module Substitutors
|
|
968
974
|
#
|
969
975
|
# source - The String source.
|
970
976
|
# spec - The lines specifier (e.g., "1-5, !2, 10" or "1..5;!2;10")
|
977
|
+
# start - The line number of the first line (optional, default: false)
|
971
978
|
#
|
972
979
|
# Returns an [Array] of unique, sorted line numbers.
|
973
|
-
def resolve_lines_to_highlight source, spec
|
980
|
+
def resolve_lines_to_highlight source, spec, start = nil
|
974
981
|
lines = []
|
975
982
|
spec = spec.delete ' ' if spec.include? ' '
|
976
983
|
((spec.include? ',') ? (spec.split ',') : (spec.split ';')).map do |entry|
|
@@ -981,21 +988,22 @@ module Substitutors
|
|
981
988
|
if (delim = (entry.include? '..') ? '..' : ((entry.include? '-') ? '-' : nil))
|
982
989
|
from, delim, to = entry.partition delim
|
983
990
|
to = (source.count LF) + 1 if to.empty? || (to = to.to_i) < 0
|
984
|
-
line_nums = (from.to_i..to).to_a
|
985
991
|
if negate
|
986
|
-
lines -=
|
992
|
+
lines -= (from.to_i..to).to_a
|
987
993
|
else
|
988
|
-
lines.
|
989
|
-
end
|
990
|
-
else
|
991
|
-
if negate
|
992
|
-
lines.delete entry.to_i
|
993
|
-
else
|
994
|
-
lines << entry.to_i
|
994
|
+
lines |= (from.to_i..to).to_a
|
995
995
|
end
|
996
|
+
elsif negate
|
997
|
+
lines.delete entry.to_i
|
998
|
+
elsif !lines.include?(line = entry.to_i)
|
999
|
+
lines << line
|
996
1000
|
end
|
997
1001
|
end
|
998
|
-
lines.
|
1002
|
+
# If the start attribute is defined, then the lines to highlight specified by the provided spec should be relative to the start value.
|
1003
|
+
unless (shift = start ? start - 1 : 0) == 0
|
1004
|
+
lines = lines.map {|it| it - shift }
|
1005
|
+
end
|
1006
|
+
lines.sort
|
999
1007
|
end
|
1000
1008
|
|
1001
1009
|
# Public: Extract the passthrough text from the document for reinsertion after processing.
|
@@ -1112,7 +1120,7 @@ module Substitutors
|
|
1112
1120
|
end
|
1113
1121
|
subs = $2
|
1114
1122
|
content = normalize_text $3, nil, true
|
1115
|
-
# NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc
|
1123
|
+
# NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc.py
|
1116
1124
|
content = content.slice 1, content.length - 2 if type == :latexmath && (content.start_with? '$') && (content.end_with? '$')
|
1117
1125
|
subs = subs ? (resolve_pass_subs subs) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
|
1118
1126
|
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: type }
|
@@ -1226,17 +1234,16 @@ module Substitutors
|
|
1226
1234
|
resolve_subs subs, :inline, nil, 'passthrough macro'
|
1227
1235
|
end
|
1228
1236
|
|
1229
|
-
# Public: Expand all groups in the subs list and return. If no subs are
|
1237
|
+
# Public: Expand all groups in the subs list and return. If no subs are resolved, return nil.
|
1230
1238
|
#
|
1231
|
-
# subs - The substitutions to expand; can be a Symbol, Symbol Array or
|
1239
|
+
# subs - The substitutions to expand; can be a Symbol, Symbol Array, or String
|
1240
|
+
# subject - The String to use in log messages to communicate the subject for which subs are being resolved (default: nil)
|
1232
1241
|
#
|
1233
1242
|
# Returns a Symbol Array of substitutions to pass to apply_subs or nil if no substitutions were resolved.
|
1234
|
-
def expand_subs subs
|
1243
|
+
def expand_subs subs, subject = nil
|
1235
1244
|
if ::Symbol === subs
|
1236
|
-
|
1237
|
-
|
1238
|
-
end
|
1239
|
-
else
|
1245
|
+
subs == :none ? nil : SUB_GROUPS[subs] || [subs]
|
1246
|
+
elsif ::Array === subs
|
1240
1247
|
expanded_subs = []
|
1241
1248
|
subs.each do |key|
|
1242
1249
|
unless key == :none
|
@@ -1247,8 +1254,9 @@ module Substitutors
|
|
1247
1254
|
end
|
1248
1255
|
end
|
1249
1256
|
end
|
1250
|
-
|
1251
1257
|
expanded_subs.empty? ? nil : expanded_subs
|
1258
|
+
else
|
1259
|
+
resolve_subs subs, :inline, nil, subject
|
1252
1260
|
end
|
1253
1261
|
end
|
1254
1262
|
|
@@ -1270,7 +1278,7 @@ module Substitutors
|
|
1270
1278
|
# NOTE :literal with listparagraph-option gets folded into text of list item later
|
1271
1279
|
default_subs = @context == :verse ? NORMAL_SUBS : VERBATIM_SUBS
|
1272
1280
|
when :raw
|
1273
|
-
# TODO make pass subs a compliance setting; AsciiDoc
|
1281
|
+
# TODO make pass subs a compliance setting; AsciiDoc.py performs :attributes and :macros on a pass block
|
1274
1282
|
default_subs = @context == :stem ? BASIC_SUBS : NO_SUBS
|
1275
1283
|
else
|
1276
1284
|
return @subs
|
@@ -1321,10 +1329,23 @@ module Substitutors
|
|
1321
1329
|
|
1322
1330
|
private
|
1323
1331
|
|
1332
|
+
# This method is used in cases when the attrlist can be mixed with the text of a macro.
|
1333
|
+
# If no attributes are detected aside from the first positional attribute, and the first positional
|
1334
|
+
# attribute matches the attrlist, then the original text is returned.
|
1335
|
+
def extract_attributes_from_text text, default_text = nil
|
1336
|
+
attrlist = (text.include? LF) ? (text.tr LF, ' ') : text
|
1337
|
+
if (resolved_text = (attrs = (AttributeList.new attrlist, self).parse)[1])
|
1338
|
+
# NOTE if resolved text remains unchanged, clear attributes and return unparsed text
|
1339
|
+
resolved_text == attrlist ? [text, attrs.clear] : [resolved_text, attrs]
|
1340
|
+
else
|
1341
|
+
[default_text, attrs]
|
1342
|
+
end
|
1343
|
+
end
|
1344
|
+
|
1324
1345
|
# Internal: Extract the callout numbers from the source to prepare it for syntax highlighting.
|
1325
1346
|
def extract_callouts source
|
1326
1347
|
callout_marks = {}
|
1327
|
-
lineno = 0
|
1348
|
+
autonum = lineno = 0
|
1328
1349
|
last_lineno = nil
|
1329
1350
|
callout_rx = (attr? 'line-comment') ? CalloutExtractRxMap[attr 'line-comment'] : CalloutExtractRx
|
1330
1351
|
# extract callout marks, indexed by line number
|
@@ -1336,7 +1357,7 @@ module Substitutors
|
|
1336
1357
|
# use sub since it might be behind a line comment
|
1337
1358
|
$&.sub RS, ''
|
1338
1359
|
else
|
1339
|
-
(callout_marks[lineno] ||= []) << [$1, $4]
|
1360
|
+
(callout_marks[lineno] ||= []) << [$1 || ($3 == '--' ? ['<!--', '-->'] : nil), $4 == '.' ? (autonum += 1).to_s : $4]
|
1340
1361
|
last_lineno = lineno
|
1341
1362
|
''
|
1342
1363
|
end
|
@@ -1358,15 +1379,15 @@ module Substitutors
|
|
1358
1379
|
else
|
1359
1380
|
preamble = ''
|
1360
1381
|
end
|
1361
|
-
|
1382
|
+
lineno = 0
|
1362
1383
|
preamble + ((source.split LF, -1).map do |line|
|
1363
1384
|
if (conums = callout_marks.delete lineno += 1)
|
1364
1385
|
if conums.size == 1
|
1365
|
-
guard,
|
1366
|
-
%(#{line}#{Inline.new(self, :callout,
|
1386
|
+
guard, numeral = conums[0]
|
1387
|
+
%(#{line}#{Inline.new(self, :callout, numeral, id: @document.callouts.read_next_id, attributes: { 'guard' => guard }).convert})
|
1367
1388
|
else
|
1368
|
-
%(#{line}#{conums.map do |guard_it,
|
1369
|
-
Inline.new(self, :callout,
|
1389
|
+
%(#{line}#{conums.map do |guard_it, numeral_it|
|
1390
|
+
Inline.new(self, :callout, numeral_it, id: @document.callouts.read_next_id, attributes: { 'guard' => guard_it }).convert
|
1370
1391
|
end.join ' '})
|
1371
1392
|
end
|
1372
1393
|
else
|
@@ -1453,13 +1474,13 @@ module Substitutors
|
|
1453
1474
|
#
|
1454
1475
|
# Returns a Hash of attributes (role and id only)
|
1455
1476
|
def parse_quoted_text_attributes str
|
1456
|
-
return {} if (str = str.rstrip).empty?
|
1457
1477
|
# NOTE attributes are typically resolved after quoted text, so substitute eagerly
|
1458
1478
|
str = sub_attributes str if str.include? ATTR_REF_HEAD
|
1459
1479
|
# for compliance, only consider first positional attribute (very unlikely)
|
1460
1480
|
str = str.slice 0, (str.index ',') if str.include? ','
|
1461
|
-
|
1462
|
-
|
1481
|
+
if (str = str.strip).empty?
|
1482
|
+
{}
|
1483
|
+
elsif (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
|
1463
1484
|
segments = str.split '#', 2
|
1464
1485
|
|
1465
1486
|
if segments.size > 1
|