asciidoctor 2.0.6 → 2.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +159 -6
  3. data/LICENSE +2 -1
  4. data/README-de.adoc +5 -5
  5. data/README-fr.adoc +4 -4
  6. data/README-jp.adoc +248 -183
  7. data/README-zh_CN.adoc +6 -6
  8. data/README.adoc +17 -11
  9. data/asciidoctor.gemspec +8 -8
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-bg.adoc +4 -3
  12. data/data/locale/attributes-ca.adoc +6 -5
  13. data/data/locale/attributes-cs.adoc +4 -3
  14. data/data/locale/attributes-da.adoc +6 -5
  15. data/data/locale/attributes-de.adoc +4 -4
  16. data/data/locale/attributes-en.adoc +4 -4
  17. data/data/locale/attributes-es.adoc +6 -5
  18. data/data/locale/attributes-fa.adoc +4 -3
  19. data/data/locale/attributes-fi.adoc +4 -3
  20. data/data/locale/attributes-fr.adoc +6 -5
  21. data/data/locale/attributes-hu.adoc +4 -3
  22. data/data/locale/attributes-id.adoc +4 -3
  23. data/data/locale/attributes-it.adoc +4 -3
  24. data/data/locale/attributes-ja.adoc +4 -3
  25. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  26. data/data/locale/attributes-nb.adoc +4 -3
  27. data/data/locale/attributes-nl.adoc +4 -3
  28. data/data/locale/attributes-nn.adoc +4 -3
  29. data/data/locale/attributes-pl.adoc +8 -7
  30. data/data/locale/attributes-pt.adoc +6 -5
  31. data/data/locale/attributes-pt_BR.adoc +6 -5
  32. data/data/locale/attributes-ro.adoc +4 -3
  33. data/data/locale/attributes-ru.adoc +6 -5
  34. data/data/locale/attributes-sr.adoc +4 -4
  35. data/data/locale/attributes-sr_Latn.adoc +4 -4
  36. data/data/locale/attributes-sv.adoc +4 -4
  37. data/data/locale/attributes-tr.adoc +4 -3
  38. data/data/locale/attributes-uk.adoc +6 -5
  39. data/data/locale/attributes-zh_CN.adoc +4 -3
  40. data/data/locale/attributes-zh_TW.adoc +4 -3
  41. data/data/stylesheets/asciidoctor-default.css +29 -26
  42. data/lib/asciidoctor.rb +94 -1098
  43. data/lib/asciidoctor/abstract_block.rb +19 -11
  44. data/lib/asciidoctor/abstract_node.rb +21 -15
  45. data/lib/asciidoctor/attribute_list.rb +59 -67
  46. data/lib/asciidoctor/cli/invoker.rb +2 -0
  47. data/lib/asciidoctor/cli/options.rb +8 -8
  48. data/lib/asciidoctor/convert.rb +198 -0
  49. data/lib/asciidoctor/converter.rb +14 -13
  50. data/lib/asciidoctor/converter/docbook5.rb +9 -25
  51. data/lib/asciidoctor/converter/html5.rb +65 -42
  52. data/lib/asciidoctor/converter/manpage.rb +13 -12
  53. data/lib/asciidoctor/converter/template.rb +6 -3
  54. data/lib/asciidoctor/document.rb +40 -48
  55. data/lib/asciidoctor/extensions.rb +3 -3
  56. data/lib/asciidoctor/helpers.rb +38 -39
  57. data/lib/asciidoctor/inline.rb +1 -1
  58. data/lib/asciidoctor/load.rb +117 -0
  59. data/lib/asciidoctor/parser.rb +29 -25
  60. data/lib/asciidoctor/path_resolver.rb +35 -25
  61. data/lib/asciidoctor/reader.rb +14 -7
  62. data/lib/asciidoctor/rx.rb +722 -0
  63. data/lib/asciidoctor/substitutors.rb +62 -40
  64. data/lib/asciidoctor/syntax_highlighter.rb +22 -8
  65. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  66. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  67. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  68. data/lib/asciidoctor/syntax_highlighter/pygments.rb +2 -3
  69. data/lib/asciidoctor/syntax_highlighter/rouge.rb +18 -11
  70. data/lib/asciidoctor/table.rb +49 -20
  71. data/lib/asciidoctor/version.rb +1 -1
  72. data/man/asciidoctor.1 +17 -17
  73. data/man/asciidoctor.adoc +15 -14
  74. metadata +12 -9
@@ -433,8 +433,10 @@ class Parser
433
433
  # is treated like an untitled section
434
434
  elsif preamble # implies parent == document
435
435
  if preamble.blocks?
436
+ if book || document.blocks[1] || !Compliance.unwrap_standalone_preamble
437
+ preamble.source_location = preamble.blocks[0].source_location if document.sourcemap
436
438
  # unwrap standalone preamble (i.e., document has no sections) except for books, if permissible
437
- unless book || document.blocks[1] || !Compliance.unwrap_standalone_preamble
439
+ else
438
440
  document.blocks.shift
439
441
  while (child_block = preamble.blocks.shift)
440
442
  document << child_block
@@ -596,7 +598,8 @@ class Parser
596
598
  end
597
599
  end
598
600
  if blk_ctx == :image
599
- document.register :images, [target, (attributes['imagesdir'] = doc_attrs['imagesdir'])]
601
+ document.register :images, target
602
+ attributes['imagesdir'] = doc_attrs['imagesdir']
600
603
  # NOTE style is the value of the first positional attribute in the block attribute line
601
604
  attributes['alt'] ||= style || (attributes['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
602
605
  unless (scaledwidth = attributes.delete 'scaledwidth').nil_or_empty?
@@ -857,6 +860,7 @@ class Parser
857
860
  when :literal
858
861
  block = build_block(block_context, :verbatim, terminator, parent, reader, attributes)
859
862
  when :example
863
+ attributes['caption'] = '' if attributes['collapsible-option']
860
864
  block = build_block(block_context, :compound, terminator, parent, reader, attributes)
861
865
  when :quote, :verse
862
866
  AttributeList.rekey(attributes, [nil, 'attribution', 'citetitle'])
@@ -898,7 +902,7 @@ class Parser
898
902
  # FIXME title and caption should be assigned when block is constructed (though we need to handle all cases)
899
903
  if attributes['title']
900
904
  block.title = block_title = attributes.delete 'title'
901
- if (caption_attr_name = CAPTION_ATTR_NAMES[block.context]) && document.attributes[caption_attr_name]
905
+ if (caption_attr_name = CAPTION_ATTRIBUTE_NAMES[block.context]) && document.attributes[caption_attr_name]
902
906
  block.assign_caption (attributes.delete 'caption')
903
907
  end
904
908
  end
@@ -1147,14 +1151,16 @@ class Parser
1147
1151
  def self.catalog_inline_anchors text, block, document, reader
1148
1152
  text.scan InlineAnchorScanRx do
1149
1153
  if (id = $1)
1150
- if (reftext = $2)
1151
- next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1152
- end
1154
+ next if (reftext = $2) && (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1153
1155
  else
1154
1156
  id = $3
1155
1157
  if (reftext = $4)
1156
- reftext = reftext.gsub '\]', ']' if reftext.include? ']'
1157
- next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1158
+ if reftext.include? ']'
1159
+ reftext = reftext.gsub '\]', ']'
1160
+ reftext = document.sub_attributes reftext if reftext.include? ATTR_REF_HEAD
1161
+ elsif (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1162
+ next
1163
+ end
1158
1164
  end
1159
1165
  end
1160
1166
  unless document.register :refs, [id, (Inline.new block, :anchor, reftext, type: :ref, id: id)]
@@ -1532,7 +1538,7 @@ class Parser
1532
1538
 
1533
1539
  reader.unshift_line this_line if this_line
1534
1540
 
1535
- buffer.delete_at detached_continuation if detached_continuation
1541
+ buffer[detached_continuation] = '' if detached_continuation
1536
1542
 
1537
1543
  until buffer.empty?
1538
1544
  # strip trailing blank lines to prevent empty blocks
@@ -2118,6 +2124,8 @@ class Parser
2118
2124
  name = 'sectnums'
2119
2125
  elsif name == 'hardbreaks'
2120
2126
  name = 'hardbreaks-option'
2127
+ elsif name == 'showtitle'
2128
+ store_attribute 'notitle', (value ? nil : ''), doc, attrs
2121
2129
  end
2122
2130
 
2123
2131
  if doc
@@ -2272,9 +2280,14 @@ class Parser
2272
2280
  end
2273
2281
 
2274
2282
  skipped = table_reader.skip_blank_lines || 0
2283
+ if attributes['header-option']
2284
+ table.has_header_option = true
2285
+ elsif skipped == 0 && !attributes['noheader-option']
2286
+ # NOTE: assume table has header until we know otherwise; if it doesn't (nil), cells in first row get reprocessed
2287
+ table.has_header_option = implicit_header = true
2288
+ end
2275
2289
  parser_ctx = Table::ParserContext.new table_reader, table, attributes
2276
2290
  format, loop_idx, implicit_header_boundary = parser_ctx.format, -1, nil
2277
- implicit_header = true unless skipped > 0 || attributes['header-option'] || attributes['noheader-option']
2278
2291
 
2279
2292
  while (line = table_reader.read_line)
2280
2293
  if (beyond_first = (loop_idx += 1) > 0) && line.empty?
@@ -2294,7 +2307,7 @@ class Parser
2294
2307
  implicit_header_boundary = nil if implicit_header_boundary
2295
2308
  # otherwise, the cell continues from previous line
2296
2309
  elsif implicit_header_boundary && implicit_header_boundary == loop_idx
2297
- implicit_header, implicit_header_boundary = false, nil
2310
+ table.has_header_option = implicit_header = implicit_header_boundary = nil
2298
2311
  end
2299
2312
  end
2300
2313
  end
@@ -2306,7 +2319,7 @@ class Parser
2306
2319
  if table_reader.has_more_lines? && table_reader.peek_line.empty?
2307
2320
  implicit_header_boundary = 1
2308
2321
  else
2309
- implicit_header = false
2322
+ table.has_header_option = implicit_header = nil
2310
2323
  end
2311
2324
  end
2312
2325
  end
@@ -2357,7 +2370,7 @@ class Parser
2357
2370
  case format
2358
2371
  when 'csv'
2359
2372
  if parser_ctx.buffer_has_unclosed_quotes?
2360
- implicit_header, implicit_header_boundary = false, nil if implicit_header_boundary && loop_idx == 0
2373
+ table.has_header_option = implicit_header = implicit_header_boundary = nil if implicit_header_boundary && loop_idx == 0
2361
2374
  parser_ctx.keep_cell_open
2362
2375
  else
2363
2376
  parser_ctx.close_cell true
@@ -2379,15 +2392,8 @@ class Parser
2379
2392
  end
2380
2393
  end
2381
2394
 
2382
- unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
2383
- table.assign_column_widths
2384
- end
2385
-
2386
- if implicit_header
2387
- table.has_header_option = true
2388
- attributes['header-option'] = ''
2389
- end
2390
-
2395
+ table.assign_column_widths unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
2396
+ attributes['header-option'] = '' if implicit_header
2391
2397
  table.partition_header_footer attributes
2392
2398
 
2393
2399
  table
@@ -2578,9 +2584,7 @@ class Parser
2578
2584
  attributes['role'] = (existing_role = attributes['role']).nil_or_empty? ? (parsed_attrs[:role].join ' ') : %(#{existing_role} #{parsed_attrs[:role].join ' '})
2579
2585
  end
2580
2586
 
2581
- if parsed_attrs.key? :option
2582
- (opts = parsed_attrs[:option]).each {|opt| attributes[%(#{opt}-option)] = '' }
2583
- end
2587
+ parsed_attrs[:option].each {|opt| attributes[%(#{opt}-option)] = '' } if parsed_attrs.key? :option
2584
2588
 
2585
2589
  parsed_style
2586
2590
  else
@@ -331,11 +331,12 @@ class PathResolver
331
331
 
332
332
  # Public: Securely resolve a system path
333
333
  #
334
- # Resolve a system path from the target relative to the start path, jail path, or working
335
- # directory (specified in the constructor), in that order. If a jail path is specified, enforce
336
- # that the resolved path descends from the jail path. If a jail path is not provided, the resolved
337
- # path may be any location on the system. If the resolved path is absolute, use it as is (unless
338
- # it breaches the jail path). Expand all parent and self references in the resolved path.
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
- # returns a String path relative to the start path, if specified, and confined to the jail path,
351
- # if specified. The path is posixified and all parent and self references in the path are expanded.
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, _ = partition_path target_path
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, _ = partition_path target
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, _ = partition_path start
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, _ = partition_path jail unless 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, _ = partition_path jail unless 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))
@@ -476,25 +478,15 @@ class PathResolver
476
478
  def web_path target, start = nil
477
479
  target = posixify target
478
480
  start = posixify start
479
- uri_prefix = nil
480
481
 
481
482
  unless start.nil_or_empty? || (web_root? target)
482
- target = (start.end_with? SLASH) ? %(#{start}#{target}) : %(#{start}#{SLASH}#{target})
483
- if (uri_prefix = Helpers.uri_prefix target)
484
- target = target[uri_prefix.length..-1]
485
- end
483
+ target, uri_prefix = extract_uri_prefix %(#{start}#{(start.end_with? SLASH) ? '' : SLASH}#{target})
486
484
  end
487
485
 
488
486
  # use this logic instead if we want to normalize target if it contains a URI
489
487
  #unless web_root? target
490
- # if preserve_uri_target && (uri_prefix = Helpers.uri_prefix target)
491
- # target = target[uri_prefix.length..-1]
492
- # elsif !start.nil_or_empty?
493
- # target = %(#{start}#{SLASH}#{target})
494
- # if (uri_prefix = Helpers.uri_prefix target)
495
- # target = target[uri_prefix.length..-1]
496
- # end
497
- # end
488
+ # target, uri_prefix = extract_uri_prefix target if preserve_uri_target
489
+ # target, uri_prefix = extract_uri_prefix %(#{start}#{SLASH}#{target}) unless uri_prefix || start.nil_or_empty?
498
490
  #end
499
491
 
500
492
  target_segments, target_root = partition_path target, true
@@ -521,5 +513,23 @@ class PathResolver
521
513
 
522
514
  uri_prefix ? %(#{uri_prefix}#{resolved_path}) : resolved_path
523
515
  end
516
+
517
+ private
518
+
519
+ # Internal: Efficiently extracts the URI prefix from the specified String if the String is a URI
520
+ #
521
+ # Uses the Asciidoctor::UriSniffRx regex to match the URI prefix in the specified String (e.g., http://). If present,
522
+ # the prefix is removed.
523
+ #
524
+ # str - the String to check
525
+ #
526
+ # returns a tuple containing the specified string without the URI prefix, if present, and the extracted URI prefix.
527
+ def extract_uri_prefix str
528
+ if (str.include? ':') && UriSniffRx =~ str
529
+ [(str.slice $&.length, str.length), $&]
530
+ else
531
+ str
532
+ end
533
+ end
524
534
  end
525
535
  end
@@ -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, default: false).
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
- ::Array === data ? (Helpers.prepare_source_array data) : (Helpers.prepare_source_string data)
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
@@ -717,7 +718,7 @@ class PreprocessorReader < Reader
717
718
  end
718
719
 
719
720
  # effectively fill the buffer
720
- if (@lines = prepare_lines data, normalize: true, condense: false, indent: attributes['indent']).empty?
721
+ if (@lines = prepare_lines data, normalize: @process_lines || :chomp, condense: @process_lines, indent: attributes['indent']).empty?
721
722
  pop_include
722
723
  else
723
724
  # FIXME we eventually want to handle leveloffset without affecting the lines
@@ -1060,7 +1061,13 @@ class PreprocessorReader < Reader
1060
1061
  return inc_path
1061
1062
  end
1062
1063
 
1064
+ if (enc = parsed_attrs['encoding']) && (::Encoding.find enc rescue nil)
1065
+ (read_mode_params = read_mode.split ':')[1] = enc
1066
+ read_mode = read_mode_params.join ':'
1067
+ end unless RUBY_ENGINE_OPAL
1068
+
1063
1069
  inc_linenos = inc_tags = nil
1070
+ # NOTE attrlist is nil if missing from include directive
1064
1071
  if attrlist
1065
1072
  if parsed_attrs.key? 'lines'
1066
1073
  inc_linenos = []
@@ -1148,7 +1155,7 @@ class PreprocessorReader < Reader
1148
1155
  active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
1149
1156
  elsif inc_tags.key? this_tag
1150
1157
  include_cursor = create_include_cursor inc_path, expanded_target, inc_lineno
1151
- if (idx = tag_stack.rindex {|key, _| key == this_tag })
1158
+ if (idx = tag_stack.rindex {|key,| key == this_tag })
1152
1159
  idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
1153
1160
  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
1161
  else
@@ -0,0 +1,722 @@
1
+ module Asciidoctor
2
+ # A collection of regular expression constants used by the parser. (For speed, these are not defined in the Rx module,
3
+ # but rather directly in the Asciidoctor module).
4
+ #
5
+ # NOTE The following pattern, which appears frequently, captures the contents between square brackets, ignoring
6
+ # escaped closing brackets (closing brackets prefixed with a backslash '\' character)
7
+ #
8
+ # Pattern: \[(|#{CC_ALL}*?[^\\])\]
9
+ # Matches: [enclosed text] and [enclosed [text\]], not [enclosed text \\] or [\\] (as these require a trailing space)
10
+ module Rx; end
11
+
12
+ ## Document header
13
+
14
+ # Matches the author info line immediately following the document title.
15
+ #
16
+ # Examples
17
+ #
18
+ # Doc Writer <doc@example.com>
19
+ # Mary_Sue Brontë
20
+ #
21
+ AuthorInfoLineRx = /^(#{CG_WORD}[#{CC_WORD}\-'.]*)(?: +(#{CG_WORD}[#{CC_WORD}\-'.]*))?(?: +(#{CG_WORD}[#{CC_WORD}\-'.]*))?(?: +<([^>]+)>)?$/
22
+
23
+ # Matches the delimiter that separates multiple authors.
24
+ #
25
+ # Examples
26
+ #
27
+ # Doc Writer; Junior Writer
28
+ #
29
+ AuthorDelimiterRx = /;(?: |$)/
30
+
31
+ # Matches the revision info line, which appears immediately following
32
+ # the author info line beneath the document title.
33
+ #
34
+ # Examples
35
+ #
36
+ # v1.0
37
+ # 2013-01-01
38
+ # v1.0, 2013-01-01: Ring in the new year release
39
+ # 1.0, Jan 01, 2013
40
+ #
41
+ RevisionInfoLineRx = /^(?:[^\d{]*(#{CC_ANY}*?),)? *(?!:)(#{CC_ANY}*?)(?: *(?!^),?: *(#{CC_ANY}*))?$/
42
+
43
+ # Matches the title and volnum in the manpage doctype.
44
+ #
45
+ # Examples
46
+ #
47
+ # = asciidoctor(1)
48
+ # = asciidoctor ( 1 )
49
+ #
50
+ ManpageTitleVolnumRx = /^(#{CC_ANY}+?) *\( *(#{CC_ANY}+?) *\)$/
51
+
52
+ # Matches the name and purpose in the manpage doctype.
53
+ #
54
+ # Examples
55
+ #
56
+ # asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
57
+ #
58
+ ManpageNamePurposeRx = /^(#{CC_ANY}+?) +- +(#{CC_ANY}+)$/
59
+
60
+ ## Preprocessor directives
61
+
62
+ # Matches a conditional preprocessor directive (e.g., ifdef, ifndef, ifeval and endif).
63
+ #
64
+ # Examples
65
+ #
66
+ # ifdef::basebackend-html[]
67
+ # ifndef::theme[]
68
+ # ifeval::["{asciidoctor-version}" >= "0.1.0"]
69
+ # ifdef::asciidoctor[Asciidoctor!]
70
+ # endif::theme[]
71
+ # endif::basebackend-html[]
72
+ # endif::[]
73
+ #
74
+ ConditionalDirectiveRx = /^(\\)?(ifdef|ifndef|ifeval|endif)::(\S*?(?:([,+])\S*?)?)\[(#{CC_ANY}+)?\]$/
75
+
76
+ # Matches a restricted (read as safe) eval expression.
77
+ #
78
+ # Examples
79
+ #
80
+ # "{asciidoctor-version}" >= "0.1.0"
81
+ #
82
+ EvalExpressionRx = /^(#{CC_ANY}+?) *([=!><]=|[><]) *(#{CC_ANY}+)$/
83
+
84
+ # Matches an include preprocessor directive.
85
+ #
86
+ # Examples
87
+ #
88
+ # include::chapter1.ad[]
89
+ # include::example.txt[lines=1;2;5..10]
90
+ #
91
+ IncludeDirectiveRx = /^(\\)?include::([^\[][^\[]*)\[(#{CC_ANY}+)?\]$/
92
+
93
+ # Matches a trailing tag directive in an include file.
94
+ #
95
+ # Examples
96
+ #
97
+ # // tag::try-catch[]
98
+ # try {
99
+ # someMethod();
100
+ # catch (Exception e) {
101
+ # log(e);
102
+ # }
103
+ # // end::try-catch[]
104
+ # NOTE m flag is required for Asciidoctor.js
105
+ TagDirectiveRx = /\b(?:tag|(e)nd)::(\S+?)\[\](?=$|[ \r])/m
106
+
107
+ ## Attribute entries and references
108
+
109
+ # Matches a document attribute entry.
110
+ #
111
+ # Examples
112
+ #
113
+ # :foo: bar
114
+ # :First Name: Dan
115
+ # :sectnums!:
116
+ # :!toc:
117
+ # :long-entry: Attribute value lines ending in ' \' \
118
+ # are joined together as a single value, \
119
+ # collapsing the line breaks and indentation to \
120
+ # a single space.
121
+ #
122
+ AttributeEntryRx = /^:(!?#{CG_WORD}[^:]*):(?:[ \t]+(#{CC_ANY}*))?$/
123
+
124
+ # Matches invalid characters in an attribute name.
125
+ InvalidAttributeNameCharsRx = /[^#{CC_WORD}-]/
126
+
127
+ # Matches a pass inline macro that surrounds the value of an attribute
128
+ # entry once it has been parsed.
129
+ #
130
+ # Examples
131
+ #
132
+ # pass:[text]
133
+ # pass:a[{a} {b} {c}]
134
+ #
135
+ if RUBY_ENGINE == 'opal'
136
+ # NOTE In JavaScript, ^ and $ match the boundaries of the string when the m flag is not set
137
+ AttributeEntryPassMacroRx = /^pass:([a-z]+(?:,[a-z-]+)*)?\[(#{CC_ALL}*)\]$/
138
+ else
139
+ AttributeEntryPassMacroRx = /\Apass:([a-z]+(?:,[a-z-]+)*)?\[(.*)\]\Z/m
140
+ end
141
+
142
+ # Matches an inline attribute reference.
143
+ #
144
+ # Examples
145
+ #
146
+ # {foobar} or {app_name} or {product-version}
147
+ # {counter:sequence-name:1}
148
+ # {set:foo:bar}
149
+ # {set:name!}
150
+ #
151
+ AttributeReferenceRx = /(\\)?\{(#{CG_WORD}[#{CC_WORD}-]*|(set|counter2?):#{CC_ANY}+?)(\\)?\}/
152
+
153
+ ## Paragraphs and delimited blocks
154
+
155
+ # Matches an anchor (i.e., id + optional reference text) on a line above a block.
156
+ #
157
+ # Examples
158
+ #
159
+ # [[idname]]
160
+ # [[idname,Reference Text]]
161
+ #
162
+ BlockAnchorRx = /^\[\[(?:|([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+))?)\]\]$/
163
+
164
+ # Matches an attribute list above a block element.
165
+ #
166
+ # Examples
167
+ #
168
+ # # strictly positional
169
+ # [quote, Adam Smith, Wealth of Nations]
170
+ #
171
+ # # name/value pairs
172
+ # [NOTE, caption="Good to know"]
173
+ #
174
+ # # as attribute reference
175
+ # [{lead}]
176
+ #
177
+ BlockAttributeListRx = /^\[(|[#{CC_WORD}.#%{,"']#{CC_ANY}*)\]$/
178
+
179
+ # A combined pattern that matches either a block anchor or a block attribute list.
180
+ #
181
+ # TODO this one gets hit a lot, should be optimized as much as possible
182
+ BlockAttributeLineRx = /^\[(?:|[#{CC_WORD}.#%{,"']#{CC_ANY}*|\[(?:|[#{CC_ALPHA}_:][#{CC_WORD}\-:.]*(?:, *#{CC_ANY}+)?)\])\]$/
183
+
184
+ # Matches a title above a block.
185
+ #
186
+ # Examples
187
+ #
188
+ # .Title goes here
189
+ #
190
+ BlockTitleRx = /^\.(\.?[^ \t.]#{CC_ANY}*)$/
191
+
192
+ # Matches an admonition label at the start of a paragraph.
193
+ #
194
+ # Examples
195
+ #
196
+ # NOTE: Just a little note.
197
+ # TIP: Don't forget!
198
+ #
199
+ AdmonitionParagraphRx = /^(#{ADMONITION_STYLES.to_a.join '|'}):[ \t]+/
200
+
201
+ # Matches a literal paragraph, which is a line of text preceded by at least one space.
202
+ #
203
+ # Examples
204
+ #
205
+ # <SPACE>Foo
206
+ # <TAB>Foo
207
+ LiteralParagraphRx = /^([ \t]+#{CC_ANY}*)$/
208
+
209
+ # Matches a comment block.
210
+ #
211
+ # Examples
212
+ #
213
+ # ////
214
+ # This is a block comment.
215
+ # It can span one or more lines.
216
+ # ////
217
+ #CommentBlockRx = %r(^/{4,}$)
218
+
219
+ # Matches a comment line.
220
+ #
221
+ # Examples
222
+ #
223
+ # // note to author
224
+ #
225
+ #CommentLineRx = %r(^//(?=[^/]|$))
226
+
227
+ ## Section titles
228
+
229
+ # Matches an Atx (single-line) section title.
230
+ #
231
+ # Examples
232
+ #
233
+ # == Foo
234
+ # // ^ a level 1 (h2) section title
235
+ #
236
+ # == Foo ==
237
+ # // ^ also a level 1 (h2) section title
238
+ #
239
+ AtxSectionTitleRx = /^(=={0,5})[ \t]+(#{CC_ANY}+?)(?:[ \t]+\1)?$/
240
+
241
+ # Matches an extended Atx section title that includes support for the Markdown variant.
242
+ ExtAtxSectionTitleRx = /^(=={0,5}|#\#{0,5})[ \t]+(#{CC_ANY}+?)(?:[ \t]+\1)?$/
243
+
244
+ # Matches the title only (first line) of an Setext (two-line) section title.
245
+ # The title cannot begin with a dot and must have at least one alphanumeric character.
246
+ SetextSectionTitleRx = /^((?!\.)#{CC_ANY}*?#{CG_ALNUM}#{CC_ANY}*)$/
247
+
248
+ # Matches an anchor (i.e., id + optional reference text) inside a section title.
249
+ #
250
+ # Examples
251
+ #
252
+ # Section Title [[idname]]
253
+ # Section Title [[idname,Reference Text]]
254
+ #
255
+ InlineSectionAnchorRx = / (\\)?\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+))?\]\]$/
256
+
257
+ # Matches invalid ID characters in a section title.
258
+ #
259
+ # NOTE uppercase chars not included since expression is only run on a lowercase string
260
+ InvalidSectionIdCharsRx = /<[^>]+>|&(?:[a-z][a-z]+\d{0,2}|#\d\d\d{0,4}|#x[\da-f][\da-f][\da-f]{0,3});|[^ #{CC_WORD}\-.]+?/
261
+
262
+ # Matches an explicit section level style like sect1
263
+ #
264
+ SectionLevelStyleRx = /^sect\d$/
265
+
266
+ ## Lists
267
+
268
+ # Detects the start of any list item.
269
+ #
270
+ # NOTE we only have to check as far as the blank character because we know it means non-whitespace follows.
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])|<(?:\d+|\.)>[ \t]))
273
+
274
+ # Matches an unordered list item (one level for hyphens, up to 5 levels for asterisks).
275
+ #
276
+ # Examples
277
+ #
278
+ # * Foo
279
+ # - Foo
280
+ #
281
+ # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
282
+ UnorderedListRx = /^[ \t]*(-|\*\**|\u2022)[ \t]+(#{CC_ANY}*)$/
283
+
284
+ # Matches an ordered list item (explicit numbering or up to 5 consecutive dots).
285
+ #
286
+ # Examples
287
+ #
288
+ # . Foo
289
+ # .. Foo
290
+ # 1. Foo (arabic, default)
291
+ # a. Foo (loweralpha)
292
+ # A. Foo (upperalpha)
293
+ # i. Foo (lowerroman)
294
+ # I. Foo (upperroman)
295
+ #
296
+ # NOTE leading space match is not always necessary, but is used for list reader
297
+ # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
298
+ OrderedListRx = /^[ \t]*(\.\.*|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]+(#{CC_ANY}*)$/
299
+
300
+ # Matches the ordinals for each type of ordered list.
301
+ OrderedListMarkerRxMap = {
302
+ arabic: /\d+\./,
303
+ loweralpha: /[a-z]\./,
304
+ lowerroman: /[ivx]+\)/,
305
+ upperalpha: /[A-Z]\./,
306
+ upperroman: /[IVX]+\)/,
307
+ #lowergreek: /[a-z]\]/,
308
+ }
309
+
310
+ # Matches a description list entry.
311
+ #
312
+ # Examples
313
+ #
314
+ # foo::
315
+ # bar:::
316
+ # baz::::
317
+ # blah;;
318
+ #
319
+ # # the term may be followed by a description on the same line...
320
+ #
321
+ # foo:: The metasyntactic variable that commonly accompanies 'bar' (see also, <<bar>>).
322
+ #
323
+ # # ...or on a separate line, which may optionally be indented
324
+ #
325
+ # foo::
326
+ # The metasyntactic variable that commonly accompanies 'bar' (see also, <<bar>>).
327
+ #
328
+ # # attribute references may be used in both the term and the description
329
+ #
330
+ # {foo-term}:: {foo-desc}
331
+ #
332
+ # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
333
+ # NOTE must skip line comment when looking for next list item inside list
334
+ DescriptionListRx = %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?)(:::{0,2}|;;)(?:$|[ \t]+(#{CC_ANY}*)$))
335
+
336
+ # Matches a sibling description list item (excluding the delimiter specified by the key).
337
+ # NOTE must skip line comment when looking for sibling list item
338
+ DescriptionListSiblingRx = {
339
+ '::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(::)(?:$|[ \t]+(#{CC_ANY}*)$)),
340
+ ':::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(:::)(?:$|[ \t]+(#{CC_ANY}*)$)),
341
+ '::::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(::::)(?:$|[ \t]+(#{CC_ANY}*)$)),
342
+ ';;' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?)(;;)(?:$|[ \t]+(#{CC_ANY}*)$))
343
+ }
344
+
345
+ # Matches a callout list item.
346
+ #
347
+ # Examples
348
+ #
349
+ # <1> Explanation
350
+ #
351
+ # or
352
+ #
353
+ # <.> Explanation with automatic number
354
+ #
355
+ # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
356
+ CalloutListRx = /^<(\d+|\.)>[ \t]+(#{CC_ANY}*)$/
357
+
358
+ # Matches a callout reference inside literal text.
359
+ #
360
+ # Examples
361
+ # <1> (optionally prefixed by //, #, -- or ;; line comment chars)
362
+ # <1> <2> (multiple callouts on one line)
363
+ # <!--1--> (for XML-based languages)
364
+ # <.> (auto-numbered)
365
+ #
366
+ # NOTE extract regexps are applied line-by-line, so we can use $ as end-of-line char
367
+ CalloutExtractRx = %r(((?://|#|--|;;) ?)?(\\)?<!?(|--)(\d+|\.)\3>(?=(?: ?\\?<!?\3(?:\d+|\.)\3>)*$))
368
+ CalloutExtractRxt = '(\\\\)?<()(\\d+|\\.)>(?=(?: ?\\\\?<(?:\\d+|\\.)>)*$)'
369
+ CalloutExtractRxMap = ::Hash.new {|h, k| h[k] = /(#{k.empty? ? '' : "#{::Regexp.escape k} ?"})?#{CalloutExtractRxt}/ }
370
+ # NOTE special characters have not been replaced when scanning
371
+ CalloutScanRx = /\\?<!?(|--)(\d+|\.)\1>(?=(?: ?\\?<!?\1(?:\d+|\.)\1>)*#{CC_EOL})/
372
+ # NOTE special characters have already been replaced when converting to an SGML format
373
+ CalloutSourceRx = %r(((?://|#|--|;;) ?)?(\\)?&lt;!?(|--)(\d+|\.)\3&gt;(?=(?: ?\\?&lt;!?\3(?:\d+|\.)\3&gt;)*#{CC_EOL}))
374
+ CalloutSourceRxt = "(\\\\)?&lt;()(\\d+|\\.)&gt;(?=(?: ?\\\\?&lt;(?:\\d+|\\.)&gt;)*#{CC_EOL})"
375
+ CalloutSourceRxMap = ::Hash.new {|h, k| h[k] = /(#{k.empty? ? '' : "#{::Regexp.escape k} ?"})?#{CalloutSourceRxt}/ }
376
+
377
+ # A Hash of regexps for lists used for dynamic access.
378
+ ListRxMap = { ulist: UnorderedListRx, olist: OrderedListRx, dlist: DescriptionListRx, colist: CalloutListRx }
379
+
380
+ ## Tables
381
+
382
+ # Parses the column spec (i.e., colspec) for a table.
383
+ #
384
+ # Examples
385
+ #
386
+ # 1*h,2*,^3e
387
+ #
388
+ ColumnSpecRx = /^(?:(\d+)\*)?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?(\d+%?|~)?([a-z])?$/
389
+
390
+ # Parses the start and end of a cell spec (i.e., cellspec) for a table.
391
+ #
392
+ # Examples
393
+ #
394
+ # 2.3+<.>m
395
+ #
396
+ # FIXME use step-wise scan (or treetop) rather than this mega-regexp
397
+ CellSpecStartRx = /^[ \t]*(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
398
+ CellSpecEndRx = /[ \t]+(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
399
+
400
+ # Block macros
401
+
402
+ # Matches the custom block macro pattern.
403
+ #
404
+ # Examples
405
+ #
406
+ # gist::123456[]
407
+ #
408
+ #--
409
+ # NOTE we've relaxed the match for target to accomodate the short format (e.g., name::[attrlist])
410
+ CustomBlockMacroRx = /^(#{CG_WORD}[#{CC_WORD}-]*)::(|\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
411
+
412
+ # Matches an image, video or audio block macro.
413
+ #
414
+ # Examples
415
+ #
416
+ # image::filename.png[Caption]
417
+ # video::http://youtube.com/12345[Cats vs Dogs]
418
+ #
419
+ BlockMediaMacroRx = /^(image|video|audio)::(\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
420
+
421
+ # Matches the TOC block macro.
422
+ #
423
+ # Examples
424
+ #
425
+ # toc::[]
426
+ # toc::[levels=2]
427
+ #
428
+ BlockTocMacroRx = /^toc::\[(#{CC_ANY}+)?\]$/
429
+
430
+ ## Inline macros
431
+
432
+ # Matches an anchor (i.e., id + optional reference text) in the flow of text.
433
+ #
434
+ # Examples
435
+ #
436
+ # [[idname]]
437
+ # [[idname,Reference Text]]
438
+ # anchor:idname[]
439
+ # anchor:idname[Reference Text]
440
+ #
441
+ InlineAnchorRx = /(\\)?(?:\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]|anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\]))/
442
+
443
+ # Scans for a non-escaped anchor (i.e., id + optional reference text) in the flow of text.
444
+ InlineAnchorScanRx = /(?:^|[^\\\[])\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]|(?:^|[^\\])anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\])/
445
+
446
+ # Scans for a leading, non-escaped anchor (i.e., id + optional reference text).
447
+ LeadingInlineAnchorRx = /^\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]/
448
+
449
+ # Matches a bibliography anchor at the start of the list item text (in a bibliography list).
450
+ #
451
+ # Examples
452
+ #
453
+ # [[[Fowler_1997]]] Fowler M. ...
454
+ #
455
+ InlineBiblioAnchorRx = /^\[\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]\]/
456
+
457
+ # Matches an inline e-mail address.
458
+ #
459
+ # doc.writer@example.com
460
+ #
461
+ InlineEmailRx = %r(([\\>:/])?#{CG_WORD}(?:&amp;|[#{CC_WORD}\-.%+])*@#{CG_ALNUM}[#{CC_ALNUM}_\-.]*\.[a-zA-Z]{2,5}\b)
462
+
463
+ # Matches an inline footnote macro, which is allowed to span multiple lines.
464
+ #
465
+ # Examples
466
+ # footnote:[text] (not referenceable)
467
+ # footnote:id[text] (referenceable)
468
+ # footnote:id[] (reference)
469
+ # footnoteref:[id,text] (legacy)
470
+ # footnoteref:[id] (legacy)
471
+ #
472
+ InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!<\/a>)/m
473
+
474
+ # Matches an image or icon inline macro.
475
+ #
476
+ # Examples
477
+ #
478
+ # image:filename.png[Alt Text]
479
+ # image:http://example.com/images/filename.png[Alt Text]
480
+ # image:filename.png[More [Alt\] Text] (alt text becomes "More [Alt] Text")
481
+ # icon:github[large]
482
+ #
483
+ # NOTE be as non-greedy as possible by not allowing newline or left square bracket in target
484
+ InlineImageMacroRx = /\\?i(?:mage|con):([^:\s\[](?:[^\n\[]*[^\s\[])?)\[(|#{CC_ALL}*?[^\\])\]/m
485
+
486
+ # Matches an indexterm inline macro, which may span multiple lines.
487
+ #
488
+ # Examples
489
+ #
490
+ # indexterm:[Tigers,Big cats]
491
+ # (((Tigers,Big cats)))
492
+ # indexterm2:[Tigers]
493
+ # ((Tigers))
494
+ #
495
+ InlineIndextermMacroRx = /\\?(?:(indexterm2?):\[(#{CC_ALL}*?[^\\])\]|\(\((#{CC_ALL}+?)\)\)(?!\)))/m
496
+
497
+ # Matches either the kbd or btn inline macro.
498
+ #
499
+ # Examples
500
+ #
501
+ # kbd:[F3]
502
+ # kbd:[Ctrl+Shift+T]
503
+ # kbd:[Ctrl+\]]
504
+ # kbd:[Ctrl,T]
505
+ # btn:[Save]
506
+ #
507
+ InlineKbdBtnMacroRx = /(\\)?(kbd|btn):\[(#{CC_ALL}*?[^\\])\]/m
508
+
509
+ # Matches an implicit link and some of the link inline macro.
510
+ #
511
+ # Examples
512
+ #
513
+ # https://github.com
514
+ # https://github.com[GitHub]
515
+ # <https://github.com>
516
+ # link:https://github.com[]
517
+ # "https://github.com[]"
518
+ #
519
+ # FIXME revisit! the main issue is we need different rules for implicit vs explicit
520
+ InlineLinkRx = %r((^|link:|#{CG_BLANK}|&lt;|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
521
+
522
+ # Match a link or e-mail inline macro.
523
+ #
524
+ # Examples
525
+ #
526
+ # link:path[label]
527
+ # mailto:doc.writer@example.com[]
528
+ #
529
+ # NOTE be as non-greedy as possible by not allowing space or left square bracket in target
530
+ InlineLinkMacroRx = /\\?(?:link|(mailto)):(|[^:\s\[][^\s\[]*)\[(|#{CC_ALL}*?[^\\])\]/m
531
+
532
+ # Matches the name of a macro.
533
+ #
534
+ MacroNameRx = /^#{CG_WORD}[#{CC_WORD}-]*$/
535
+
536
+ # Matches a stem (and alternatives, asciimath and latexmath) inline macro, which may span multiple lines.
537
+ #
538
+ # Examples
539
+ #
540
+ # stem:[x != 0]
541
+ # asciimath:[x != 0]
542
+ # latexmath:[\sqrt{4} = 2]
543
+ #
544
+ InlineStemMacroRx = /\\?(stem|(?:latex|ascii)math):([a-z]+(?:,[a-z-]+)*)?\[(#{CC_ALL}*?[^\\])\]/m
545
+
546
+ # Matches a menu inline macro.
547
+ #
548
+ # Examples
549
+ #
550
+ # menu:File[Save As...]
551
+ # menu:Edit[]
552
+ # menu:View[Page Style > No Style]
553
+ # menu:View[Page Style, No Style]
554
+ #
555
+ InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))\]/m
556
+
557
+ # Matches an implicit menu inline macro.
558
+ #
559
+ # Examples
560
+ #
561
+ # "File > New..."
562
+ #
563
+ InlineMenuRx = /\\?"([#{CC_WORD}&][^"]*?[ \n]+&gt;[ \n]+[^"]*)"/
564
+
565
+ # Matches an inline passthrough, which may span multiple lines.
566
+ #
567
+ # Examples
568
+ #
569
+ # +text+
570
+ # `text` (compat)
571
+ #
572
+ # NOTE we always capture the attributes so we know when to use compatible (i.e., legacy) behavior
573
+ InlinePassRx = {
574
+ false => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+)\])?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m],
575
+ true => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+)\])?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m]
576
+ }
577
+
578
+ # Matches an inline plus passthrough spanning multiple lines, but only when it occurs directly
579
+ # inside constrained monospaced formatting in non-compat mode.
580
+ #
581
+ # Examples
582
+ #
583
+ # +text+
584
+ #
585
+ SinglePlusInlinePassRx = /^(\\)?\+(\S|\S#{CC_ALL}*?\S)\+$/m
586
+
587
+ # Matches several variants of the passthrough inline macro, which may span multiple lines.
588
+ #
589
+ # Examples
590
+ #
591
+ # +++text+++
592
+ # $$text$$
593
+ # pass:quotes[text]
594
+ #
595
+ # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc Python
596
+ InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
597
+
598
+ # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
599
+ #
600
+ # Examples
601
+ #
602
+ # <<id,reftext>>
603
+ # xref:id[reftext]
604
+ #
605
+ # NOTE special characters have already been escaped, hence the entity references
606
+ # NOTE { is included in start characters to support target that begins with attribute reference in title content
607
+ InlineXrefMacroRx = %r(\\?(?:&lt;&lt;([#{CC_WORD}#/.:{]#{CC_ALL}*?)&gt;&gt;|xref:([#{CC_WORD}#/.:{]#{CC_ALL}*?)\[(?:\]|(#{CC_ALL}*?[^\\])\])))m
608
+
609
+ ## Layout
610
+
611
+ # Matches a trailing + preceded by at least one space character,
612
+ # which forces a hard line break (<br> tag in HTML output).
613
+ #
614
+ # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
615
+ #
616
+ # Examples
617
+ #
618
+ # Humpty Dumpty sat on a wall, +
619
+ # Humpty Dumpty had a great fall.
620
+ #
621
+ if RUBY_ENGINE == 'opal'
622
+ # NOTE In JavaScript, ^ and $ only match the start and end of line if the multiline flag is present
623
+ HardLineBreakRx = /^(#{CC_ANY}*) \+$/m
624
+ else
625
+ # NOTE In Ruby, ^ and $ always match start and end of line
626
+ HardLineBreakRx = /^(.*) \+$/
627
+ end
628
+
629
+ # Matches a Markdown horizontal rule.
630
+ #
631
+ # Examples
632
+ #
633
+ # --- or - - -
634
+ # *** or * * *
635
+ # ___ or _ _ _
636
+ #
637
+ MarkdownThematicBreakRx = /^ {0,3}([-*_])( *)\1\2\1$/
638
+
639
+ # Matches an AsciiDoc or Markdown horizontal rule or AsciiDoc page break.
640
+ #
641
+ # Examples
642
+ #
643
+ # ''' (horizontal rule)
644
+ # <<< (page break)
645
+ # --- or - - - (horizontal rule, Markdown)
646
+ # *** or * * * (horizontal rule, Markdown)
647
+ # ___ or _ _ _ (horizontal rule, Markdown)
648
+ #
649
+ ExtLayoutBreakRx = /^(?:'{3,}|<{3,}|([-*_])( *)\1\2\1)$/
650
+
651
+ ## General
652
+
653
+ # Matches consecutive blank lines.
654
+ #
655
+ # Examples
656
+ #
657
+ # one
658
+ #
659
+ # two
660
+ #
661
+ BlankLineRx = /\n{2,}/
662
+
663
+ # Matches a comma or semi-colon delimiter.
664
+ #
665
+ # Examples
666
+ #
667
+ # one,two
668
+ # three;four
669
+ #
670
+ #DataDelimiterRx = /[,;]/
671
+
672
+ # Matches whitespace (space, tab, newline) escaped by a backslash.
673
+ #
674
+ # Examples
675
+ #
676
+ # three\ blind\ mice
677
+ #
678
+ EscapedSpaceRx = /\\([ \t\n])/
679
+
680
+ # Detects if text is a possible candidate for the replacements substitution.
681
+ #
682
+ ReplaceableTextRx = /[&']|--|\.\.\.|\([CRT]M?\)/
683
+
684
+ # Matches a whitespace delimiter, a sequence of spaces, tabs, and/or newlines.
685
+ # Matches the parsing rules of %w strings in Ruby.
686
+ #
687
+ # Examples
688
+ #
689
+ # one two three four
690
+ # five six
691
+ #
692
+ # TODO change to /(?<!\\)[ \t\n]+/ once lookbehind assertions are implemented in all modern browsers
693
+ SpaceDelimiterRx = /([^\\])[ \t\n]+/
694
+
695
+ # Matches a + or - modifier in a subs list
696
+ #
697
+ SubModifierSniffRx = /[+-]/
698
+
699
+ # Matches one or more consecutive digits at the end of a line.
700
+ #
701
+ # Examples
702
+ #
703
+ # docbook5
704
+ # html5
705
+ #
706
+ TrailingDigitsRx = /\d+$/
707
+
708
+ # Detects strings that resemble URIs.
709
+ #
710
+ # Examples
711
+ # http://domain
712
+ # https://domain
713
+ # file:///path
714
+ # data:info
715
+ #
716
+ # not c:/sample.adoc or c:\sample.adoc
717
+ #
718
+ UriSniffRx = %r(^#{CG_ALPHA}[#{CC_ALNUM}.+-]+:/{0,2})
719
+
720
+ # Detects XML tags
721
+ XmlSanitizeRx = /<[^>]+>/
722
+ end