asciidoctor 2.0.0.rc.2 → 2.0.0.rc.3

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.
@@ -58,7 +58,7 @@ class Converter::TemplateConverter < Converter::Base
58
58
  @safe = opts[:safe]
59
59
  @active_engines = {}
60
60
  @engine = opts[:template_engine]
61
- @engine_options = {}.tap {|accum| DEFAULT_ENGINE_OPTIONS.each {|engine, engine_opts| accum[engine] = engine_opts.dup } }
61
+ @engine_options = {}.tap {|accum| DEFAULT_ENGINE_OPTIONS.each {|engine, engine_opts| accum[engine] = engine_opts.merge } }
62
62
  if opts[:htmlsyntax] == 'html' # if not set, assume xml since this converter is also used for DocBook (which doesn't specify htmlsyntax)
63
63
  @engine_options[:haml][:format] = :html5
64
64
  @engine_options[:slim][:format] = :html
@@ -124,7 +124,7 @@ class Converter::TemplateConverter < Converter::Base
124
124
  #
125
125
  # Returns a [Hash] of Tilt template objects keyed by template name.
126
126
  def templates
127
- @templates.dup
127
+ @templates.merge
128
128
  end
129
129
 
130
130
  # Public: Registers a Tilt template with this converter.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative 'core_ext/float/truncate'
3
+ require_relative 'core_ext/hash/merge'
3
4
  require_relative 'core_ext/match_data/names' if RUBY_ENGINE == 'opal'
4
5
  require_relative 'core_ext/nil_or_empty'
5
6
  require_relative 'core_ext/regexp/is_match'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ # NOTE remove once minimum required Ruby version is at least 2.6
3
+ Hash.prepend(Module.new do
4
+ def merge *args
5
+ (len = args.length) < 1 ? super({}) : (len > 1 ? args.inject(self) {|acc, arg| acc.merge arg } : (super args[0]))
6
+ end
7
+ end) if (Hash.instance_method :merge).arity == 1
@@ -241,7 +241,7 @@ class Document < AbstractBlock
241
241
  #
242
242
  # data - The AsciiDoc source data as a String or String Array. (default: nil)
243
243
  # options - A Hash of options to control processing (e.g., safe mode value (:safe), backend (:backend),
244
- # header/footer toggle (:header_footer), custom attributes (:attributes)). (default: {})
244
+ # standalone enclosure (:standalone), custom attributes (:attributes)). (default: {})
245
245
  #
246
246
  # Duplication of the options Hash is handled in the enclosing API.
247
247
  #
@@ -257,10 +257,10 @@ class Document < AbstractBlock
257
257
  @parent_document = parent_doc
258
258
  options[:base_dir] ||= parent_doc.base_dir
259
259
  options[:catalog_assets] = true if parent_doc.options[:catalog_assets]
260
- @catalog = parent_doc.catalog.dup.tap {|catalog| catalog[:footnotes] = [] }
260
+ @catalog = parent_doc.catalog.merge footnotes: []
261
261
  # QUESTION should we support setting attribute in parent document from nested document?
262
262
  # NOTE we must dup or else all the assignments to the overrides clobbers the real attributes
263
- @attribute_overrides = attr_overrides = parent_doc.attributes.dup
263
+ @attribute_overrides = attr_overrides = parent_doc.attributes.merge
264
264
  parent_doctype = attr_overrides.delete 'doctype'
265
265
  attr_overrides.delete 'compat-mode'
266
266
  attr_overrides.delete 'toc'
@@ -284,7 +284,7 @@ class Document < AbstractBlock
284
284
  footnotes: [],
285
285
  links: [],
286
286
  images: [],
287
- indexterms: [],
287
+ #indexterms: [],
288
288
  callouts: Callouts.new,
289
289
  includes: {},
290
290
  }
@@ -328,6 +328,7 @@ class Document < AbstractBlock
328
328
  @path_resolver = PathResolver.new
329
329
  initialize_extensions = (defined? ::Asciidoctor::Extensions) ? true : nil
330
330
  @extensions = nil # initialize furthur down if initialize_extensions is true
331
+ options[:standalone] = options[:header_footer] if (options.key? :header_footer) && !(options.key? :standalone)
331
332
  end
332
333
 
333
334
  @parsed = false
@@ -335,20 +336,20 @@ class Document < AbstractBlock
335
336
  @counters = {}
336
337
  @attributes_modified = ::Set.new
337
338
  @docinfo_processor_extensions = {}
338
- header_footer = (options[:header_footer] ||= false)
339
+ standalone = options[:standalone]
339
340
  (@options = options).freeze
340
341
 
341
342
  attrs = @attributes
342
343
  #attrs['encoding'] = 'UTF-8'
343
344
  attrs['sectids'] = ''
344
345
  attrs['toc-placement'] = 'auto'
345
- if header_footer
346
+ if standalone
346
347
  attrs['copycss'] = ''
347
- # sync embedded attribute with :header_footer option value
348
+ # sync embedded attribute with :standalone option value
348
349
  attr_overrides['embedded'] = nil
349
350
  else
350
351
  attrs['notitle'] = ''
351
- # sync embedded attribute with :header_footer option value
352
+ # sync embedded attribute with :standalone option value
352
353
  attr_overrides['embedded'] = ''
353
354
  end
354
355
  attrs['stylesheet'] = ''
@@ -606,7 +607,8 @@ class Document < AbstractBlock
606
607
  when :refs
607
608
  @catalog[:refs][value[0]] ||= (ref = value[1])
608
609
  ref
609
- when :footnotes, :indexterms
610
+ #when :footnotes, :indexterms
611
+ when :footnotes
610
612
  @catalog[type] << value
611
613
  else
612
614
  @catalog[type] << (type == :images ? (ImageReference.new value[0], value[1]) : value) if @options[:catalog_assets]
@@ -723,6 +725,10 @@ class Document < AbstractBlock
723
725
  end
724
726
  alias name doctitle
725
727
 
728
+ def xreftext xrefstyle = nil
729
+ (val = reftext) && !val.empty? ? val : title
730
+ end
731
+
726
732
  # Public: Convenience method to retrieve the document attribute 'author'
727
733
  #
728
734
  # returns the full name of the author as a String
@@ -928,7 +934,13 @@ class Document < AbstractBlock
928
934
  end
929
935
  end
930
936
  else
931
- transform = ((opts.key? :header_footer) ? opts[:header_footer] : @options[:header_footer]) ? 'document' : 'embedded'
937
+ if opts.key? :standalone
938
+ transform = opts[:standalone] ? 'document' : 'embedded'
939
+ elsif opts.key? :header_footer
940
+ transform = opts[:header_footer] ? 'document' : 'embedded'
941
+ else
942
+ transform = @options[:standalone] ? 'document' : 'embedded'
943
+ end
932
944
  output = @converter.convert self, transform
933
945
  end
934
946
 
@@ -1053,10 +1065,12 @@ class Document < AbstractBlock
1053
1065
 
1054
1066
  # TODO allow document to control whether extension docinfo is contributed
1055
1067
  if @extensions && (docinfo_processors? location)
1056
- (content ||= []).concat @docinfo_processor_extensions[location].map {|ext| ext.process_method[self] }.compact
1068
+ ((content || []).concat @docinfo_processor_extensions[location].map {|ext| ext.process_method[self] }.compact).join LF
1069
+ elsif content
1070
+ content.join LF
1071
+ else
1072
+ ''
1057
1073
  end
1058
-
1059
- content ? (content.join LF) : ''
1060
1074
  end
1061
1075
 
1062
1076
  def docinfo_processors?(location = :head)
@@ -1235,7 +1249,7 @@ class Document < AbstractBlock
1235
1249
  end
1236
1250
  end
1237
1251
 
1238
- @header_attributes = attrs.dup
1252
+ @header_attributes = attrs.merge
1239
1253
  end
1240
1254
 
1241
1255
  # Internal: Assign the local and document datetime attributes, which includes localdate, localyear, localtime,
@@ -201,7 +201,7 @@ module Extensions
201
201
  end
202
202
 
203
203
  def create_inline parent, context, text, opts = {}
204
- Inline.new parent, context, text, opts
204
+ Inline.new parent, context, text, context == :quoted ? ({ type: :unquoted }.merge opts) : opts
205
205
  end
206
206
 
207
207
  # Public: Parses blocks in the content and attaches the block to the parent.
@@ -211,7 +211,7 @@ module Extensions
211
211
  # QUESTION is parse_content the right method name? should we wrap in open block automatically?
212
212
  def parse_content parent, content, attributes = nil
213
213
  reader = Reader === content ? content : (Reader.new content)
214
- while ((block = Parser.next_block reader, parent, (attributes ? attributes.dup : {})) && parent << block) ||
214
+ while ((block = Parser.next_block reader, parent, (attributes ? attributes.merge : {})) && parent << block) ||
215
215
  reader.has_more_lines?
216
216
  end
217
217
  parent
@@ -225,7 +225,8 @@ module Extensions
225
225
  [:create_pass_block, :create_block, :pass],
226
226
  [:create_listing_block, :create_block, :listing],
227
227
  [:create_literal_block, :create_block, :literal],
228
- [:create_anchor, :create_inline, :anchor]
228
+ [:create_anchor, :create_inline, :anchor],
229
+ [:create_inline_pass, :create_inline, :quoted],
229
230
  ].each do |method_name, delegate_method_name, context|
230
231
  define_method method_name do |*args|
231
232
  args.unshift args.shift, context
@@ -3,7 +3,7 @@ module Asciidoctor
3
3
  # Public: Methods for managing inline elements in AsciiDoc block
4
4
  class Inline < AbstractNode
5
5
  # Public: Get the text of this inline element
6
- attr_reader :text
6
+ attr_accessor :text
7
7
 
8
8
  # Public: Get the type (qualifier) of this inline element
9
9
  attr_reader :type
@@ -12,19 +12,12 @@ class Inline < AbstractNode
12
12
  attr_accessor :target
13
13
 
14
14
  def initialize(parent, context, text = nil, opts = {})
15
- super(parent, context)
15
+ super(parent, context, opts)
16
16
  @node_name = %(inline_#{context})
17
-
18
17
  @text = text
19
-
20
18
  @id = opts[:id]
21
19
  @type = opts[:type]
22
20
  @target = opts[:target]
23
-
24
- # value of attributes option for inline nodes may be nil
25
- if (attrs = opts[:attributes])
26
- @attributes = attrs.dup
27
- end
28
21
  end
29
22
 
30
23
  def block?
@@ -346,7 +346,10 @@ class Parser
346
346
  while reader.has_more_lines?
347
347
  parse_block_metadata_lines reader, document, attributes
348
348
  if (next_level = is_next_line_section?(reader, attributes))
349
- next_level += document.attr('leveloffset').to_i if document.attr?('leveloffset')
349
+ if document.attr? 'leveloffset'
350
+ next_level += (document.attr 'leveloffset').to_i
351
+ next_level = 0 if next_level < 0
352
+ end
350
353
  if next_level > current_level
351
354
  if expected_next_level
352
355
  unless next_level == expected_next_level || (expected_next_level_alt && next_level == expected_next_level_alt) || expected_next_level < 0
@@ -445,7 +448,7 @@ class Parser
445
448
  # of a section that need to get transfered to the next section
446
449
  # see "trailing block attributes transfer to the following section" in
447
450
  # test/attributes_test.rb for an example
448
- [section != parent ? section : nil, attributes.dup]
451
+ [section != parent ? section : nil, attributes.merge]
449
452
  end
450
453
 
451
454
  # Public: Parse and return the next Block at the Reader's current location
@@ -579,14 +582,14 @@ class Parser
579
582
  end
580
583
  # style doesn't have special meaning for media macros
581
584
  attributes.delete 'style' if attributes.key? 'style'
582
- if (target.include? ATTR_REF_HEAD) && (target = block.sub_attributes target, attribute_missing: 'drop-line').empty?
583
- # retain as unparsed if attribute-missing is skip
584
- if (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'skip'
585
- return Block.new(parent, :paragraph, content_model: :simple, source: [this_line])
586
- # otherwise, drop the line
587
- else
585
+ if target.include? ATTR_REF_HEAD
586
+ if (expanded_target = block.sub_attributes target).empty? &&
587
+ (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'drop-line' &&
588
+ (block.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty?
588
589
  attributes.clear
589
590
  return
591
+ else
592
+ target = expanded_target
590
593
  end
591
594
  end
592
595
  if blk_ctx == :image
@@ -616,12 +619,16 @@ class Parser
616
619
  if report_unknown_block_macro
617
620
  logger.debug message_with_context %(unknown name for block macro: #{$1}), source_location: reader.cursor_at_mark
618
621
  else
619
- target = $2
620
622
  content = $3
621
- if (target.include? ATTR_REF_HEAD) && (target = parent.sub_attributes target).empty? &&
622
- (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'drop-line'
623
- attributes.clear
624
- return
623
+ if (target = $2).include? ATTR_REF_HEAD
624
+ if (expanded_target = parent.sub_attributes target).empty? &&
625
+ (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'drop-line' &&
626
+ (parent.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty?
627
+ attributes.clear
628
+ return
629
+ else
630
+ target = expanded_target
631
+ end
625
632
  end
626
633
  if extension.config[:content_model] == :attributes
627
634
  document.parse_attributes content, extension.config[:pos_attrs] || [], sub_input: true, into: attributes if content
@@ -903,7 +910,7 @@ class Parser
903
910
  end
904
911
  # FIXME remove the need for this update!
905
912
  block.attributes.update(attributes) unless attributes.empty?
906
- block.lock_in_subs
913
+ block.commit_subs
907
914
 
908
915
  #if doc_attrs.key? :pending_attribute_entries
909
916
  # doc_attrs.delete(:pending_attribute_entries).each do |entry|
@@ -1025,7 +1032,7 @@ class Parser
1025
1032
  if (extension = options[:extension])
1026
1033
  # QUESTION do we want to delete the style?
1027
1034
  attributes.delete('style')
1028
- if (block = extension.process_method[parent, block_reader || (Reader.new lines), attributes.dup])
1035
+ if (block = extension.process_method[parent, block_reader || (Reader.new lines), attributes.merge])
1029
1036
  attributes.replace block.attributes
1030
1037
  # FIXME if the content model is set to compound, but we only have simple in this context, then
1031
1038
  # forcefully set the content_model to simple to prevent parsing blocks from children
@@ -1749,7 +1756,10 @@ class Parser
1749
1756
  else
1750
1757
  raise %(Unrecognized section at #{reader.cursor_at_prev_line})
1751
1758
  end
1752
- sect_level += document.attr('leveloffset').to_i if document.attr?('leveloffset')
1759
+ if document.attr? 'leveloffset'
1760
+ sect_level += (document.attr 'leveloffset').to_i
1761
+ sect_level = 0 if sect_level < 0
1762
+ end
1753
1763
  [sect_id, sect_reftext, sect_title, sect_level, atx]
1754
1764
  end
1755
1765
 
@@ -2448,7 +2458,7 @@ class Parser
2448
2458
  end
2449
2459
 
2450
2460
  if m[1]
2451
- 1.upto(m[1].to_i) { specs << spec.dup }
2461
+ 1.upto(m[1].to_i) { specs << spec.merge }
2452
2462
  else
2453
2463
  specs << spec
2454
2464
  end
@@ -902,30 +902,28 @@ class PreprocessorReader < Reader
902
902
  # attributes are case insensitive
903
903
  target = target.downcase unless (no_target = target.empty?)
904
904
 
905
- # must have a target before brackets if ifdef or ifndef
906
- # must not have text between brackets if endif
907
- # skip line if it doesn't meet this criteria
908
- # QUESTION should we warn for these bogus declarations?
909
- return false if (no_target && (keyword == 'ifdef' || keyword == 'ifndef')) || (text && keyword == 'endif')
910
-
911
905
  if keyword == 'endif'
912
- if @conditional_stack.empty?
913
- logger.error message_with_context %(unmatched macro: endif::#{target}[]), source_location: cursor
906
+ if text
907
+ logger.error message_with_context %(malformed preprocessor directive - text not permitted: endif::#{target}[#{text}]), source_location: cursor
908
+ elsif @conditional_stack.empty?
909
+ logger.error message_with_context %(unmatched preprocessor directive: endif::#{target}[]), source_location: cursor
914
910
  elsif no_target || target == (pair = @conditional_stack[-1])[:target]
915
911
  @conditional_stack.pop
916
912
  @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
917
913
  else
918
- logger.error message_with_context %(mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[]), source_location: cursor
914
+ logger.error message_with_context %(mismatched preprocessor directive: endif::#{target}[], expected endif::#{pair[:target]}[]), source_location: cursor
919
915
  end
920
916
  return true
921
- end
922
-
923
- if @skipping
917
+ elsif @skipping
924
918
  skip = false
925
919
  else
926
920
  # QUESTION any way to wrap ifdef & ifndef logic up together?
927
921
  case keyword
928
922
  when 'ifdef'
923
+ if no_target
924
+ logger.error message_with_context %(malformed preprocessor directive - missing target: ifdef::[#{text}]), source_location: cursor
925
+ return true
926
+ end
929
927
  case delimiter
930
928
  when ','
931
929
  # skip if no attribute is defined
@@ -938,6 +936,10 @@ class PreprocessorReader < Reader
938
936
  skip = !@document.attributes.key?(target)
939
937
  end
940
938
  when 'ifndef'
939
+ if no_target
940
+ logger.error message_with_context %(malformed preprocessor directive - missing target: ifndef::[#{text}]), source_location: cursor
941
+ return true
942
+ end
941
943
  case delimiter
942
944
  when ','
943
945
  # skip if any attribute is defined
@@ -950,15 +952,19 @@ class PreprocessorReader < Reader
950
952
  skip = @document.attributes.key?(target)
951
953
  end
952
954
  when 'ifeval'
953
- # the text in brackets must match an expression
954
- # don't honor match if it doesn't meet this criteria
955
- return false unless no_target && EvalExpressionRx =~ text.strip
956
-
957
- lhs = resolve_expr_val $1
958
- rhs = resolve_expr_val $3
959
-
960
- # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
961
- skip = (lhs.send $2, rhs) ? false : true
955
+ if no_target
956
+ # the text in brackets must match a conditional expression
957
+ if text && EvalExpressionRx =~ text.strip
958
+ # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
959
+ skip = ((resolve_expr_val $1).send $2, (resolve_expr_val $3)) ? false : true
960
+ else
961
+ logger.error message_with_context %(malformed preprocessor directive - #{text ? 'invalid expression' : 'missing expression'}: ifeval::[#{text}]), source_location: cursor
962
+ return true
963
+ end
964
+ else
965
+ logger.error message_with_context %(malformed preprocessor directive - target not permitted: ifeval::#{target}[#{text}]), source_location: cursor
966
+ return true
967
+ end
962
968
  end
963
969
  end
964
970
 
@@ -1008,12 +1014,20 @@ class PreprocessorReader < Reader
1008
1014
  def preprocess_include_directive target, attrlist
1009
1015
  doc = @document
1010
1016
  if ((expanded_target = target).include? ATTR_REF_HEAD) &&
1011
- (expanded_target = doc.sub_attributes target, attribute_missing: 'drop-line').empty?
1012
- shift
1013
- if (doc.attributes['attribute-missing'] || Compliance.attribute_missing) == 'skip'
1014
- unshift %(Unresolved directive in #{@path} - include::#{target}[#{attrlist}])
1017
+ (expanded_target = doc.sub_attributes target, attribute_missing: ((attr_missing = doc.attributes['attribute-missing'] || Compliance.attribute_missing) == 'warn' ? 'drop-line' : attr_missing)).empty?
1018
+ if attr_missing == 'drop-line' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty?
1019
+ logger.info { message_with_context %(include dropped due to missing attribute: include::#{target}[#{attrlist}]), source_location: cursor }
1020
+ shift
1021
+ true
1022
+ elsif (doc.parse_attributes attrlist, [], sub_input: true)['optional-option']
1023
+ logger.info { message_with_context %(optional include dropped #{attr_missing == 'warn' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty? ? 'due to missing attribute' : 'because resolved target is blank'}: include::#{target}[#{attrlist}]), source_location: cursor }
1024
+ shift
1025
+ true
1026
+ else
1027
+ logger.warn message_with_context %(include dropped #{attr_missing == 'warn' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty? ? 'due to missing attribute' : 'because resolved target is blank'}: include::#{target}[#{attrlist}]), source_location: cursor
1028
+ # QUESTION should this line include target or expanded_target (or escaped target?)
1029
+ replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{attrlist}])
1015
1030
  end
1016
- true
1017
1031
  elsif include_processors? && (ext = @include_processor_extensions.find {|candidate| candidate.instance.handles? expanded_target })
1018
1032
  shift
1019
1033
  # FIXME parse attributes only if requested by extension
@@ -1222,6 +1236,7 @@ class PreprocessorReader < Reader
1222
1236
  inc_path = doc.normalize_system_path target, @dir, nil, target_name: 'include file'
1223
1237
  unless ::File.file? inc_path
1224
1238
  if attributes['optional-option']
1239
+ logger.info { message_with_context %(optional include dropped because include file not found: #{inc_path}), source_location: cursor }
1225
1240
  shift
1226
1241
  return true
1227
1242
  else
@@ -111,14 +111,7 @@ class Section < AbstractBlock
111
111
  # Returns the section number as a String
112
112
  def sectnum(delimiter = '.', append = nil)
113
113
  append ||= (append == false ? '' : delimiter)
114
- if @level == 1
115
- %(#{@numeral}#{append})
116
- elsif @level > 1
117
- Section === @parent ? %(#{@parent.sectnum(delimiter, delimiter)}#{@numeral}#{append}) : %(#{@numeral}#{append})
118
- else # @level == 0
119
- # NOTE coerce @numeral to int just in case not set; can happen if section nesting is out of sequence
120
- %(#{Helpers.int_to_roman @numeral.to_i}#{append})
121
- end
114
+ @level > 1 && Section === @parent ? %(#{@parent.sectnum(delimiter, delimiter)}#{@numeral}#{append}) : %(#{@numeral}#{append})
122
115
  end
123
116
 
124
117
  # (see AbstractBlock#xreftext)
@@ -395,9 +395,9 @@ module Substitutors
395
395
  when :none
396
396
  replacement
397
397
  when :bounding
398
- %(#{m[1]}#{replacement}#{m[2]})
398
+ m[1] + replacement + m[2]
399
399
  else # :leading
400
- %(#{m[1]}#{replacement})
400
+ m[1] + replacement
401
401
  end
402
402
  end
403
403
  end
@@ -416,7 +416,7 @@ module Substitutors
416
416
  # Returns the [String] text with the attribute references replaced with resolved values
417
417
  def sub_attributes text, opts = {}
418
418
  doc_attrs = @document.attributes
419
- drop = drop_line = drop_empty_line = attribute_undefined = attribute_missing = nil
419
+ drop = drop_line = drop_line_severity = drop_empty_line = attribute_undefined = attribute_missing = nil
420
420
  text = text.gsub AttributeReferenceRx do
421
421
  # escaped attribute, return unescaped
422
422
  if $1 == RS || $4 == RS
@@ -426,7 +426,7 @@ module Substitutors
426
426
  when 'set'
427
427
  _, value = Parser.store_attribute args[0], args[1] || '', @document
428
428
  # NOTE since this is an assignment, only drop-line applies here (skip and drop imply the same result)
429
- if value || (attribute_undefined ||= doc_attrs['attribute-undefined'] || Compliance.attribute_undefined) != 'drop-line'
429
+ if value || (attribute_undefined ||= (doc_attrs['attribute-undefined'] || Compliance.attribute_undefined)) != 'drop-line'
430
430
  drop = drop_empty_line = DEL
431
431
  else
432
432
  drop = drop_line = CAN
@@ -442,11 +442,15 @@ module Substitutors
442
442
  elsif (value = INTRINSIC_ATTRIBUTES[key])
443
443
  value
444
444
  else
445
- case (attribute_missing ||= opts[:attribute_missing] || doc_attrs['attribute-missing'] || Compliance.attribute_missing)
445
+ case (attribute_missing ||= (opts[:attribute_missing] || doc_attrs['attribute-missing'] || Compliance.attribute_missing))
446
446
  when 'drop'
447
447
  drop = drop_empty_line = DEL
448
448
  when 'drop-line'
449
- logger.warn %(dropping line containing reference to missing attribute: #{key})
449
+ if (drop_line_severity ||= (opts[:drop_line_severity] || :info)) == :info
450
+ logger.info { %(dropping line containing reference to missing attribute: #{key}) }
451
+ #elsif drop_line_severity == :warn
452
+ # logger.warn %(dropping line containing reference to missing attribute: #{key})
453
+ end
450
454
  drop = drop_line = CAN
451
455
  when 'warn'
452
456
  logger.warn %(skipping reference to missing attribute: #{key})
@@ -460,7 +464,7 @@ module Substitutors
460
464
  if drop
461
465
  # drop lines from text
462
466
  if drop_empty_line
463
- lines = (text.tr_s DEL, DEL).split LF, -1
467
+ lines = (text.squeeze DEL).split LF, -1
464
468
  if drop_line
465
469
  (lines.reject {|line| line == DEL || line == CAN || (line.start_with? CAN) || (line.include? CAN) }.join LF).delete DEL
466
470
  else
@@ -486,13 +490,54 @@ module Substitutors
486
490
  def sub_macros(text)
487
491
  #return text if text.nil_or_empty?
488
492
  # some look ahead assertions to cut unnecessary regex calls
489
- found = {}
490
- found_square_bracket = found[:square_bracket] = text.include? '['
493
+ found_square_bracket = text.include? '['
491
494
  found_colon = text.include? ':'
492
- found_macroish = found[:macroish] = found_square_bracket && found_colon
495
+ found_macroish = found_square_bracket && found_colon
493
496
  found_macroish_short = found_macroish && (text.include? ':[')
494
497
  doc_attrs = (doc = @document).attributes
495
498
 
499
+ # TODO allow position of substitution to be controlled (before or after other macros)
500
+ # TODO this handling needs some cleanup
501
+ if (extensions = doc.extensions) && extensions.inline_macros? # && found_macroish
502
+ extensions.inline_macros.each do |extension|
503
+ text = text.gsub extension.instance.regexp do
504
+ # honor the escape
505
+ next $&.slice 1, $&.length if $&.start_with? RS
506
+ extconf = extension.config
507
+ if $~.names.empty?
508
+ target, content = $1, $2
509
+ else
510
+ target, content = ($~[:target] rescue nil), ($~[:content] rescue nil)
511
+ end
512
+ attributes = (attributes = extconf[:default_attrs]) ? attributes.merge : {}
513
+ if content.nil_or_empty?
514
+ attributes['text'] = content if content && extconf[:content_model] != :attributes
515
+ else
516
+ content = unescape_bracketed_text content
517
+ # QUESTION should we store the unparsed attrlist in the attrlist key?
518
+ if extconf[:content_model] == :attributes
519
+ parse_attributes content, extconf[:pos_attrs] || [], into: attributes
520
+ else
521
+ attributes['text'] = content
522
+ end
523
+ end
524
+ # NOTE for convenience, map content (unparsed attrlist) to target when format is short
525
+ target ||= extconf[:format] == :short ? content : target
526
+ if (Inline === (replacement = extension.process_method[self, target, attributes]))
527
+ if (inline_subs = replacement.attributes.delete 'subs')
528
+ replacement.text = apply_subs replacement.text, (expand_subs inline_subs)
529
+ end
530
+ replacement.convert
531
+ elsif replacement
532
+ logger.info %(expected substitution value for custom inline macro to be of type Inline; got #{replacement.class}: #{$&})
533
+ replacement
534
+ else
535
+ ''
536
+ end
537
+ end
538
+ end
539
+ end
540
+
496
541
  if doc_attrs.key? 'experimental'
497
542
  if found_macroish_short && ((text.include? 'kbd:') || (text.include? 'btn:'))
498
543
  text = text.gsub InlineKbdBtnMacroRx do
@@ -509,7 +554,7 @@ module Substitutors
509
554
  # NOTE handle special case where keys ends with delimiter (e.g., Ctrl++ or Ctrl,,)
510
555
  if keys.end_with? delim
511
556
  keys = (keys.chop.split delim, -1).map {|key| key.strip }
512
- keys[-1] = %(#{keys[-1]}#{delim})
557
+ keys[-1] += delim
513
558
  else
514
559
  keys = keys.split(delim).map {|key| key.strip }
515
560
  end
@@ -557,39 +602,6 @@ module Substitutors
557
602
  end
558
603
  end
559
604
 
560
- # FIXME this location is somewhat arbitrary, probably need to be able to control ordering
561
- # TODO this handling needs some cleanup
562
- if (extensions = doc.extensions) && extensions.inline_macros? # && found_macroish
563
- extensions.inline_macros.each do |extension|
564
- text = text.gsub extension.instance.regexp do
565
- # honor the escape
566
- next $&.slice 1, $&.length if $&.start_with? RS
567
- extconf = extension.config
568
- if $~.names.empty?
569
- target, content = $1, $2
570
- else
571
- target, content = ($~[:target] rescue nil), ($~[:content] rescue nil)
572
- end
573
- attributes = (attributes = extconf[:default_attrs]) ? attributes.dup : {}
574
- if content.nil_or_empty?
575
- attributes['text'] = content if content && extconf[:content_model] != :attributes
576
- else
577
- content = unescape_bracketed_text content
578
- # QUESTION should we store the unparsed attrlist in the attrlist key?
579
- if extconf[:content_model] == :attributes
580
- parse_attributes content, extconf[:pos_attrs] || [], into: attributes
581
- else
582
- attributes['text'] = content
583
- end
584
- end
585
- # NOTE for convenience, map content (unparsed attrlist) to target when format is short
586
- target ||= extconf[:format] == :short ? content : target
587
- replacement = extension.process_method[self, target, attributes]
588
- Inline === replacement ? replacement.convert : replacement
589
- end
590
- end
591
- end
592
-
593
605
  if found_macroish && ((text.include? 'image:') || (text.include? 'icon:'))
594
606
  # image:filename.png[Alt Text]
595
607
  text = text.gsub InlineImageMacroRx do
@@ -625,7 +637,7 @@ module Substitutors
625
637
 
626
638
  # indexterm:[Tigers,Big cats]
627
639
  terms = split_simple_csv normalize_string $2, true
628
- doc.register :indexterms, terms
640
+ #doc.register :indexterms, terms
629
641
  (Inline.new self, :indexterm, nil, attributes: { 'terms' => terms }).convert
630
642
  when 'indexterm2'
631
643
  # honor the escape
@@ -633,7 +645,7 @@ module Substitutors
633
645
 
634
646
  # indexterm2:[Tigers]
635
647
  term = normalize_string $2, true
636
- doc.register :indexterms, [term]
648
+ #doc.register :indexterms, [term]
637
649
  (Inline.new self, :indexterm, term, type: :visible).convert
638
650
  else
639
651
  text = $3
@@ -661,12 +673,12 @@ module Substitutors
661
673
  if visible
662
674
  # ((Tigers))
663
675
  term = normalize_string text
664
- doc.register :indexterms, [term]
676
+ #doc.register :indexterms, [term]
665
677
  subbed_term = (Inline.new self, :indexterm, term, type: :visible).convert
666
678
  else
667
679
  # (((Tigers,Big cats)))
668
680
  terms = split_simple_csv(normalize_string text)
669
- doc.register :indexterms, terms
681
+ #doc.register :indexterms, terms
670
682
  subbed_term = (Inline.new self, :indexterm, nil, attributes: { 'terms' => terms }).convert
671
683
  end
672
684
  before ? %(#{before}#{subbed_term}#{after}) : subbed_term
@@ -771,15 +783,14 @@ module Substitutors
771
783
  end
772
784
  end
773
785
 
774
- if found_macroish && ((text.include? 'link:') || (text.include? 'mailto:'))
786
+ if found_macroish && ((text.include? 'link:') || (text.include? 'ilto:'))
775
787
  # inline link macros, link:target[text]
776
788
  text = text.gsub InlineLinkMacroRx do
777
789
  # honor the escape
778
790
  if $&.start_with? RS
779
791
  next $&.slice 1, $&.length
780
792
  elsif (mailto = $1)
781
- target = %(mailto:#{$2})
782
- mailto_text = $2
793
+ target = 'mailto:' + (mailto_text = $2)
783
794
  else
784
795
  target = $2
785
796
  end
@@ -854,7 +865,7 @@ module Substitutors
854
865
  # honor the escape
855
866
  next $1 == RS ? ($&.slice 1, $&.length) : $& if $1
856
867
 
857
- target = %(mailto:#{$&})
868
+ target = 'mailto:' + $&
858
869
  # QUESTION should this be registered as an e-mail address?
859
870
  doc.register(:links, target)
860
871
 
@@ -862,65 +873,11 @@ module Substitutors
862
873
  end
863
874
  end
864
875
 
865
- if found_macroish && (text.include? 'tnote')
866
- text = text.gsub InlineFootnoteMacroRx do
867
- # honor the escape
868
- next $&.slice 1, $&.length if $&.start_with? RS
869
-
870
- # footnoteref
871
- if $1
872
- if $3
873
- id, text = $3.split ',', 2
874
- logger.warn %(found deprecated footnoteref macro: #{$&}; use footnote macro with target instead) unless doc.compat_mode
875
- else
876
- next $&
877
- end
878
- # footnote
879
- else
880
- id = $2
881
- text = $3
882
- end
883
-
884
- if id
885
- if text
886
- # REVIEW it's a dirty job, but somebody's gotta do it
887
- text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string text, true)))
888
- index = doc.counter('footnote-number')
889
- doc.register(:footnotes, Document::Footnote.new(index, id, text))
890
- type, target = :ref, nil
891
- else
892
- if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
893
- index, text = footnote.index, footnote.text
894
- else
895
- logger.warn %(invalid footnote reference: #{id})
896
- index, text = nil, id
897
- end
898
- type, target, id = :xref, id, nil
899
- end
900
- elsif text
901
- # REVIEW it's a dirty job, but somebody's gotta do it
902
- text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string text, true)))
903
- index = doc.counter('footnote-number')
904
- doc.register(:footnotes, Document::Footnote.new(index, id, text))
905
- type = target = nil
906
- else
907
- next $&
908
- end
909
- Inline.new(self, :footnote, text, attributes: { 'index' => index }, id: id, target: target, type: type).convert
910
- end
911
- end
912
-
913
- sub_inline_xrefs(sub_inline_anchors(text, found), found)
914
- end
915
-
916
- # Internal: Substitute normal and bibliographic anchors
917
- def sub_inline_anchors(text, found = nil)
918
- if @context == :list_item && @parent.style == 'bibliography'
876
+ if found_square_bracket && @context == :list_item && @parent.style == 'bibliography'
919
877
  text = text.sub(InlineBiblioAnchorRx) { (Inline.new self, :anchor, $2, type: :bibref, id: $1).convert }
920
878
  end
921
879
 
922
- if ((!found || found[:square_bracket]) && text.include?('[[')) ||
923
- ((!found || found[:macroish]) && text.include?('or:'))
880
+ if (found_square_bracket && text.include?('[[')) || (found_macroish && text.include?('or:'))
924
881
  text = text.gsub InlineAnchorRx do
925
882
  # honor the escape
926
883
  next $&.slice 1, $&.length if $1
@@ -938,17 +895,13 @@ module Substitutors
938
895
  end
939
896
  end
940
897
 
941
- text
942
- end
943
-
944
- # Internal: Substitute cross reference links
945
- def sub_inline_xrefs(content, found = nil)
946
- if ((found ? found[:macroish] : (content.include? '[')) && (content.include? 'xref:')) || ((content.include? '&') && (content.include? 'lt;&'))
947
- content = content.gsub InlineXrefMacroRx do
898
+ #if (text.include? ';&l') || (found_macroish && (text.include? 'xref:'))
899
+ if ((text.include? '&') && (text.include? ';&l')) || (found_macroish && (text.include? 'xref:'))
900
+ text = text.gsub InlineXrefMacroRx do
948
901
  # honor the escape
949
902
  next $&.slice 1, $&.length if $&.start_with? RS
950
903
 
951
- attrs, doc = {}, @document
904
+ attrs = {}
952
905
  if (refid = $1)
953
906
  refid, text = refid.split ',', 2
954
907
  text = text.lstrip if text
@@ -994,14 +947,14 @@ module Substitutors
994
947
  # handles: #id
995
948
  if target
996
949
  refid = fragment
997
- logger.debug %(possible invalid reference: #{refid}) if logger.debug? && !doc.catalog[:refs][refid]
950
+ logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
998
951
  elsif path
999
952
  # handles: path#, path#id, path.adoc#, path.adoc#id, or path.adoc (xref macro only)
1000
953
  # the referenced path is the current document, or its contents have been included in the current document
1001
954
  if src2src && (doc.attributes['docname'] == path || doc.catalog[:includes][path])
1002
955
  if fragment
1003
956
  refid, path, target = fragment, nil, %(##{fragment})
1004
- logger.debug %(possible invalid reference: #{refid}) if logger.debug? && !doc.catalog[:refs][refid]
957
+ logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
1005
958
  else
1006
959
  refid, path, target = nil, nil, '#'
1007
960
  end
@@ -1016,7 +969,7 @@ module Substitutors
1016
969
  # handles: id (in compat mode or when natural xrefs are disabled)
1017
970
  elsif doc.compat_mode || !Compliance.natural_xrefs
1018
971
  refid, target = fragment, %(##{fragment})
1019
- logger.debug %(possible invalid reference: #{refid}) if logger.debug? && doc.catalog[:refs][refid]
972
+ logger.info %(possible invalid reference: #{refid}) if logger.info? && doc.catalog[:refs][refid]
1020
973
  # handles: id
1021
974
  elsif doc.catalog[:refs][fragment]
1022
975
  refid, target = fragment, %(##{fragment})
@@ -1026,14 +979,62 @@ module Substitutors
1026
979
  fragment, target = refid, %(##{refid})
1027
980
  else
1028
981
  refid, target = fragment, %(##{fragment})
1029
- logger.debug %(possible invalid reference: #{refid}) if logger.debug?
982
+ logger.info %(possible invalid reference: #{refid}) if logger.info?
1030
983
  end
1031
- attrs['path'], attrs['fragment'], attrs['refid'] = path, fragment, refid
984
+ attrs['path'] = path
985
+ attrs['fragment'] = fragment
986
+ attrs['refid'] = refid
1032
987
  Inline.new(self, :anchor, text, type: :xref, target: target, attributes: attrs).convert
1033
988
  end
1034
989
  end
1035
990
 
1036
- content
991
+ if found_macroish && (text.include? 'tnote')
992
+ text = text.gsub InlineFootnoteMacroRx do
993
+ # honor the escape
994
+ next $&.slice 1, $&.length if $&.start_with? RS
995
+
996
+ # footnoteref
997
+ if $1
998
+ if $3
999
+ id, text = $3.split ',', 2
1000
+ logger.warn %(found deprecated footnoteref macro: #{$&}; use footnote macro with target instead) unless doc.compat_mode
1001
+ else
1002
+ next $&
1003
+ end
1004
+ # footnote
1005
+ else
1006
+ id = $2
1007
+ text = $3
1008
+ end
1009
+
1010
+ if id
1011
+ if text
1012
+ text = restore_passthroughs(normalize_string text, true)
1013
+ index = doc.counter('footnote-number')
1014
+ doc.register(:footnotes, Document::Footnote.new(index, id, text))
1015
+ type, target = :ref, nil
1016
+ else
1017
+ if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
1018
+ index, text = footnote.index, footnote.text
1019
+ else
1020
+ logger.warn %(invalid footnote reference: #{id})
1021
+ index, text = nil, id
1022
+ end
1023
+ type, target, id = :xref, id, nil
1024
+ end
1025
+ elsif text
1026
+ text = restore_passthroughs(normalize_string text, true)
1027
+ index = doc.counter('footnote-number')
1028
+ doc.register(:footnotes, Document::Footnote.new(index, id, text))
1029
+ type = target = nil
1030
+ else
1031
+ next $&
1032
+ end
1033
+ Inline.new(self, :footnote, text, attributes: { 'index' => index }, id: id, target: target, type: type).convert
1034
+ end
1035
+ end
1036
+
1037
+ text
1037
1038
  end
1038
1039
 
1039
1040
  # Public: Substitute callout source references
@@ -1473,7 +1474,7 @@ module Substitutors
1473
1474
  # Internal: Inserts text into a formatted text enclosure; used by xreftext
1474
1475
  alias sub_placeholder sprintf unless RUBY_ENGINE == 'opal'
1475
1476
 
1476
- # Internal: Lock-in the substitutions for this block
1477
+ # Internal: Commit the requested substitutions to this block.
1477
1478
  #
1478
1479
  # Looks for an attribute named "subs". If present, resolves substitutions
1479
1480
  # from the value of that attribute and assigns them to the subs property on
@@ -1482,7 +1483,7 @@ module Substitutors
1482
1483
  # the content model of the block.
1483
1484
  #
1484
1485
  # Returns The Array of resolved substitutions now assigned to this block
1485
- def lock_in_subs
1486
+ def commit_subs
1486
1487
  unless (default_subs = @default_subs)
1487
1488
  case @content_model
1488
1489
  when :simple