asciidoctor 2.0.0.rc.3 → 2.0.0
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 +13 -4
- data/README-de.adoc +3 -2
- data/README-fr.adoc +3 -2
- data/README-jp.adoc +3 -2
- data/README-zh_CN.adoc +3 -2
- data/README.adoc +3 -2
- data/data/locale/attributes-de.adoc +4 -3
- data/data/reference/syntax.adoc +8 -2
- data/lib/asciidoctor.rb +11 -1
- data/lib/asciidoctor/abstract_node.rb +4 -5
- data/lib/asciidoctor/attribute_list.rb +16 -16
- data/lib/asciidoctor/converter/docbook5.rb +36 -21
- data/lib/asciidoctor/converter/html5.rb +2 -2
- data/lib/asciidoctor/extensions.rb +27 -11
- data/lib/asciidoctor/parser.rb +65 -73
- data/lib/asciidoctor/substitutors.rb +561 -549
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +4 -4
- data/man/asciidoctor.adoc +1 -1
- metadata +4 -4
@@ -101,7 +101,7 @@ class Converter::Html5Converter < Converter::Base
|
|
101
101
|
result << %(<meta name="application-name" content="#{node.attr 'app-name'}"#{slash}>) if node.attr? 'app-name'
|
102
102
|
result << %(<meta name="description" content="#{node.attr 'description'}"#{slash}>) if node.attr? 'description'
|
103
103
|
result << %(<meta name="keywords" content="#{node.attr 'keywords'}"#{slash}>) if node.attr? 'keywords'
|
104
|
-
result << %(<meta name="author" content="#{((authors = node.attr 'authors').include? '<') ? (authors.gsub XmlSanitizeRx, '') : authors}"#{slash}>) if node.attr? 'authors'
|
104
|
+
result << %(<meta name="author" content="#{((authors = node.sub_replacements node.attr 'authors').include? '<') ? (authors.gsub XmlSanitizeRx, '') : authors}"#{slash}>) if node.attr? 'authors'
|
105
105
|
result << %(<meta name="copyright" content="#{node.attr 'copyright'}"#{slash}>) if node.attr? 'copyright'
|
106
106
|
if node.attr? 'favicon'
|
107
107
|
if (icon_href = node.attr 'favicon').empty?
|
@@ -184,7 +184,7 @@ class Converter::Html5Converter < Converter::Base
|
|
184
184
|
details = []
|
185
185
|
idx = 1
|
186
186
|
node.authors.each do |author|
|
187
|
-
details << %(<span id="author#{idx > 1 ? idx : ''}" class="author">#{author.name}</span>#{br})
|
187
|
+
details << %(<span id="author#{idx > 1 ? idx : ''}" class="author">#{node.sub_replacements author.name}</span>#{br})
|
188
188
|
details << %(<span id="email#{idx > 1 ? idx : ''}" class="email">#{node.sub_macros author.email}</span>#{br}) if author.email
|
189
189
|
idx += 1
|
190
190
|
end
|
@@ -143,7 +143,9 @@ module Extensions
|
|
143
143
|
else
|
144
144
|
sect.numbered = true if opts.fetch :numbered, (book && (doc.attr? 'partnums'))
|
145
145
|
end
|
146
|
-
|
146
|
+
if (id = attrs['id']) == false
|
147
|
+
attrs.delete 'id'
|
148
|
+
else
|
147
149
|
sect.id = attrs['id'] = id || ((doc.attr? 'sectids') ? (Section.generate_id sect.title, doc) : nil)
|
148
150
|
end
|
149
151
|
sect.update_attributes attrs
|
@@ -211,12 +213,25 @@ module Extensions
|
|
211
213
|
# QUESTION is parse_content the right method name? should we wrap in open block automatically?
|
212
214
|
def parse_content parent, content, attributes = nil
|
213
215
|
reader = Reader === content ? content : (Reader.new content)
|
214
|
-
|
215
|
-
reader.has_more_lines?
|
216
|
-
end
|
216
|
+
Parser.parse_blocks reader, parent, attributes
|
217
217
|
parent
|
218
218
|
end
|
219
219
|
|
220
|
+
# Public: Parses the attrlist String into a Hash of attributes
|
221
|
+
#
|
222
|
+
# block - the current AbstractBlock or the parent AbstractBlock if there is no current block (used for applying subs)
|
223
|
+
# attrlist - the list of attributes as a String
|
224
|
+
# opts - an optional Hash of options to control processing:
|
225
|
+
# :positional_attributes - an Array of attribute names to map positional arguments to (optional, default: false)
|
226
|
+
# :sub_attributes - enables attribute substitution on the attrlist argument (optional, default: false)
|
227
|
+
#
|
228
|
+
# Returns a Hash of parsed attributes
|
229
|
+
def parse_attributes block, attrlist, opts = {}
|
230
|
+
return {} if attrlist ? attrlist.empty? : true
|
231
|
+
attrlist = block.sub_attributes attrlist if opts[:sub_attributes] && (attrlist.include? ATTR_REF_HEAD)
|
232
|
+
(AttributeList.new attrlist).parse (opts[:positional_attributes] || [])
|
233
|
+
end
|
234
|
+
|
220
235
|
# TODO fill out remaining methods
|
221
236
|
[
|
222
237
|
[:create_paragraph, :create_block, :paragraph],
|
@@ -294,16 +309,17 @@ module Extensions
|
|
294
309
|
alias parse_content_as content_model
|
295
310
|
|
296
311
|
def positional_attributes *value
|
297
|
-
option :
|
312
|
+
option :positional_attrs, value.flatten
|
298
313
|
end
|
299
314
|
alias name_positional_attributes positional_attributes
|
300
315
|
# NOTE positional_attrs alias is deprecated
|
301
316
|
alias positional_attrs positional_attributes
|
302
317
|
|
303
|
-
def
|
318
|
+
def default_attributes value
|
304
319
|
option :default_attrs, value
|
305
320
|
end
|
306
|
-
alias
|
321
|
+
# NOTE default_attrs alias is deprecated
|
322
|
+
alias default_attr default_attributes
|
307
323
|
|
308
324
|
def resolve_attributes *args
|
309
325
|
# NOTE assume true as default value; rewrap single-argument string or symbol
|
@@ -312,7 +328,7 @@ module Extensions
|
|
312
328
|
end unless args.size > 1
|
313
329
|
case args
|
314
330
|
when true
|
315
|
-
option :
|
331
|
+
option :positional_attrs, []
|
316
332
|
option :default_attrs, {}
|
317
333
|
when ::Array
|
318
334
|
names, defaults = [], {}
|
@@ -333,7 +349,7 @@ module Extensions
|
|
333
349
|
names << arg
|
334
350
|
end
|
335
351
|
end
|
336
|
-
option :
|
352
|
+
option :positional_attrs, names.compact
|
337
353
|
option :default_attrs, defaults
|
338
354
|
when ::Hash
|
339
355
|
names, defaults = [], {}
|
@@ -345,7 +361,7 @@ module Extensions
|
|
345
361
|
end
|
346
362
|
defaults[name] = val if val
|
347
363
|
end
|
348
|
-
option :
|
364
|
+
option :positional_attrs, names.compact
|
349
365
|
option :default_attrs, defaults
|
350
366
|
else
|
351
367
|
raise ::ArgumentError, %(unsupported attributes specification for macro: #{args.inspect})
|
@@ -507,7 +523,7 @@ module Extensions
|
|
507
523
|
# * :named - The name of the block (required: true)
|
508
524
|
# * :contexts - The blocks contexts on which this style can be used (default: [:paragraph, :open]
|
509
525
|
# * :content_model - The structure of the content supported in this block (default: :compound)
|
510
|
-
# * :
|
526
|
+
# * :positional_attrs - A list of attribute names used to map positional attributes (default: nil)
|
511
527
|
# * :default_attrs - A hash of attribute names and values used to seed the attributes hash (default: nil)
|
512
528
|
# * ...
|
513
529
|
#
|
data/lib/asciidoctor/parser.rb
CHANGED
@@ -41,6 +41,8 @@ class Parser
|
|
41
41
|
|
42
42
|
NoOp = nil
|
43
43
|
|
44
|
+
AuthorKeys = ['author', 'authorinitials', 'firstname', 'middlename', 'lastname', 'email']
|
45
|
+
|
44
46
|
# Internal: A Hash mapping horizontal alignment abbreviations to alignments
|
45
47
|
# that can be applied to a table cell (or to all cells in a column)
|
46
48
|
TableCellHorzAlignments = {
|
@@ -631,7 +633,7 @@ class Parser
|
|
631
633
|
end
|
632
634
|
end
|
633
635
|
if extension.config[:content_model] == :attributes
|
634
|
-
document.parse_attributes content, extension.config[:
|
636
|
+
document.parse_attributes content, extension.config[:positional_attrs] || [], sub_input: true, into: attributes if content
|
635
637
|
else
|
636
638
|
attributes['text'] = content || ''
|
637
639
|
end
|
@@ -786,40 +788,27 @@ class Parser
|
|
786
788
|
|
787
789
|
# either delimited block or styled paragraph
|
788
790
|
unless block
|
789
|
-
# abstract and partintro should be handled by open block
|
790
|
-
# FIXME kind of hackish...need to sort out how to generalize this
|
791
|
-
block_context = :open if block_context == :abstract || block_context == :partintro
|
792
|
-
|
793
791
|
case block_context
|
794
|
-
when :
|
795
|
-
attributes[
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
attributes['language'] = doc_attrs['source-language']
|
814
|
-
end unless attributes.key? 'language'
|
815
|
-
if attributes['linenums-option'] || doc_attrs['source-linenums-option']
|
816
|
-
attributes['linenums'] = ''
|
817
|
-
end unless attributes.key? 'linenums'
|
818
|
-
if doc_attrs.key? 'source-indent'
|
819
|
-
attributes['indent'] = doc_attrs['source-indent']
|
820
|
-
end unless attributes.key? 'indent'
|
792
|
+
when :listing, :source
|
793
|
+
if block_context == :source || (!attributes[1] && (language = attributes[2] || doc_attrs['source-language']))
|
794
|
+
if language
|
795
|
+
attributes['style'] = 'source'
|
796
|
+
attributes['language'] = language
|
797
|
+
AttributeList.rekey attributes, [nil, nil, 'linenums']
|
798
|
+
else
|
799
|
+
AttributeList.rekey attributes, [nil, 'language', 'linenums']
|
800
|
+
if doc_attrs.key? 'source-language'
|
801
|
+
attributes['language'] = doc_attrs['source-language']
|
802
|
+
end unless attributes.key? 'language'
|
803
|
+
end
|
804
|
+
if attributes['linenums-option'] || doc_attrs['source-linenums-option']
|
805
|
+
attributes['linenums'] = ''
|
806
|
+
end unless attributes.key? 'linenums'
|
807
|
+
if doc_attrs.key? 'source-indent'
|
808
|
+
attributes['indent'] = doc_attrs['source-indent']
|
809
|
+
end unless attributes.key? 'indent'
|
810
|
+
end
|
821
811
|
block = build_block(:listing, :verbatim, terminator, parent, reader, attributes)
|
822
|
-
|
823
812
|
when :fenced_code
|
824
813
|
attributes['style'] = 'source'
|
825
814
|
if (ll = this_line.length) > 3
|
@@ -847,17 +836,6 @@ class Parser
|
|
847
836
|
end unless attributes.key? 'indent'
|
848
837
|
terminator = terminator.slice 0, 3
|
849
838
|
block = build_block(:listing, :verbatim, terminator, parent, reader, attributes)
|
850
|
-
|
851
|
-
when :pass
|
852
|
-
block = build_block(block_context, :raw, terminator, parent, reader, attributes)
|
853
|
-
|
854
|
-
when :stem, :latexmath, :asciimath
|
855
|
-
attributes['style'] = STEM_TYPE_ALIASES[attributes[2] || doc_attrs['stem']] if block_context == :stem
|
856
|
-
block = build_block(:stem, :raw, terminator, parent, reader, attributes)
|
857
|
-
|
858
|
-
when :open, :sidebar
|
859
|
-
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
|
860
|
-
|
861
839
|
when :table
|
862
840
|
block_cursor = reader.cursor
|
863
841
|
block_reader = Reader.new reader.read_lines_until(terminator: terminator, skip_line_comments: true, context: :table, cursor: :at_mark), block_cursor
|
@@ -867,16 +845,35 @@ class Parser
|
|
867
845
|
attributes['format'] ||= (terminator.start_with? ',') ? 'csv' : 'dsv'
|
868
846
|
end
|
869
847
|
block = parse_table(block_reader, parent, attributes)
|
870
|
-
|
848
|
+
when :sidebar
|
849
|
+
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
|
850
|
+
when :admonition
|
851
|
+
attributes['name'] = admonition_name = style.downcase
|
852
|
+
attributes['textlabel'] = (attributes.delete 'caption') || doc_attrs[%(#{admonition_name}-caption)]
|
853
|
+
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
|
854
|
+
when :open, :abstract, :partintro
|
855
|
+
block = build_block(:open, :compound, terminator, parent, reader, attributes)
|
856
|
+
when :literal
|
857
|
+
block = build_block(block_context, :verbatim, terminator, parent, reader, attributes)
|
858
|
+
when :example
|
859
|
+
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
|
871
860
|
when :quote, :verse
|
872
861
|
AttributeList.rekey(attributes, [nil, 'attribution', 'citetitle'])
|
873
862
|
block = build_block(block_context, (block_context == :verse ? :verbatim : :compound), terminator, parent, reader, attributes)
|
874
|
-
|
863
|
+
when :stem, :latexmath, :asciimath
|
864
|
+
attributes['style'] = STEM_TYPE_ALIASES[attributes[2] || doc_attrs['stem']] if block_context == :stem
|
865
|
+
block = build_block(:stem, :raw, terminator, parent, reader, attributes)
|
866
|
+
when :pass
|
867
|
+
block = build_block(block_context, :raw, terminator, parent, reader, attributes)
|
868
|
+
when :comment
|
869
|
+
build_block(block_context, :skip, terminator, parent, reader, attributes)
|
870
|
+
attributes.clear
|
871
|
+
return
|
875
872
|
else
|
876
|
-
if block_extensions && (extension = extensions.registered_for_block?
|
873
|
+
if block_extensions && (extension = extensions.registered_for_block? block_context, cloaked_context)
|
877
874
|
unless (content_model = extension.config[:content_model]) == :skip
|
878
|
-
unless (
|
879
|
-
AttributeList.rekey(attributes, [nil] +
|
875
|
+
unless (positional_attrs = extension.config[:positional_attrs] || []).empty?
|
876
|
+
AttributeList.rekey(attributes, [nil] + positional_attrs)
|
880
877
|
end
|
881
878
|
if (default_attrs = extension.config[:default_attrs])
|
882
879
|
default_attrs.each {|k, v| attributes[k] ||= v }
|
@@ -884,8 +881,7 @@ class Parser
|
|
884
881
|
# QUESTION should we clone the extension for each cloaked context and set in config?
|
885
882
|
attributes['cloaked-context'] = cloaked_context
|
886
883
|
end
|
887
|
-
block = build_block block_context, content_model, terminator, parent, reader, attributes, extension: extension
|
888
|
-
unless block
|
884
|
+
unless (block = build_block block_context, content_model, terminator, parent, reader, attributes, extension: extension)
|
889
885
|
attributes.clear
|
890
886
|
return
|
891
887
|
end
|
@@ -909,7 +905,7 @@ class Parser
|
|
909
905
|
end
|
910
906
|
end
|
911
907
|
# FIXME remove the need for this update!
|
912
|
-
block.attributes
|
908
|
+
block.update_attributes attributes unless attributes.empty?
|
913
909
|
block.commit_subs
|
914
910
|
|
915
911
|
#if doc_attrs.key? :pending_attribute_entries
|
@@ -1072,9 +1068,13 @@ class Parser
|
|
1072
1068
|
# parent - The parent Block to which to attach the parsed blocks
|
1073
1069
|
#
|
1074
1070
|
# Returns nothing.
|
1075
|
-
def self.parse_blocks(reader, parent)
|
1076
|
-
|
1071
|
+
def self.parse_blocks(reader, parent, attributes = nil)
|
1072
|
+
if attributes
|
1073
|
+
while ((block = next_block reader, parent, attributes.merge) && parent.blocks << block) || reader.has_more_lines?; end
|
1074
|
+
else
|
1075
|
+
while ((block = next_block reader, parent) && parent.blocks << block) || reader.has_more_lines?; end
|
1077
1076
|
end
|
1077
|
+
nil
|
1078
1078
|
end
|
1079
1079
|
|
1080
1080
|
# Internal: Parse and construct an ordered or unordered list at the current position of the Reader
|
@@ -1127,8 +1127,7 @@ class Parser
|
|
1127
1127
|
# doc - The document to which the node belongs; computed from node if not specified
|
1128
1128
|
#
|
1129
1129
|
# Returns nothing
|
1130
|
-
def self.catalog_inline_anchor id, reftext, node, location, doc =
|
1131
|
-
doc = node.document unless doc
|
1130
|
+
def self.catalog_inline_anchor id, reftext, node, location, doc = node.document
|
1132
1131
|
reftext = doc.sub_attributes reftext if reftext && (reftext.include? ATTR_REF_HEAD)
|
1133
1132
|
unless doc.register :refs, [id, (Inline.new node, :anchor, reftext, type: :ref, id: id)]
|
1134
1133
|
location = location.cursor if Reader === location
|
@@ -1318,6 +1317,9 @@ class Parser
|
|
1318
1317
|
end
|
1319
1318
|
else # :colist
|
1320
1319
|
list_item.marker = sibling_trait
|
1320
|
+
if item_text.start_with?('[[') && LeadingInlineAnchorRx =~ item_text
|
1321
|
+
catalog_inline_anchor $1, $2, list_item, reader
|
1322
|
+
end
|
1321
1323
|
end
|
1322
1324
|
end
|
1323
1325
|
|
@@ -1789,9 +1791,8 @@ class Parser
|
|
1789
1791
|
if document
|
1790
1792
|
# apply header subs and assign to document
|
1791
1793
|
author_metadata.each do |key, val|
|
1792
|
-
|
1793
|
-
|
1794
|
-
end
|
1794
|
+
# NOTE the attributes substitution only applies for the email record
|
1795
|
+
doc_attrs[key] = ::String === val ? (document.apply_header_subs val) : val unless doc_attrs.key? key
|
1795
1796
|
end
|
1796
1797
|
|
1797
1798
|
implicit_author = doc_attrs['author']
|
@@ -1915,20 +1916,13 @@ class Parser
|
|
1915
1916
|
def self.process_authors author_line, names_only = false, multiple = true
|
1916
1917
|
author_metadata = {}
|
1917
1918
|
author_idx = 0
|
1918
|
-
|
1919
|
-
author_entries = multiple ? (author_line.split ';').map {|it| it.strip } : [*author_line]
|
1920
|
-
author_entries.each do |author_entry|
|
1919
|
+
(multiple && (author_line.include? ';') ? (author_line.split AuthorDelimiterRx) : [*author_line]).each do |author_entry|
|
1921
1920
|
next if author_entry.empty?
|
1922
|
-
author_idx += 1
|
1923
1921
|
key_map = {}
|
1924
|
-
if author_idx == 1
|
1925
|
-
|
1926
|
-
key_map[key.to_sym] = key
|
1927
|
-
end
|
1922
|
+
if (author_idx += 1) == 1
|
1923
|
+
AuthorKeys.each {|key| key_map[key.to_sym] = key }
|
1928
1924
|
else
|
1929
|
-
|
1930
|
-
key_map[key.to_sym] = %(#{key}_#{author_idx})
|
1931
|
-
end
|
1925
|
+
AuthorKeys.each {|key| key_map[key.to_sym] = %(#{key}_#{author_idx}) }
|
1932
1926
|
end
|
1933
1927
|
|
1934
1928
|
if names_only # when parsing an attribute value
|
@@ -1972,9 +1966,7 @@ class Parser
|
|
1972
1966
|
else
|
1973
1967
|
# only assign the _1 attributes once we see the second author
|
1974
1968
|
if author_idx == 2
|
1975
|
-
|
1976
|
-
author_metadata[%(#{key}_1)] = author_metadata[key] if author_metadata.key? key
|
1977
|
-
end
|
1969
|
+
AuthorKeys.each {|key| author_metadata[%(#{key}_1)] = author_metadata[key] if author_metadata.key? key }
|
1978
1970
|
end
|
1979
1971
|
author_metadata['authors'] = %(#{author_metadata['authors']}, #{author_metadata[key_map[:author]]})
|
1980
1972
|
end
|
@@ -80,12 +80,11 @@ module Substitutors
|
|
80
80
|
end
|
81
81
|
|
82
82
|
if subs.include? :macros
|
83
|
-
text
|
84
|
-
|
85
|
-
passthrus =
|
86
|
-
else
|
83
|
+
text = extract_passthroughs text
|
84
|
+
unless @passthroughs.empty?
|
85
|
+
passthrus = @passthroughs
|
87
86
|
# NOTE placeholders can move around, so we can only clear in the outermost substitution call
|
88
|
-
@passthroughs_locked ||= (
|
87
|
+
@passthroughs_locked ||= (clear_passthrus = true)
|
89
88
|
end
|
90
89
|
end
|
91
90
|
|
@@ -114,7 +113,7 @@ module Substitutors
|
|
114
113
|
|
115
114
|
if passthrus
|
116
115
|
text = restore_passthroughs text
|
117
|
-
if
|
116
|
+
if clear_passthrus
|
118
117
|
passthrus.clear
|
119
118
|
@passthroughs_locked = nil
|
120
119
|
end
|
@@ -134,11 +133,20 @@ module Substitutors
|
|
134
133
|
apply_subs text, NORMAL_SUBS
|
135
134
|
end
|
136
135
|
|
136
|
+
# Public: Apply substitutions for header metadata and attribute assignments
|
137
|
+
#
|
138
|
+
# text - String containing the text process
|
139
|
+
#
|
140
|
+
# Returns A String with header substitutions performed
|
141
|
+
def apply_header_subs text
|
142
|
+
apply_subs text, HEADER_SUBS
|
143
|
+
end
|
144
|
+
|
137
145
|
# Public: Apply substitutions for titles.
|
138
146
|
#
|
139
147
|
# title - The String title to process
|
140
148
|
#
|
141
|
-
#
|
149
|
+
# Returns A String with title substitutions performed
|
142
150
|
alias apply_title_subs apply_subs
|
143
151
|
|
144
152
|
# Public: Apply substitutions for reftext.
|
@@ -150,223 +158,13 @@ module Substitutors
|
|
150
158
|
apply_subs text, REFTEXT_SUBS
|
151
159
|
end
|
152
160
|
|
153
|
-
# Public: Apply substitutions for header metadata and attribute assignments
|
154
|
-
#
|
155
|
-
# text - String containing the text process
|
156
|
-
#
|
157
|
-
# returns - A String with header substitutions performed
|
158
|
-
def apply_header_subs(text)
|
159
|
-
apply_subs text, HEADER_SUBS
|
160
|
-
end
|
161
|
-
|
162
|
-
# Internal: Extract the passthrough text from the document for reinsertion after processing.
|
163
|
-
#
|
164
|
-
# text - The String from which to extract passthrough fragements
|
165
|
-
#
|
166
|
-
# returns - A tuple of the String text with passthrough regions substituted with placeholders and the passthroughs Hash
|
167
|
-
def extract_passthroughs(text)
|
168
|
-
compat_mode = @document.compat_mode
|
169
|
-
passthrus = @passthroughs
|
170
|
-
text = text.gsub InlinePassMacroRx do
|
171
|
-
preceding = ''
|
172
|
-
|
173
|
-
if (boundary = $4) # $$, ++, or +++
|
174
|
-
# skip ++ in compat mode, handled as normal quoted text
|
175
|
-
if compat_mode && boundary == '++'
|
176
|
-
content, _ = extract_passthroughs $5
|
177
|
-
next $2 ? %(#{$1}[#{$2}]#{$3}++#{content}++) : %(#{$1}#{$3}++#{content}++)
|
178
|
-
end
|
179
|
-
|
180
|
-
attributes = $2
|
181
|
-
escape_count = $3.length
|
182
|
-
content = $5
|
183
|
-
old_behavior = false
|
184
|
-
|
185
|
-
if attributes
|
186
|
-
if escape_count > 0
|
187
|
-
# NOTE we don't look for nested unconstrained pass macros
|
188
|
-
next %(#{$1}[#{attributes}]#{RS * (escape_count - 1)}#{boundary}#{$5}#{boundary})
|
189
|
-
elsif $1 == RS
|
190
|
-
preceding = %([#{attributes}])
|
191
|
-
attributes = nil
|
192
|
-
else
|
193
|
-
if boundary == '++' && (attributes.end_with? 'x-')
|
194
|
-
old_behavior = true
|
195
|
-
attributes = attributes.slice 0, attributes.length - 2
|
196
|
-
end
|
197
|
-
attributes = parse_quoted_text_attributes attributes
|
198
|
-
end
|
199
|
-
elsif escape_count > 0
|
200
|
-
# NOTE we don't look for nested unconstrained pass macros
|
201
|
-
next %(#{RS * (escape_count - 1)}#{boundary}#{$5}#{boundary})
|
202
|
-
end
|
203
|
-
subs = (boundary == '+++' ? [] : BASIC_SUBS)
|
204
|
-
|
205
|
-
if attributes
|
206
|
-
if old_behavior
|
207
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: NORMAL_SUBS, type: :monospaced, attributes: attributes }
|
208
|
-
else
|
209
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: :unquoted, attributes: attributes }
|
210
|
-
end
|
211
|
-
else
|
212
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs }
|
213
|
-
end
|
214
|
-
else # pass:[]
|
215
|
-
# NOTE we don't look for nested pass:[] macros
|
216
|
-
# honor the escape
|
217
|
-
next $&.slice 1, $&.length if $6 == RS
|
218
|
-
passthrus[passthru_key = passthrus.size] = { text: (unescape_brackets $8), subs: ($7 ? (resolve_pass_subs $7) : nil) }
|
219
|
-
end
|
220
|
-
|
221
|
-
%(#{preceding}#{PASS_START}#{passthru_key}#{PASS_END})
|
222
|
-
end if (text.include? '++') || (text.include? '$$') || (text.include? 'ss:')
|
223
|
-
|
224
|
-
pass_inline_char1, pass_inline_char2, pass_inline_rx = InlinePassRx[compat_mode]
|
225
|
-
text = text.gsub pass_inline_rx do
|
226
|
-
preceding = $1
|
227
|
-
attributes = $2
|
228
|
-
escape_mark = RS if (quoted_text = $3).start_with? RS
|
229
|
-
format_mark = $4
|
230
|
-
content = $5
|
231
|
-
|
232
|
-
if compat_mode
|
233
|
-
old_behavior = true
|
234
|
-
else
|
235
|
-
if (old_behavior = attributes && (attributes.end_with? 'x-'))
|
236
|
-
attributes = attributes.slice 0, attributes.length - 2
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
if attributes
|
241
|
-
if format_mark == '`' && !old_behavior
|
242
|
-
next extract_inner_passthrough content, %(#{preceding}[#{attributes}]#{escape_mark}), attributes
|
243
|
-
elsif escape_mark
|
244
|
-
# honor the escape of the formatting mark
|
245
|
-
next %(#{preceding}[#{attributes}]#{quoted_text.slice 1, quoted_text.length})
|
246
|
-
elsif preceding == RS
|
247
|
-
# honor the escape of the attributes
|
248
|
-
preceding = %([#{attributes}])
|
249
|
-
attributes = nil
|
250
|
-
else
|
251
|
-
attributes = parse_quoted_text_attributes attributes
|
252
|
-
end
|
253
|
-
elsif format_mark == '`' && !old_behavior
|
254
|
-
next extract_inner_passthrough content, %(#{preceding}#{escape_mark})
|
255
|
-
elsif escape_mark
|
256
|
-
# honor the escape of the formatting mark
|
257
|
-
next %(#{preceding}#{quoted_text.slice 1, quoted_text.length})
|
258
|
-
end
|
259
|
-
|
260
|
-
if compat_mode
|
261
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS, attributes: attributes, type: :monospaced }
|
262
|
-
elsif attributes
|
263
|
-
if old_behavior
|
264
|
-
subs = (format_mark == '`' ? BASIC_SUBS : NORMAL_SUBS)
|
265
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, attributes: attributes, type: :monospaced }
|
266
|
-
else
|
267
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS, attributes: attributes, type: :unquoted }
|
268
|
-
end
|
269
|
-
else
|
270
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS }
|
271
|
-
end
|
272
|
-
|
273
|
-
%(#{preceding}#{PASS_START}#{passthru_key}#{PASS_END})
|
274
|
-
end if (text.include? pass_inline_char1) || (pass_inline_char2 && (text.include? pass_inline_char2))
|
275
|
-
|
276
|
-
# NOTE we need to do the stem in a subsequent step to allow it to be escaped by the former
|
277
|
-
text = text.gsub InlineStemMacroRx do
|
278
|
-
# honor the escape
|
279
|
-
next $&.slice 1, $&.length if $&.start_with? RS
|
280
|
-
|
281
|
-
if (type = $1.to_sym) == :stem
|
282
|
-
type = STEM_TYPE_ALIASES[@document.attributes['stem']].to_sym
|
283
|
-
end
|
284
|
-
content = unescape_brackets $3
|
285
|
-
# NOTE for backwards compatibility with AsciiDoc Python, drop enclosing $ signs around latexmath
|
286
|
-
content = content.slice 1, content.length - 2 if type == :latexmath && (content.start_with? '$') && (content.end_with? '$')
|
287
|
-
subs = $2 ? (resolve_pass_subs $2) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
|
288
|
-
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: type }
|
289
|
-
%(#{PASS_START}#{passthru_key}#{PASS_END})
|
290
|
-
end if (text.include? ':') && ((text.include? 'stem:') || (text.include? 'math:'))
|
291
|
-
|
292
|
-
[text, passthrus]
|
293
|
-
end
|
294
|
-
|
295
|
-
# Internal: Extract nested single-plus passthrough; otherwise return unprocessed
|
296
|
-
def extract_inner_passthrough text, pre, attributes = nil
|
297
|
-
if (text.end_with? '+') && (text.start_with? '+', '\+') && SinglePlusInlinePassRx =~ text
|
298
|
-
if $1
|
299
|
-
%(#{pre}`+#{$2}+`)
|
300
|
-
else
|
301
|
-
@passthroughs[passthru_key = @passthroughs.size] = attributes ?
|
302
|
-
{ text: $2, subs: BASIC_SUBS, attributes: attributes, type: :unquoted } :
|
303
|
-
{ text: $2, subs: BASIC_SUBS }
|
304
|
-
%(#{pre}`#{PASS_START}#{passthru_key}#{PASS_END}`)
|
305
|
-
end
|
306
|
-
else
|
307
|
-
%(#{pre}`#{text}`)
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
# Internal: Restore the passthrough text by reinserting into the placeholder positions
|
312
|
-
#
|
313
|
-
# text - The String text into which to restore the passthrough text
|
314
|
-
#
|
315
|
-
# returns The String text with the passthrough text restored
|
316
|
-
def restore_passthroughs text
|
317
|
-
passthrus = @passthroughs
|
318
|
-
text.gsub PassSlotRx do
|
319
|
-
if (pass = passthrus[$1.to_i])
|
320
|
-
subbed_text = apply_subs(pass[:text], pass[:subs])
|
321
|
-
if (type = pass[:type])
|
322
|
-
if (attributes = pass[:attributes])
|
323
|
-
id = attributes.delete 'id'
|
324
|
-
end
|
325
|
-
subbed_text = Inline.new(self, :quoted, subbed_text, type: type, id: id, attributes: attributes).convert
|
326
|
-
end
|
327
|
-
subbed_text.include?(PASS_START) ? restore_passthroughs(subbed_text) : subbed_text
|
328
|
-
else
|
329
|
-
logger.error %(unresolved passthrough detected: #{text})
|
330
|
-
'??pass??'
|
331
|
-
end
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
# Public: Substitute quoted text (includes emphasis, strong, monospaced, etc.)
|
336
|
-
#
|
337
|
-
# text - The String text to process
|
338
|
-
#
|
339
|
-
# returns The converted [String] text
|
340
|
-
def sub_quotes text
|
341
|
-
if QuotedTextSniffRx[compat = @document.compat_mode].match? text
|
342
|
-
QUOTE_SUBS[compat].each do |type, scope, pattern|
|
343
|
-
text = text.gsub(pattern) { convert_quoted_text $~, type, scope }
|
344
|
-
end
|
345
|
-
end
|
346
|
-
text
|
347
|
-
end
|
348
|
-
|
349
|
-
# Public: Substitute replacement characters (e.g., copyright, trademark, etc.)
|
350
|
-
#
|
351
|
-
# text - The String text to process
|
352
|
-
#
|
353
|
-
# returns The [String] text with the replacement characters substituted
|
354
|
-
def sub_replacements text
|
355
|
-
if ReplaceableTextRx.match? text
|
356
|
-
REPLACEMENTS.each do |pattern, replacement, restore|
|
357
|
-
text = text.gsub(pattern) { do_replacement $~, replacement, restore }
|
358
|
-
end
|
359
|
-
end
|
360
|
-
text
|
361
|
-
end
|
362
|
-
|
363
161
|
# Public: Substitute special characters (i.e., encode XML)
|
364
162
|
#
|
365
163
|
# The special characters <, &, and > get replaced with <, &, and >, respectively.
|
366
164
|
#
|
367
165
|
# text - The String text to process.
|
368
166
|
#
|
369
|
-
#
|
167
|
+
# Returns The String text with special characters replaced.
|
370
168
|
if RUBY_ENGINE == 'opal'
|
371
169
|
def sub_specialchars text
|
372
170
|
(text.include? ?>) || (text.include? ?&) || (text.include? ?<) ? (text.gsub SpecialCharsRx, SpecialCharsTr) : text
|
@@ -383,23 +181,18 @@ module Substitutors
|
|
383
181
|
end
|
384
182
|
alias sub_specialcharacters sub_specialchars
|
385
183
|
|
386
|
-
#
|
184
|
+
# Public: Substitute quoted text (includes emphasis, strong, monospaced, etc.)
|
387
185
|
#
|
388
|
-
#
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
when :none
|
396
|
-
replacement
|
397
|
-
when :bounding
|
398
|
-
m[1] + replacement + m[2]
|
399
|
-
else # :leading
|
400
|
-
m[1] + replacement
|
186
|
+
# text - The String text to process
|
187
|
+
#
|
188
|
+
# returns The converted [String] text
|
189
|
+
def sub_quotes text
|
190
|
+
if QuotedTextSniffRx[compat = @document.compat_mode].match? text
|
191
|
+
QUOTE_SUBS[compat].each do |type, scope, pattern|
|
192
|
+
text = text.gsub(pattern) { convert_quoted_text $~, type, scope }
|
401
193
|
end
|
402
194
|
end
|
195
|
+
text
|
403
196
|
end
|
404
197
|
|
405
198
|
# Public: Substitutes attribute references in the specified text
|
@@ -411,7 +204,8 @@ module Substitutors
|
|
411
204
|
#
|
412
205
|
# text - The String text to process
|
413
206
|
# opts - A Hash of options to control processing: (default: {})
|
414
|
-
# * :attribute_missing controls how to handle a missing attribute
|
207
|
+
# * :attribute_missing controls how to handle a missing attribute (see Compliance.attribute_missing for values)
|
208
|
+
# * :drop_line_severity the severity level at which to log a dropped line (:info or :ignore)
|
415
209
|
#
|
416
210
|
# Returns the [String] text with the attribute references replaced with resolved values
|
417
211
|
def sub_attributes text, opts = {}
|
@@ -480,6 +274,18 @@ module Substitutors
|
|
480
274
|
end
|
481
275
|
end
|
482
276
|
|
277
|
+
# Public: Substitute replacement characters (e.g., copyright, trademark, etc.)
|
278
|
+
#
|
279
|
+
# text - The String text to process
|
280
|
+
#
|
281
|
+
# returns The [String] text with the replacement characters substituted
|
282
|
+
def sub_replacements text
|
283
|
+
REPLACEMENTS.each do |pattern, replacement, restore|
|
284
|
+
text = text.gsub(pattern) { do_replacement $~, replacement, restore }
|
285
|
+
end if ReplaceableTextRx.match? text
|
286
|
+
text
|
287
|
+
end
|
288
|
+
|
483
289
|
# Public: Substitute inline macros (e.g., links, images, etc)
|
484
290
|
#
|
485
291
|
# Replace inline macros, which may span multiple lines, in the provided text
|
@@ -487,7 +293,7 @@ module Substitutors
|
|
487
293
|
# source - The String text to process
|
488
294
|
#
|
489
295
|
# returns The converted String text
|
490
|
-
def sub_macros
|
296
|
+
def sub_macros text
|
491
297
|
#return text if text.nil_or_empty?
|
492
298
|
# some look ahead assertions to cut unnecessary regex calls
|
493
299
|
found_square_bracket = text.include? '['
|
@@ -513,10 +319,10 @@ module Substitutors
|
|
513
319
|
if content.nil_or_empty?
|
514
320
|
attributes['text'] = content if content && extconf[:content_model] != :attributes
|
515
321
|
else
|
516
|
-
content =
|
322
|
+
content = normalize_text content, true, true
|
517
323
|
# QUESTION should we store the unparsed attrlist in the attrlist key?
|
518
324
|
if extconf[:content_model] == :attributes
|
519
|
-
parse_attributes content, extconf[:
|
325
|
+
parse_attributes content, extconf[:positional_attrs] || [], into: attributes
|
520
326
|
else
|
521
327
|
attributes['text'] = content
|
522
328
|
end
|
@@ -563,7 +369,7 @@ module Substitutors
|
|
563
369
|
end
|
564
370
|
(Inline.new self, :kbd, nil, attributes: { 'keys' => keys }).convert
|
565
371
|
else # $2 == 'btn'
|
566
|
-
(Inline.new self, :button, (
|
372
|
+
(Inline.new self, :button, (normalize_text $3, true, true)).convert
|
567
373
|
end
|
568
374
|
end
|
569
375
|
end
|
@@ -613,10 +419,7 @@ module Substitutors
|
|
613
419
|
else
|
614
420
|
type, posattrs = 'image', ['alt', 'width', 'height']
|
615
421
|
end
|
616
|
-
|
617
|
-
# TODO remove this special case once titles use normal substitution order
|
618
|
-
target = sub_attributes target
|
619
|
-
end
|
422
|
+
target = $1
|
620
423
|
attrs = parse_attributes $2, posattrs, unescape_input: true
|
621
424
|
doc.register :images, [target, (attrs['imagesdir'] = doc_attrs['imagesdir'])] unless type == 'icon'
|
622
425
|
attrs['alt'] ||= (attrs['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
|
@@ -636,17 +439,39 @@ module Substitutors
|
|
636
439
|
next $&.slice 1, $&.length if $&.start_with? RS
|
637
440
|
|
638
441
|
# indexterm:[Tigers,Big cats]
|
639
|
-
|
442
|
+
if (attrlist = normalize_text $2, true, true).include? '='
|
443
|
+
if (primary = (attrs = (AttributeList.new attrlist, self).parse)[1])
|
444
|
+
attrs['terms'] = terms = [primary]
|
445
|
+
if (secondary = attrs[2])
|
446
|
+
terms << secondary
|
447
|
+
if (tertiary = attrs[3])
|
448
|
+
terms << tertiary
|
449
|
+
end
|
450
|
+
end
|
451
|
+
if (see_also = attrs['see-also'])
|
452
|
+
attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
|
453
|
+
end
|
454
|
+
else
|
455
|
+
attrs = { 'terms' => (terms = attrlist) }
|
456
|
+
end
|
457
|
+
else
|
458
|
+
attrs = { 'terms' => (terms = split_simple_csv attrlist) }
|
459
|
+
end
|
640
460
|
#doc.register :indexterms, terms
|
641
|
-
(Inline.new self, :indexterm, nil, attributes:
|
461
|
+
(Inline.new self, :indexterm, nil, attributes: attrs).convert
|
642
462
|
when 'indexterm2'
|
643
463
|
# honor the escape
|
644
464
|
next $&.slice 1, $&.length if $&.start_with? RS
|
645
465
|
|
646
466
|
# indexterm2:[Tigers]
|
647
|
-
term =
|
467
|
+
if (term = normalize_text $2, true, true).include? '='
|
468
|
+
term = (attrs = (AttributeList.new term, self).parse)[1] || (attrs = nil) || term
|
469
|
+
if attrs && (see_also = attrs['see-also'])
|
470
|
+
attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
|
471
|
+
end
|
472
|
+
end
|
648
473
|
#doc.register :indexterms, [term]
|
649
|
-
(Inline.new self, :indexterm, term, type: :visible).convert
|
474
|
+
(Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
|
650
475
|
else
|
651
476
|
text = $3
|
652
477
|
# honor the escape
|
@@ -672,14 +497,32 @@ module Substitutors
|
|
672
497
|
end
|
673
498
|
if visible
|
674
499
|
# ((Tigers))
|
675
|
-
term =
|
500
|
+
if (term = normalize_text text, true).include? ';&'
|
501
|
+
if term.include? ' >> '
|
502
|
+
term, _, see = term.partition ' >> '
|
503
|
+
attrs = { 'see' => see }
|
504
|
+
elsif term.include? ' &> '
|
505
|
+
term, *see_also = term.split ' &> '
|
506
|
+
attrs = { 'see-also' => see_also }
|
507
|
+
end
|
508
|
+
end
|
676
509
|
#doc.register :indexterms, [term]
|
677
|
-
subbed_term = (Inline.new self, :indexterm, term, type: :visible).convert
|
510
|
+
subbed_term = (Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
|
678
511
|
else
|
679
512
|
# (((Tigers,Big cats)))
|
680
|
-
|
513
|
+
attrs = {}
|
514
|
+
if (terms = normalize_text text, true).include? ';&'
|
515
|
+
if terms.include? ' >> '
|
516
|
+
terms, _, see = terms.partition ' >> '
|
517
|
+
attrs['see'] = see
|
518
|
+
elsif terms.include? ' &> '
|
519
|
+
terms, *see_also = terms.split ' &> '
|
520
|
+
attrs['see-also'] = see_also
|
521
|
+
end
|
522
|
+
end
|
523
|
+
attrs['terms'] = terms = split_simple_csv terms
|
681
524
|
#doc.register :indexterms, terms
|
682
|
-
subbed_term = (Inline.new self, :indexterm, nil, attributes:
|
525
|
+
subbed_term = (Inline.new self, :indexterm, nil, attributes: attrs).convert
|
683
526
|
end
|
684
527
|
before ? %(#{before}#{subbed_term}#{after}) : subbed_term
|
685
528
|
end
|
@@ -745,18 +588,9 @@ module Substitutors
|
|
745
588
|
text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
|
746
589
|
if !doc.compat_mode && (text.include? '=')
|
747
590
|
text = (attrs = (AttributeList.new text, self).parse)[1] || ''
|
748
|
-
link_opts[:id] = attrs
|
591
|
+
link_opts[:id] = attrs['id']
|
749
592
|
end
|
750
593
|
|
751
|
-
# TODO enable in Asciidoctor 1.6.x
|
752
|
-
# support pipe-separated text and title
|
753
|
-
#unless attrs && (attrs.key? 'title')
|
754
|
-
# if text.include? '|'
|
755
|
-
# attrs ||= {}
|
756
|
-
# text, _, attrs['title'] = text.partition '|'
|
757
|
-
# end
|
758
|
-
#end
|
759
|
-
|
760
594
|
if text.end_with? '^'
|
761
595
|
text = text.chop
|
762
596
|
if attrs
|
@@ -800,7 +634,7 @@ module Substitutors
|
|
800
634
|
if mailto
|
801
635
|
if !doc.compat_mode && (text.include? ',')
|
802
636
|
text = (attrs = (AttributeList.new text, self).parse)[1] || ''
|
803
|
-
link_opts[:id] = attrs
|
637
|
+
link_opts[:id] = attrs['id']
|
804
638
|
if attrs.key? 2
|
805
639
|
if attrs.key? 3
|
806
640
|
target = %(#{target}?subject=#{Helpers.encode_uri_component attrs[2]}&body=#{Helpers.encode_uri_component attrs[3]})
|
@@ -811,18 +645,9 @@ module Substitutors
|
|
811
645
|
end
|
812
646
|
elsif !doc.compat_mode && (text.include? '=')
|
813
647
|
text = (attrs = (AttributeList.new text, self).parse)[1] || ''
|
814
|
-
link_opts[:id] = attrs
|
648
|
+
link_opts[:id] = attrs['id']
|
815
649
|
end
|
816
650
|
|
817
|
-
# TODO enable in Asciidoctor 1.6.x
|
818
|
-
# support pipe-separated text and title
|
819
|
-
#unless attrs && (attrs.key? 'title')
|
820
|
-
# if text.include? '|'
|
821
|
-
# attrs ||= {}
|
822
|
-
# text, _, attrs['title'] = text.partition '|'
|
823
|
-
# end
|
824
|
-
#end
|
825
|
-
|
826
651
|
if text.end_with? '^'
|
827
652
|
text = text.chop
|
828
653
|
if attrs
|
@@ -1009,7 +834,7 @@ module Substitutors
|
|
1009
834
|
|
1010
835
|
if id
|
1011
836
|
if text
|
1012
|
-
text = restore_passthroughs(
|
837
|
+
text = restore_passthroughs(normalize_text text, true, true)
|
1013
838
|
index = doc.counter('footnote-number')
|
1014
839
|
doc.register(:footnotes, Document::Footnote.new(index, id, text))
|
1015
840
|
type, target = :ref, nil
|
@@ -1023,7 +848,7 @@ module Substitutors
|
|
1023
848
|
type, target, id = :xref, id, nil
|
1024
849
|
end
|
1025
850
|
elsif text
|
1026
|
-
text = restore_passthroughs(
|
851
|
+
text = restore_passthroughs(normalize_text text, true, true)
|
1027
852
|
index = doc.counter('footnote-number')
|
1028
853
|
doc.register(:footnotes, Document::Footnote.new(index, id, text))
|
1029
854
|
type = target = nil
|
@@ -1037,31 +862,12 @@ module Substitutors
|
|
1037
862
|
text
|
1038
863
|
end
|
1039
864
|
|
1040
|
-
# Public: Substitute
|
865
|
+
# Public: Substitute post replacements
|
1041
866
|
#
|
1042
867
|
# text - The String text to process
|
1043
868
|
#
|
1044
869
|
# Returns the converted String text
|
1045
|
-
def
|
1046
|
-
callout_rx = (attr? 'line-comment') ? CalloutSourceRxMap[attr 'line-comment'] : CalloutSourceRx
|
1047
|
-
autonum = 0
|
1048
|
-
text.gsub callout_rx do
|
1049
|
-
# honor the escape
|
1050
|
-
if $2
|
1051
|
-
# use sub since it might be behind a line comment
|
1052
|
-
$&.sub(RS, '')
|
1053
|
-
else
|
1054
|
-
Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 }).convert
|
1055
|
-
end
|
1056
|
-
end
|
1057
|
-
end
|
1058
|
-
|
1059
|
-
# Public: Substitute post replacements
|
1060
|
-
#
|
1061
|
-
# text - The String text to process
|
1062
|
-
#
|
1063
|
-
# Returns the converted String text
|
1064
|
-
def sub_post_replacements(text)
|
870
|
+
def sub_post_replacements text
|
1065
871
|
#if attr? 'hardbreaks-option', nil, true
|
1066
872
|
if @attributes['hardbreaks-option'] || @document.attributes['hardbreaks-option']
|
1067
873
|
lines = text.split LF, -1
|
@@ -1077,196 +883,271 @@ module Substitutors
|
|
1077
883
|
end
|
1078
884
|
end
|
1079
885
|
|
1080
|
-
#
|
886
|
+
# Public: Apply verbatim substitutions on source (for use when highlighting is disabled).
|
1081
887
|
#
|
1082
|
-
#
|
1083
|
-
#
|
1084
|
-
# scope - The scope of the quoting (constrained or unconstrained)
|
888
|
+
# source - the source code String on which to apply verbatim substitutions
|
889
|
+
# process_callouts - a Boolean flag indicating whether callout marks should be substituted
|
1085
890
|
#
|
1086
|
-
# Returns
|
1087
|
-
def
|
1088
|
-
|
1089
|
-
|
1090
|
-
unescaped_attrs = %([#{attrs}])
|
1091
|
-
else
|
1092
|
-
return match[0].slice 1, match[0].length
|
1093
|
-
end
|
1094
|
-
end
|
891
|
+
# Returns the substituted source
|
892
|
+
def sub_source source, process_callouts
|
893
|
+
process_callouts ? sub_callouts(sub_specialchars source) : (sub_specialchars source)
|
894
|
+
end
|
1095
895
|
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
896
|
+
# Public: Substitute callout source references
|
897
|
+
#
|
898
|
+
# text - The String text to process
|
899
|
+
#
|
900
|
+
# Returns the converted String text
|
901
|
+
def sub_callouts text
|
902
|
+
callout_rx = (attr? 'line-comment') ? CalloutSourceRxMap[attr 'line-comment'] : CalloutSourceRx
|
903
|
+
autonum = 0
|
904
|
+
text.gsub callout_rx do
|
905
|
+
# honor the escape
|
906
|
+
if $2
|
907
|
+
# use sub since it might be behind a line comment
|
908
|
+
$&.sub(RS, '')
|
1099
909
|
else
|
1100
|
-
|
1101
|
-
id = (attributes = parse_quoted_text_attributes attrlist).delete 'id'
|
1102
|
-
type = :unquoted if type == :mark
|
1103
|
-
end
|
1104
|
-
%(#{match[1]}#{Inline.new(self, :quoted, match[3], type: type, id: id, attributes: attributes).convert})
|
1105
|
-
end
|
1106
|
-
else
|
1107
|
-
if (attrlist = match[1])
|
1108
|
-
id = (attributes = parse_quoted_text_attributes attrlist).delete 'id'
|
1109
|
-
type = :unquoted if type == :mark
|
910
|
+
Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 }).convert
|
1110
911
|
end
|
1111
|
-
Inline.new(self, :quoted, match[2], type: type, id: id, attributes: attributes).convert
|
1112
912
|
end
|
1113
913
|
end
|
1114
914
|
|
1115
|
-
#
|
915
|
+
# Public: Highlight (i.e., colorize) the source code during conversion using a syntax highlighter, if activated by the
|
916
|
+
# source-highlighter document attribute. Otherwise return the text with verbatim substitutions applied.
|
1116
917
|
#
|
1117
|
-
#
|
1118
|
-
#
|
918
|
+
# If the process_callouts argument is true, this method will extract the callout marks from the source before passing
|
919
|
+
# it to the syntax highlighter, then subsequently restore those callout marks to the highlighted source so the callout
|
920
|
+
# marks don't confuse the syntax highlighter.
|
1119
921
|
#
|
1120
|
-
#
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
922
|
+
# source - the source code String to syntax highlight
|
923
|
+
# process_callouts - a Boolean flag indicating whether callout marks should be located and substituted
|
924
|
+
#
|
925
|
+
# Returns the highlighted source code, if a syntax highlighter is defined on the document, otherwise the source with
|
926
|
+
# verbatim substituions applied
|
927
|
+
def highlight_source source, process_callouts
|
928
|
+
# NOTE the call to highlight? is a defensive check since, normally, we wouldn't arrive here unless it returns true
|
929
|
+
return sub_source source, process_callouts unless (syntax_hl = @document.syntax_highlighter) && syntax_hl.highlight?
|
1127
930
|
|
1128
|
-
|
1129
|
-
segments = str.split '#', 2
|
931
|
+
source, callout_marks = extract_callouts source if process_callouts
|
1130
932
|
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
933
|
+
doc_attrs = @document.attributes
|
934
|
+
syntax_hl_name = syntax_hl.name
|
935
|
+
if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil)
|
936
|
+
start_line_number = 1 if (start_line_number = (attr 'start', 1).to_i) < 1
|
937
|
+
end
|
938
|
+
highlight_lines = resolve_lines_to_highlight source, (attr 'highlight') if attr? 'highlight'
|
1136
939
|
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
940
|
+
highlighted, source_offset = syntax_hl.highlight self, source, (attr 'language'),
|
941
|
+
callouts: callout_marks,
|
942
|
+
css_mode: (doc_attrs[%(#{syntax_hl_name}-css)] || :class).to_sym,
|
943
|
+
highlight_lines: highlight_lines,
|
944
|
+
number_lines: linenums_mode,
|
945
|
+
start_line_number: start_line_number,
|
946
|
+
style: doc_attrs[%(#{syntax_hl_name}-style)]
|
1141
947
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
end
|
948
|
+
# fix passthrough placeholders that got caught up in syntax highlighting
|
949
|
+
highlighted = highlighted.gsub HighlightedPassSlotRx, %(#{PASS_START}\\1#{PASS_END}) unless @passthroughs.empty?
|
1145
950
|
|
1146
|
-
|
1147
|
-
|
1148
|
-
attrs['role'] = roles.join ' ' unless roles.empty?
|
1149
|
-
attrs
|
1150
|
-
else
|
1151
|
-
{ 'role' => str }
|
1152
|
-
end
|
951
|
+
# NOTE highlight method may have depleted callouts
|
952
|
+
callout_marks.nil_or_empty? ? highlighted : (restore_callouts highlighted, callout_marks, source_offset)
|
1153
953
|
end
|
1154
954
|
|
1155
|
-
#
|
955
|
+
# Public: Resolve the line numbers in the specified source to highlight from the provided spec.
|
1156
956
|
#
|
1157
|
-
#
|
1158
|
-
# posattrs - An Array of positional attribute names (default: []).
|
1159
|
-
# opts - A Hash of options to control how the string is parsed (default: {}):
|
1160
|
-
# :into - The Hash to parse the attributes into (optional, default: false).
|
1161
|
-
# :sub_input - A Boolean that indicates whether to substitute attributes prior to
|
1162
|
-
# parsing (optional, default: false).
|
1163
|
-
# :sub_result - A Boolean that indicates whether to apply substitutions
|
1164
|
-
# single-quoted attribute values (optional, default: true).
|
1165
|
-
# :unescape_input - A Boolean that indicates whether to unescape square brackets prior
|
1166
|
-
# to parsing (optional, default: false).
|
957
|
+
# e.g., highlight="1-5, !2, 10" or highlight=1-5;!2,10
|
1167
958
|
#
|
1168
|
-
#
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
959
|
+
# source - The String source.
|
960
|
+
# spec - The lines specifier (e.g., "1-5, !2, 10" or "1..5;!2;10")
|
961
|
+
#
|
962
|
+
# Returns an [Array] of unique, sorted line numbers.
|
963
|
+
def resolve_lines_to_highlight source, spec
|
964
|
+
lines = []
|
965
|
+
spec = spec.delete ' ' if spec.include? ' '
|
966
|
+
((spec.include? ',') ? (spec.split ',') : (spec.split ';')).map do |entry|
|
967
|
+
if entry.start_with? '!'
|
968
|
+
entry = entry.slice 1, entry.length
|
969
|
+
negate = true
|
970
|
+
end
|
971
|
+
if (delim = (entry.include? '..') ? '..' : ((entry.include? '-') ? '-' : nil))
|
972
|
+
from, delim, to = entry.partition delim
|
973
|
+
to = (source.count LF) + 1 if to.empty? || (to = to.to_i) < 0
|
974
|
+
line_nums = (from.to_i..to).to_a
|
975
|
+
if negate
|
976
|
+
lines -= line_nums
|
977
|
+
else
|
978
|
+
lines.concat line_nums
|
979
|
+
end
|
980
|
+
else
|
981
|
+
if negate
|
982
|
+
lines.delete entry.to_i
|
983
|
+
else
|
984
|
+
lines << entry.to_i
|
985
|
+
end
|
986
|
+
end
|
1179
987
|
end
|
988
|
+
lines.sort.uniq
|
1180
989
|
end
|
1181
990
|
|
1182
|
-
#
|
991
|
+
# Public: Extract the passthrough text from the document for reinsertion after processing.
|
1183
992
|
#
|
1184
|
-
#
|
993
|
+
# text - The String from which to extract passthrough fragements
|
1185
994
|
#
|
1186
|
-
# Returns
|
1187
|
-
def
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
995
|
+
# Returns the String text with passthrough regions substituted with placeholders
|
996
|
+
def extract_passthroughs text
|
997
|
+
compat_mode = @document.compat_mode
|
998
|
+
passthrus = @passthroughs
|
999
|
+
text = text.gsub InlinePassMacroRx do
|
1000
|
+
if (boundary = $4) # $$, ++, or +++
|
1001
|
+
# skip ++ in compat mode, handled as normal quoted text
|
1002
|
+
if compat_mode && boundary == '++'
|
1003
|
+
content = extract_passthroughs $5
|
1004
|
+
next $2 ? %(#{$1}[#{$2}]#{$3}++#{content}++) : %(#{$1}#{$3}++#{content}++)
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
attributes = $2
|
1008
|
+
escape_count = $3.length
|
1009
|
+
content = $5
|
1010
|
+
|
1011
|
+
if attributes
|
1012
|
+
if escape_count > 0
|
1013
|
+
# NOTE we don't look for nested unconstrained pass macros
|
1014
|
+
next %(#{$1}[#{attributes}]#{RS * (escape_count - 1)}#{boundary}#{$5}#{boundary})
|
1015
|
+
elsif $1 == RS
|
1016
|
+
preceding = %([#{attributes}])
|
1017
|
+
attributes = nil
|
1198
1018
|
else
|
1199
|
-
|
1019
|
+
if boundary == '++' && (attributes.end_with? 'x-')
|
1020
|
+
old_behavior = true
|
1021
|
+
attributes = attributes.slice 0, attributes.length - 2
|
1022
|
+
end
|
1023
|
+
attributes = parse_quoted_text_attributes attributes
|
1200
1024
|
end
|
1025
|
+
elsif escape_count > 0
|
1026
|
+
# NOTE we don't look for nested unconstrained pass macros
|
1027
|
+
next %(#{RS * (escape_count - 1)}#{boundary}#{$5}#{boundary})
|
1201
1028
|
end
|
1029
|
+
subs = (boundary == '+++' ? [] : BASIC_SUBS)
|
1030
|
+
|
1031
|
+
if attributes
|
1032
|
+
if old_behavior
|
1033
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: NORMAL_SUBS, type: :monospaced, attributes: attributes }
|
1034
|
+
else
|
1035
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: :unquoted, attributes: attributes }
|
1036
|
+
end
|
1037
|
+
else
|
1038
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs }
|
1039
|
+
end
|
1040
|
+
else # pass:[]
|
1041
|
+
# NOTE we don't look for nested pass:[] macros
|
1042
|
+
# honor the escape
|
1043
|
+
next $&.slice 1, $&.length if $6 == RS
|
1044
|
+
passthrus[passthru_key = passthrus.size] = { text: (normalize_text $8, nil, true), subs: ($7 ? (resolve_pass_subs $7) : nil) }
|
1202
1045
|
end
|
1203
1046
|
|
1204
|
-
|
1205
|
-
end
|
1206
|
-
end
|
1047
|
+
%(#{preceding || ''}#{PASS_START}#{passthru_key}#{PASS_END})
|
1048
|
+
end if (text.include? '++') || (text.include? '$$') || (text.include? 'ss:')
|
1207
1049
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
end
|
1050
|
+
pass_inline_char1, pass_inline_char2, pass_inline_rx = InlinePassRx[compat_mode]
|
1051
|
+
text = text.gsub pass_inline_rx do
|
1052
|
+
preceding = $1
|
1053
|
+
attributes = $2
|
1054
|
+
escape_mark = RS if (quoted_text = $3).start_with? RS
|
1055
|
+
format_mark = $4
|
1056
|
+
content = $5
|
1216
1057
|
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
end
|
1223
|
-
str
|
1224
|
-
end
|
1058
|
+
if compat_mode
|
1059
|
+
old_behavior = true
|
1060
|
+
elsif (old_behavior = attributes && (attributes.end_with? 'x-'))
|
1061
|
+
attributes = attributes.slice 0, attributes.length - 2
|
1062
|
+
end
|
1225
1063
|
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1064
|
+
if attributes
|
1065
|
+
if format_mark == '`' && !old_behavior
|
1066
|
+
next extract_inner_passthrough content, %(#{preceding}[#{attributes}]#{escape_mark}), attributes
|
1067
|
+
elsif escape_mark
|
1068
|
+
# honor the escape of the formatting mark
|
1069
|
+
next %(#{preceding}[#{attributes}]#{quoted_text.slice 1, quoted_text.length})
|
1070
|
+
elsif preceding == RS
|
1071
|
+
# honor the escape of the attributes
|
1072
|
+
preceding = %([#{attributes}])
|
1073
|
+
attributes = nil
|
1074
|
+
else
|
1075
|
+
attributes = parse_quoted_text_attributes attributes
|
1076
|
+
end
|
1077
|
+
elsif format_mark == '`' && !old_behavior
|
1078
|
+
next extract_inner_passthrough content, %(#{preceding}#{escape_mark})
|
1079
|
+
elsif escape_mark
|
1080
|
+
# honor the escape of the formatting mark
|
1081
|
+
next %(#{preceding}#{quoted_text.slice 1, quoted_text.length})
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
if compat_mode
|
1085
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS, attributes: attributes, type: :monospaced }
|
1086
|
+
elsif attributes
|
1087
|
+
if old_behavior
|
1088
|
+
subs = (format_mark == '`' ? BASIC_SUBS : NORMAL_SUBS)
|
1089
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, attributes: attributes, type: :monospaced }
|
1090
|
+
else
|
1091
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS, attributes: attributes, type: :unquoted }
|
1092
|
+
end
|
1093
|
+
else
|
1094
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS }
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
%(#{preceding}#{PASS_START}#{passthru_key}#{PASS_END})
|
1098
|
+
end if (text.include? pass_inline_char1) || (pass_inline_char2 && (text.include? pass_inline_char2))
|
1099
|
+
|
1100
|
+
# NOTE we need to do the stem in a subsequent step to allow it to be escaped by the former
|
1101
|
+
text = text.gsub InlineStemMacroRx do
|
1102
|
+
# honor the escape
|
1103
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
1104
|
+
|
1105
|
+
if (type = $1.to_sym) == :stem
|
1106
|
+
type = STEM_TYPE_ALIASES[@document.attributes['stem']].to_sym
|
1107
|
+
end
|
1108
|
+
content = normalize_text $3, nil, true
|
1109
|
+
# NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc Python
|
1110
|
+
content = content.slice 1, content.length - 2 if type == :latexmath && (content.start_with? '$') && (content.end_with? '$')
|
1111
|
+
subs = $2 ? (resolve_pass_subs $2) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
|
1112
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: type }
|
1113
|
+
%(#{PASS_START}#{passthru_key}#{PASS_END})
|
1114
|
+
end if (text.include? ':') && ((text.include? 'stem:') || (text.include? 'math:'))
|
1115
|
+
|
1116
|
+
text
|
1233
1117
|
end
|
1234
1118
|
|
1235
|
-
#
|
1236
|
-
#
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
accum = accum + c
|
1249
|
-
else
|
1250
|
-
values << accum.strip
|
1251
|
-
accum = ''
|
1119
|
+
# Public: Restore the passthrough text by reinserting into the placeholder positions
|
1120
|
+
#
|
1121
|
+
# text - The String text into which to restore the passthrough text
|
1122
|
+
#
|
1123
|
+
# returns The String text with the passthrough text restored
|
1124
|
+
def restore_passthroughs text
|
1125
|
+
passthrus = @passthroughs
|
1126
|
+
text.gsub PassSlotRx do
|
1127
|
+
if (pass = passthrus[$1.to_i])
|
1128
|
+
subbed_text = apply_subs(pass[:text], pass[:subs])
|
1129
|
+
if (type = pass[:type])
|
1130
|
+
if (attributes = pass[:attributes])
|
1131
|
+
id = attributes['id']
|
1252
1132
|
end
|
1253
|
-
|
1254
|
-
quote_open = !quote_open
|
1255
|
-
else
|
1256
|
-
accum = accum + c
|
1133
|
+
subbed_text = Inline.new(self, :quoted, subbed_text, type: type, id: id, attributes: attributes).convert
|
1257
1134
|
end
|
1135
|
+
subbed_text.include?(PASS_START) ? restore_passthroughs(subbed_text) : subbed_text
|
1136
|
+
else
|
1137
|
+
logger.error %(unresolved passthrough detected: #{text})
|
1138
|
+
'??pass??'
|
1258
1139
|
end
|
1259
|
-
values << accum.strip
|
1260
|
-
else
|
1261
|
-
str.split(',').map {|it| it.strip }
|
1262
1140
|
end
|
1263
1141
|
end
|
1264
1142
|
|
1265
|
-
#
|
1143
|
+
# Public: Resolve the list of comma-delimited subs against the possible options.
|
1266
1144
|
#
|
1267
|
-
# subs
|
1145
|
+
# subs - The comma-delimited String of substitution names or aliases.
|
1146
|
+
# type - A Symbol representing the context for which the subs are being resolved (default: :block).
|
1147
|
+
# defaults - An Array of substitutions to start with when computing incremental substitutions (default: nil).
|
1148
|
+
# subject - The String to use in log messages to communicate the subject for which subs are being resolved (default: nil)
|
1268
1149
|
#
|
1269
|
-
#
|
1150
|
+
# Returns An Array of Symbols representing the substitution operation or nothing if no subs are found.
|
1270
1151
|
def resolve_subs subs, type = :block, defaults = nil, subject = nil
|
1271
1152
|
return if subs.nil_or_empty?
|
1272
1153
|
# QUESTION should we store candidates as a Set instead of an Array?
|
@@ -1315,68 +1196,125 @@ module Substitutors
|
|
1315
1196
|
candidates -= resolved_keys
|
1316
1197
|
end
|
1317
1198
|
else
|
1318
|
-
candidates ||= []
|
1319
|
-
candidates += resolved_keys
|
1199
|
+
candidates ||= []
|
1200
|
+
candidates += resolved_keys
|
1201
|
+
end
|
1202
|
+
end
|
1203
|
+
return unless candidates
|
1204
|
+
# weed out invalid options and remove duplicates (order is preserved; first occurence wins)
|
1205
|
+
resolved = candidates & SUB_OPTIONS[type]
|
1206
|
+
unless (candidates - resolved).empty?
|
1207
|
+
invalid = candidates - resolved
|
1208
|
+
logger.warn %(invalid substitution type#{invalid.size > 1 ? 's' : ''}#{subject ? ' for ' : ''}#{subject}: #{invalid.join ', '})
|
1209
|
+
end
|
1210
|
+
resolved
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
# Public: Call resolve_subs for the :block type.
|
1214
|
+
def resolve_block_subs subs, defaults, subject
|
1215
|
+
resolve_subs subs, :block, defaults, subject
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
# Public: Call resolve_subs for the :inline type with the subject set as passthrough macro.
|
1219
|
+
def resolve_pass_subs subs
|
1220
|
+
resolve_subs subs, :inline, nil, 'passthrough macro'
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
# Public: Expand all groups in the subs list and return. If no subs are resolve, return nil.
|
1224
|
+
#
|
1225
|
+
# subs - The substitutions to expand; can be a Symbol, Symbol Array or nil
|
1226
|
+
#
|
1227
|
+
# Returns a Symbol Array of substitutions to pass to apply_subs or nil if no substitutions were resolved.
|
1228
|
+
def expand_subs subs
|
1229
|
+
if ::Symbol === subs
|
1230
|
+
unless subs == :none
|
1231
|
+
SUB_GROUPS[subs] || [subs]
|
1232
|
+
end
|
1233
|
+
else
|
1234
|
+
expanded_subs = []
|
1235
|
+
subs.each do |key|
|
1236
|
+
unless key == :none
|
1237
|
+
if (sub_group = SUB_GROUPS[key])
|
1238
|
+
expanded_subs += sub_group
|
1239
|
+
else
|
1240
|
+
expanded_subs << key
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
expanded_subs.empty? ? nil : expanded_subs
|
1246
|
+
end
|
1247
|
+
end
|
1248
|
+
|
1249
|
+
# Internal: Commit the requested substitutions to this block.
|
1250
|
+
#
|
1251
|
+
# Looks for an attribute named "subs". If present, resolves substitutions
|
1252
|
+
# from the value of that attribute and assigns them to the subs property on
|
1253
|
+
# this block. Otherwise, uses the substitutions assigned to the default_subs
|
1254
|
+
# property, if specified, or selects a default set of substitutions based on
|
1255
|
+
# the content model of the block.
|
1256
|
+
#
|
1257
|
+
# Returns nothing
|
1258
|
+
def commit_subs
|
1259
|
+
unless (default_subs = @default_subs)
|
1260
|
+
case @content_model
|
1261
|
+
when :simple
|
1262
|
+
default_subs = NORMAL_SUBS
|
1263
|
+
when :verbatim
|
1264
|
+
# NOTE :literal with listparagraph-option gets folded into text of list item later
|
1265
|
+
default_subs = @context == :verse ? NORMAL_SUBS : VERBATIM_SUBS
|
1266
|
+
when :raw
|
1267
|
+
# TODO make pass subs a compliance setting; AsciiDoc Python performs :attributes and :macros on a pass block
|
1268
|
+
default_subs = @context == :stem ? BASIC_SUBS : NO_SUBS
|
1269
|
+
else
|
1270
|
+
return @subs
|
1320
1271
|
end
|
1321
1272
|
end
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
logger.warn %(invalid substitution type#{invalid.size > 1 ? 's' : ''}#{subject ? ' for ' : ''}#{subject}: #{invalid.join ', '})
|
1273
|
+
|
1274
|
+
if (custom_subs = @attributes['subs'])
|
1275
|
+
@subs = (resolve_block_subs custom_subs, default_subs, @context) || []
|
1276
|
+
else
|
1277
|
+
@subs = default_subs.drop 0
|
1328
1278
|
end
|
1329
|
-
resolved
|
1330
|
-
end
|
1331
1279
|
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1280
|
+
# QUESION delegate this logic to a method?
|
1281
|
+
if @context == :listing && @style == 'source' && (syntax_hl = @document.syntax_highlighter) &&
|
1282
|
+
syntax_hl.highlight? && (idx = @subs.index :specialcharacters)
|
1283
|
+
@subs[idx] = :highlight
|
1284
|
+
end
|
1335
1285
|
|
1336
|
-
|
1337
|
-
resolve_subs subs, :inline, nil, 'passthrough macro'
|
1286
|
+
nil
|
1338
1287
|
end
|
1339
1288
|
|
1340
|
-
#
|
1341
|
-
# source-highlighter document attribute. Otherwise return the text with verbatim substitutions applied.
|
1342
|
-
#
|
1343
|
-
# If the process_callouts argument is true, this method will extract the callout marks from the source before passing
|
1344
|
-
# it to the syntax highlighter, then subsequently restore those callout marks to the highlighted source so the callout
|
1345
|
-
# marks don't confuse the syntax highlighter.
|
1289
|
+
# Internal: Parse attributes in name or name=value format from a comma-separated String
|
1346
1290
|
#
|
1347
|
-
#
|
1348
|
-
#
|
1291
|
+
# attrlist - A comma-separated String list of attributes in name or name=value format.
|
1292
|
+
# posattrs - An Array of positional attribute names (default: []).
|
1293
|
+
# opts - A Hash of options to control how the string is parsed (default: {}):
|
1294
|
+
# :into - The Hash to parse the attributes into (optional, default: false).
|
1295
|
+
# :sub_input - A Boolean that indicates whether to substitute attributes prior to
|
1296
|
+
# parsing (optional, default: false).
|
1297
|
+
# :sub_result - A Boolean that indicates whether to apply substitutions
|
1298
|
+
# single-quoted attribute values (optional, default: true).
|
1299
|
+
# :unescape_input - A Boolean that indicates whether to unescape square brackets prior
|
1300
|
+
# to parsing (optional, default: false).
|
1349
1301
|
#
|
1350
|
-
# Returns
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
start_line_number = 1 if (start_line_number = (attr 'start', 1).to_i) < 1
|
1302
|
+
# Returns an empty Hash if attrlist is nil or empty, otherwise a Hash of parsed attributes.
|
1303
|
+
def parse_attributes attrlist, posattrs = [], opts = {}
|
1304
|
+
return {} if attrlist ? attrlist.empty? : true
|
1305
|
+
attrlist = normalize_text attrlist, true, true if opts[:unescape_input]
|
1306
|
+
attrlist = @document.sub_attributes attrlist if opts[:sub_input] && (attrlist.include? ATTR_REF_HEAD)
|
1307
|
+
# substitutions are only performed on attribute values if block is not nil
|
1308
|
+
block = self if opts[:sub_result]
|
1309
|
+
if (into = opts[:into])
|
1310
|
+
AttributeList.new(attrlist, block).parse_into(into, posattrs)
|
1311
|
+
else
|
1312
|
+
AttributeList.new(attrlist, block).parse(posattrs)
|
1362
1313
|
end
|
1363
|
-
highlight_lines = resolve_lines_to_highlight source, (attr 'highlight') if attr? 'highlight'
|
1364
|
-
|
1365
|
-
highlighted, source_offset = syntax_hl.highlight self, source, (attr 'language'),
|
1366
|
-
callouts: callout_marks,
|
1367
|
-
css_mode: (doc_attrs[%(#{syntax_hl_name}-css)] || :class).to_sym,
|
1368
|
-
highlight_lines: highlight_lines,
|
1369
|
-
number_lines: linenums_mode,
|
1370
|
-
start_line_number: start_line_number,
|
1371
|
-
style: doc_attrs[%(#{syntax_hl_name}-style)]
|
1372
|
-
|
1373
|
-
# fix passthrough placeholders that got caught up in syntax highlighting
|
1374
|
-
highlighted = highlighted.gsub HighlightedPassSlotRx, %(#{PASS_START}\\1#{PASS_END}) unless @passthroughs.empty?
|
1375
|
-
|
1376
|
-
# NOTE highlight method may have depleted callouts
|
1377
|
-
callout_marks.nil_or_empty? ? highlighted : (restore_callouts highlighted, callout_marks, source_offset)
|
1378
1314
|
end
|
1379
1315
|
|
1316
|
+
private
|
1317
|
+
|
1380
1318
|
# Internal: Extract the callout numbers from the source to prepare it for syntax highlighting.
|
1381
1319
|
def extract_callouts source
|
1382
1320
|
callout_marks = {}
|
@@ -1431,87 +1369,161 @@ module Substitutors
|
|
1431
1369
|
end.join LF)
|
1432
1370
|
end
|
1433
1371
|
|
1434
|
-
#
|
1435
|
-
def
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1372
|
+
# Internal: Extract nested single-plus passthrough; otherwise return unprocessed
|
1373
|
+
def extract_inner_passthrough text, pre, attributes = nil
|
1374
|
+
if (text.end_with? '+') && (text.start_with? '+', '\+') && SinglePlusInlinePassRx =~ text
|
1375
|
+
if $1
|
1376
|
+
%(#{pre}`+#{$2}+`)
|
1377
|
+
else
|
1378
|
+
@passthroughs[passthru_key = @passthroughs.size] = attributes ?
|
1379
|
+
{ text: $2, subs: BASIC_SUBS, attributes: attributes, type: :unquoted } :
|
1380
|
+
{ text: $2, subs: BASIC_SUBS }
|
1381
|
+
%(#{pre}`#{PASS_START}#{passthru_key}#{PASS_END}`)
|
1443
1382
|
end
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1383
|
+
else
|
1384
|
+
%(#{pre}`#{text}`)
|
1385
|
+
end
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
# Internal: Convert a quoted text region
|
1389
|
+
#
|
1390
|
+
# match - The MatchData for the quoted text region
|
1391
|
+
# type - The quoting type (single, double, strong, emphasis, monospaced, etc)
|
1392
|
+
# scope - The scope of the quoting (constrained or unconstrained)
|
1393
|
+
#
|
1394
|
+
# Returns The converted String text for the quoted text region
|
1395
|
+
def convert_quoted_text match, type, scope
|
1396
|
+
if match[0].start_with? RS
|
1397
|
+
if scope == :constrained && (attrs = match[2])
|
1398
|
+
unescaped_attrs = %([#{attrs}])
|
1453
1399
|
else
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1400
|
+
return match[0].slice 1, match[0].length
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
if scope == :constrained
|
1405
|
+
if unescaped_attrs
|
1406
|
+
%(#{unescaped_attrs}#{Inline.new(self, :quoted, match[3], type: type).convert})
|
1407
|
+
else
|
1408
|
+
if (attrlist = match[2])
|
1409
|
+
id = (attributes = parse_quoted_text_attributes attrlist)['id']
|
1410
|
+
type = :unquoted if type == :mark
|
1458
1411
|
end
|
1412
|
+
%(#{match[1]}#{Inline.new(self, :quoted, match[3], type: type, id: id, attributes: attributes).convert})
|
1413
|
+
end
|
1414
|
+
else
|
1415
|
+
if (attrlist = match[1])
|
1416
|
+
id = (attributes = parse_quoted_text_attributes attrlist)['id']
|
1417
|
+
type = :unquoted if type == :mark
|
1459
1418
|
end
|
1419
|
+
Inline.new(self, :quoted, match[2], type: type, id: id, attributes: attributes).convert
|
1460
1420
|
end
|
1461
|
-
lines.sort.uniq
|
1462
1421
|
end
|
1463
1422
|
|
1464
|
-
#
|
1465
|
-
#
|
1466
|
-
# source - the source code String on which to apply verbatim substitutions
|
1467
|
-
# process_callouts - a Boolean flag indicating whether callout marks should be substituted
|
1423
|
+
# Internal: Substitute replacement text for matched location
|
1468
1424
|
#
|
1469
|
-
# returns the substituted
|
1470
|
-
def
|
1471
|
-
|
1425
|
+
# returns The String text with the replacement characters substituted
|
1426
|
+
def do_replacement m, replacement, restore
|
1427
|
+
if (captured = m[0]).include? RS
|
1428
|
+
# we have to use sub since we aren't sure it's the first char
|
1429
|
+
captured.sub RS, ''
|
1430
|
+
else
|
1431
|
+
case restore
|
1432
|
+
when :none
|
1433
|
+
replacement
|
1434
|
+
when :bounding
|
1435
|
+
m[1] + replacement + m[2]
|
1436
|
+
else # :leading
|
1437
|
+
m[1] + replacement
|
1438
|
+
end
|
1439
|
+
end
|
1472
1440
|
end
|
1473
1441
|
|
1474
1442
|
# Internal: Inserts text into a formatted text enclosure; used by xreftext
|
1475
1443
|
alias sub_placeholder sprintf unless RUBY_ENGINE == 'opal'
|
1476
1444
|
|
1477
|
-
# Internal:
|
1445
|
+
# Internal: Parse the attributes that are defined on quoted (aka formatted) text
|
1478
1446
|
#
|
1479
|
-
#
|
1480
|
-
#
|
1481
|
-
# this block. Otherwise, uses the substitutions assigned to the default_subs
|
1482
|
-
# property, if specified, or selects a default set of substitutions based on
|
1483
|
-
# the content model of the block.
|
1447
|
+
# str - A non-nil String of unprocessed attributes;
|
1448
|
+
# space-separated roles (e.g., role1 role2) or the id/role shorthand syntax (e.g., #idname.role)
|
1484
1449
|
#
|
1485
|
-
# Returns
|
1486
|
-
def
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1450
|
+
# Returns a Hash of attributes (role and id only)
|
1451
|
+
def parse_quoted_text_attributes str
|
1452
|
+
return {} if (str = str.rstrip).empty?
|
1453
|
+
# NOTE attributes are typically resolved after quoted text, so substitute eagerly
|
1454
|
+
str = sub_attributes str if str.include? ATTR_REF_HEAD
|
1455
|
+
# for compliance, only consider first positional attribute (very unlikely)
|
1456
|
+
str = str.slice 0, (str.index ',') if str.include? ','
|
1457
|
+
|
1458
|
+
if (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
|
1459
|
+
segments = str.split '#', 2
|
1460
|
+
|
1461
|
+
if segments.size > 1
|
1462
|
+
id, *more_roles = segments[1].split('.')
|
1497
1463
|
else
|
1498
|
-
|
1464
|
+
more_roles = []
|
1499
1465
|
end
|
1500
|
-
end
|
1501
1466
|
|
1502
|
-
|
1503
|
-
|
1467
|
+
roles = segments[0].empty? ? [] : segments[0].split('.')
|
1468
|
+
if roles.size > 1
|
1469
|
+
roles.shift
|
1470
|
+
end
|
1471
|
+
|
1472
|
+
if more_roles.size > 0
|
1473
|
+
roles.concat more_roles
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
attrs = {}
|
1477
|
+
attrs['id'] = id if id
|
1478
|
+
attrs['role'] = roles.join ' ' unless roles.empty?
|
1479
|
+
attrs
|
1504
1480
|
else
|
1505
|
-
|
1481
|
+
{ 'role' => str }
|
1506
1482
|
end
|
1483
|
+
end
|
1507
1484
|
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1485
|
+
# Internal: Normalize text to prepare it for parsing.
|
1486
|
+
#
|
1487
|
+
# If normalize_whitespace is true, strip surrounding whitespace and fold newlines. If unescape_closing_square_bracket
|
1488
|
+
# is set, unescape any escaped closing square brackets.
|
1489
|
+
#
|
1490
|
+
# Returns the normalized text String
|
1491
|
+
def normalize_text text, normalize_whitespace = nil, unescape_closing_square_brackets = nil
|
1492
|
+
unless text.empty?
|
1493
|
+
text = text.strip.tr LF, ' ' if normalize_whitespace
|
1494
|
+
text = text.gsub ESC_R_SB, R_SB if unescape_closing_square_brackets && (text.include? R_SB)
|
1512
1495
|
end
|
1496
|
+
text
|
1497
|
+
end
|
1513
1498
|
|
1514
|
-
|
1499
|
+
# Internal: Split text formatted as CSV with support
|
1500
|
+
# for double-quoted values (in which commas are ignored)
|
1501
|
+
def split_simple_csv str
|
1502
|
+
if str.empty?
|
1503
|
+
[]
|
1504
|
+
elsif str.include? '"'
|
1505
|
+
values = []
|
1506
|
+
accum = ''
|
1507
|
+
quote_open = nil
|
1508
|
+
str.each_char do |c|
|
1509
|
+
case c
|
1510
|
+
when ','
|
1511
|
+
if quote_open
|
1512
|
+
accum = accum + c
|
1513
|
+
else
|
1514
|
+
values << accum.strip
|
1515
|
+
accum = ''
|
1516
|
+
end
|
1517
|
+
when '"'
|
1518
|
+
quote_open = !quote_open
|
1519
|
+
else
|
1520
|
+
accum = accum + c
|
1521
|
+
end
|
1522
|
+
end
|
1523
|
+
values << accum.strip
|
1524
|
+
else
|
1525
|
+
str.split(',').map {|it| it.strip }
|
1526
|
+
end
|
1515
1527
|
end
|
1516
1528
|
end
|
1517
1529
|
end
|