asciidoctor 2.0.0.rc.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|