asciidoctor 1.5.8 → 2.0.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.adoc +628 -45
- data/LICENSE +2 -1
- data/README-de.adoc +28 -38
- data/README-fr.adoc +30 -43
- data/README-jp.adoc +255 -201
- data/README-zh_CN.adoc +40 -44
- data/README.adoc +170 -143
- data/asciidoctor.gemspec +22 -34
- data/bin/asciidoctor +5 -4
- data/data/locale/attributes-ar.adoc +4 -3
- data/data/locale/attributes-be.adoc +23 -0
- data/data/locale/attributes-bg.adoc +4 -3
- data/data/locale/attributes-ca.adoc +6 -5
- data/data/locale/attributes-cs.adoc +4 -3
- data/data/locale/attributes-da.adoc +6 -5
- data/data/locale/attributes-de.adoc +6 -5
- data/data/locale/attributes-en.adoc +4 -4
- data/data/locale/attributes-es.adoc +6 -5
- data/data/locale/attributes-fa.adoc +4 -3
- data/data/locale/attributes-fi.adoc +4 -3
- data/data/locale/attributes-fr.adoc +8 -7
- data/data/locale/attributes-hu.adoc +4 -3
- data/data/locale/attributes-id.adoc +4 -3
- data/data/locale/attributes-it.adoc +6 -5
- data/data/locale/attributes-ja.adoc +4 -3
- data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
- data/data/locale/attributes-nb.adoc +4 -3
- data/data/locale/attributes-nl.adoc +6 -5
- data/data/locale/attributes-nn.adoc +4 -3
- data/data/locale/attributes-pl.adoc +8 -7
- data/data/locale/attributes-pt.adoc +6 -5
- data/data/locale/attributes-pt_BR.adoc +6 -5
- data/data/locale/attributes-ro.adoc +4 -3
- data/data/locale/attributes-ru.adoc +6 -5
- data/data/locale/attributes-sr.adoc +4 -4
- data/data/locale/attributes-sr_Latn.adoc +4 -4
- data/data/locale/attributes-sv.adoc +4 -4
- data/data/locale/attributes-th.adoc +23 -0
- data/data/locale/attributes-tr.adoc +4 -3
- data/data/locale/attributes-uk.adoc +6 -5
- data/data/locale/attributes-vi.adoc +23 -0
- data/data/locale/attributes-zh_CN.adoc +4 -3
- data/data/locale/attributes-zh_TW.adoc +4 -3
- data/data/reference/syntax.adoc +296 -0
- data/data/stylesheets/asciidoctor-default.css +120 -114
- data/data/stylesheets/coderay-asciidoctor.css +15 -17
- data/lib/asciidoctor/abstract_block.rb +146 -140
- data/lib/asciidoctor/abstract_node.rb +152 -170
- data/lib/asciidoctor/attribute_list.rb +77 -89
- data/lib/asciidoctor/block.rb +29 -28
- data/lib/asciidoctor/callouts.rb +4 -2
- data/lib/asciidoctor/cli/invoker.rb +20 -24
- data/lib/asciidoctor/cli/options.rb +107 -96
- data/lib/asciidoctor/cli.rb +3 -2
- data/lib/asciidoctor/convert.rb +199 -0
- data/lib/asciidoctor/converter/composite.rb +40 -48
- data/lib/asciidoctor/converter/docbook5.rb +627 -644
- data/lib/asciidoctor/converter/html5.rb +1053 -951
- data/lib/asciidoctor/converter/manpage.rb +581 -532
- data/lib/asciidoctor/converter/template.rb +232 -271
- data/lib/asciidoctor/converter.rb +370 -185
- data/lib/asciidoctor/core_ext/float/truncate.rb +20 -0
- data/lib/asciidoctor/core_ext/hash/merge.rb +8 -0
- data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
- data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
- data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
- data/lib/asciidoctor/core_ext.rb +8 -17
- data/lib/asciidoctor/document.rb +503 -461
- data/lib/asciidoctor/extensions.rb +127 -174
- data/lib/asciidoctor/helpers.rb +184 -107
- data/lib/asciidoctor/inline.rb +9 -12
- data/lib/asciidoctor/list.rb +11 -29
- data/lib/asciidoctor/load.rb +119 -0
- data/lib/asciidoctor/logging.rb +22 -17
- data/lib/asciidoctor/parser.rb +673 -719
- data/lib/asciidoctor/path_resolver.rb +48 -33
- data/lib/asciidoctor/reader.rb +383 -338
- data/lib/asciidoctor/rouge_ext.rb +39 -0
- data/lib/asciidoctor/rx.rb +723 -0
- data/lib/asciidoctor/section.rb +17 -16
- data/lib/asciidoctor/stylesheets.rb +19 -37
- data/lib/asciidoctor/substitutors.rb +926 -1022
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +88 -0
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +34 -0
- data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
- data/lib/asciidoctor/syntax_highlighter/prettify.rb +30 -0
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +157 -0
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +143 -0
- data/lib/asciidoctor/syntax_highlighter.rb +253 -0
- data/lib/asciidoctor/table.rb +152 -114
- data/lib/asciidoctor/timings.rb +7 -5
- data/lib/asciidoctor/version.rb +2 -1
- data/lib/asciidoctor/writer.rb +30 -0
- data/lib/asciidoctor.rb +266 -1340
- data/man/asciidoctor.1 +49 -47
- data/man/asciidoctor.adoc +54 -45
- metadata +50 -245
- data/CONTRIBUTING.adoc +0 -185
- data/Gemfile +0 -60
- data/Rakefile +0 -129
- data/bin/asciidoctor-safe +0 -15
- data/features/open_block.feature +0 -92
- data/features/pass_block.feature +0 -66
- data/features/step_definitions.rb +0 -49
- data/features/text_formatting.feature +0 -57
- data/features/xref.feature +0 -1039
- data/lib/asciidoctor/converter/base.rb +0 -59
- data/lib/asciidoctor/converter/docbook45.rb +0 -93
- data/lib/asciidoctor/converter/factory.rb +0 -226
- data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
- data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
- data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
- data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
- data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
- data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
- data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
- data/test/api_test.rb +0 -1240
- data/test/attribute_list_test.rb +0 -242
- data/test/attributes_test.rb +0 -1623
- data/test/blocks_test.rb +0 -3870
- data/test/converter_test.rb +0 -470
- data/test/document_test.rb +0 -1853
- data/test/extensions_test.rb +0 -1560
- data/test/fixtures/asciidoc_index.txt +0 -521
- data/test/fixtures/basic-docinfo-footer.html +0 -6
- data/test/fixtures/basic-docinfo-footer.xml +0 -8
- data/test/fixtures/basic-docinfo.html +0 -1
- data/test/fixtures/basic-docinfo.xml +0 -4
- data/test/fixtures/basic.asciidoc +0 -5
- data/test/fixtures/chapter-a.adoc +0 -3
- data/test/fixtures/child-include.adoc +0 -5
- data/test/fixtures/circle.svg +0 -9
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
- data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
- data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
- data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
- data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
- data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
- data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
- data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
- data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
- data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
- data/test/fixtures/docinfo-footer.html +0 -1
- data/test/fixtures/docinfo-footer.xml +0 -9
- data/test/fixtures/docinfo.html +0 -1
- data/test/fixtures/docinfo.xml +0 -3
- data/test/fixtures/doctime-localtime.adoc +0 -2
- data/test/fixtures/dot.gif +0 -0
- data/test/fixtures/encoding.asciidoc +0 -13
- data/test/fixtures/file-with-missing-include.adoc +0 -1
- data/test/fixtures/grandchild-include.adoc +0 -3
- data/test/fixtures/hello-asciidoctor.pdf +0 -69
- data/test/fixtures/include-file.asciidoc +0 -24
- data/test/fixtures/include-file.jsx +0 -8
- data/test/fixtures/include-file.ml +0 -3
- data/test/fixtures/include-file.xml +0 -5
- data/test/fixtures/lists.adoc +0 -96
- data/test/fixtures/master.adoc +0 -5
- data/test/fixtures/mismatched-end-tag.adoc +0 -7
- data/test/fixtures/other-chapters.adoc +0 -11
- data/test/fixtures/outer-include.adoc +0 -5
- data/test/fixtures/parent-include-restricted.adoc +0 -5
- data/test/fixtures/parent-include.adoc +0 -5
- data/test/fixtures/sample.asciidoc +0 -30
- data/test/fixtures/section-a.adoc +0 -4
- data/test/fixtures/stylesheets/custom.css +0 -3
- data/test/fixtures/subdir/index.adoc +0 -3
- data/test/fixtures/subdir/inner-include.adoc +0 -3
- data/test/fixtures/subdir/middle-include.adoc +0 -5
- data/test/fixtures/subs-docinfo.html +0 -2
- data/test/fixtures/subs.adoc +0 -6
- data/test/fixtures/tagged-class-enclosed.rb +0 -25
- data/test/fixtures/tagged-class.rb +0 -23
- data/test/fixtures/tip.gif +0 -0
- data/test/fixtures/unclosed-tag.adoc +0 -3
- data/test/fixtures/unexpected-end-tag.adoc +0 -4
- data/test/invoker_test.rb +0 -745
- data/test/links_test.rb +0 -855
- data/test/lists_test.rb +0 -5151
- data/test/logger_test.rb +0 -211
- data/test/manpage_test.rb +0 -660
- data/test/options_test.rb +0 -262
- data/test/paragraphs_test.rb +0 -562
- data/test/parser_test.rb +0 -742
- data/test/paths_test.rb +0 -395
- data/test/preamble_test.rb +0 -173
- data/test/reader_test.rb +0 -2161
- data/test/sections_test.rb +0 -3575
- data/test/substitutions_test.rb +0 -2066
- data/test/tables_test.rb +0 -2036
- data/test/test_helper.rb +0 -447
- data/test/text_test.rb +0 -309
@@ -1,7 +1,7 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
module Asciidoctor
|
3
3
|
# Public: Methods to perform substitutions on lines of AsciiDoc text. This module
|
4
|
-
# is
|
4
|
+
# is intended to be mixed-in to Section and Block to provide operations for performing
|
5
5
|
# the necessary substitutions.
|
6
6
|
module Substitutors
|
7
7
|
SpecialCharsRx = /[<&>]/
|
@@ -12,55 +12,45 @@ module Substitutors
|
|
12
12
|
|
13
13
|
(BASIC_SUBS = [:specialcharacters]).freeze
|
14
14
|
(HEADER_SUBS = [:specialcharacters, :attributes]).freeze
|
15
|
+
(NO_SUBS = []).freeze
|
15
16
|
(NORMAL_SUBS = [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements]).freeze
|
16
|
-
(NONE_SUBS = []).freeze
|
17
|
-
(TITLE_SUBS = [:specialcharacters, :quotes, :replacements, :macros, :attributes, :post_replacements]).freeze
|
18
17
|
(REFTEXT_SUBS = [:specialcharacters, :quotes, :replacements]).freeze
|
19
18
|
(VERBATIM_SUBS = [:specialcharacters, :callouts]).freeze
|
20
19
|
|
21
20
|
SUB_GROUPS = {
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
21
|
+
none: NO_SUBS,
|
22
|
+
normal: NORMAL_SUBS,
|
23
|
+
verbatim: VERBATIM_SUBS,
|
24
|
+
specialchars: BASIC_SUBS,
|
26
25
|
}
|
27
26
|
|
28
27
|
SUB_HINTS = {
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
28
|
+
a: :attributes,
|
29
|
+
m: :macros,
|
30
|
+
n: :normal,
|
31
|
+
p: :post_replacements,
|
32
|
+
q: :quotes,
|
33
|
+
r: :replacements,
|
34
|
+
c: :specialcharacters,
|
35
|
+
v: :verbatim,
|
37
36
|
}
|
38
37
|
|
39
38
|
SUB_OPTIONS = {
|
40
|
-
:
|
41
|
-
:
|
39
|
+
block: SUB_GROUPS.keys + NORMAL_SUBS + [:callouts],
|
40
|
+
inline: SUB_GROUPS.keys + NORMAL_SUBS,
|
42
41
|
}
|
43
42
|
|
44
|
-
|
43
|
+
CAN = ?\u0018
|
44
|
+
DEL = ?\u007f
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
DEL = %(\u007f)
|
46
|
+
# Delimiters and matchers for the passthrough placeholder
|
47
|
+
# See http://www.aivosto.com/vbtips/control-characters.html#listabout for characters to use
|
49
48
|
|
50
|
-
|
51
|
-
|
49
|
+
# SPA, start of guarded protected area (\u0096)
|
50
|
+
PASS_START = ?\u0096
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# EPA, end of guarded protected area (\u0097)
|
57
|
-
PASS_END = %(\u0097)
|
58
|
-
else
|
59
|
-
CAN = 24.chr
|
60
|
-
DEL = 127.chr
|
61
|
-
PASS_START = 150.chr
|
62
|
-
PASS_END = 151.chr
|
63
|
-
end
|
52
|
+
# EPA, end of guarded protected area (\u0097)
|
53
|
+
PASS_END = ?\u0097
|
64
54
|
|
65
55
|
# match passthrough slot
|
66
56
|
PassSlotRx = /#{PASS_START}(\d+)#{PASS_END}/
|
@@ -76,14 +66,6 @@ module Substitutors
|
|
76
66
|
|
77
67
|
PLUS = '+'
|
78
68
|
|
79
|
-
PygmentsWrapperDivRx = %r(<div class="pyhl">(.*)</div>)m
|
80
|
-
# NOTE handles all permutations of <pre> wrapper
|
81
|
-
# NOTE trailing whitespace appears when pygments-linenums-mode=table; <pre> has style attribute when pygments-css=inline
|
82
|
-
PygmentsWrapperPreRx = %r(<pre\b[^>]*?>(.*?)</pre>\s*)m
|
83
|
-
|
84
|
-
# Internal: A String Array of passthough (unprocessed) text captured from this block
|
85
|
-
attr_reader :passthroughs
|
86
|
-
|
87
69
|
# Public: Apply the specified substitutions to the text.
|
88
70
|
#
|
89
71
|
# text - The String or String Array of text to process; must not be nil.
|
@@ -93,14 +75,17 @@ module Substitutors
|
|
93
75
|
def apply_subs text, subs = NORMAL_SUBS
|
94
76
|
return text if text.empty? || !subs
|
95
77
|
|
96
|
-
if (
|
97
|
-
#text = text.size > 1 ? (text.join LF) : text[0]
|
78
|
+
if (is_multiline = ::Array === text)
|
98
79
|
text = text[1] ? (text.join LF) : text[0]
|
99
80
|
end
|
100
81
|
|
101
|
-
if
|
82
|
+
if subs.include? :macros
|
102
83
|
text = extract_passthroughs text
|
103
|
-
|
84
|
+
unless @passthroughs.empty?
|
85
|
+
passthrus = @passthroughs
|
86
|
+
# NOTE placeholders can move around, so we can only clear in the outermost substitution call
|
87
|
+
@passthroughs_locked ||= (clear_passthrus = true)
|
88
|
+
end
|
104
89
|
end
|
105
90
|
|
106
91
|
subs.each do |type|
|
@@ -125,9 +110,16 @@ module Substitutors
|
|
125
110
|
logger.warn %(unknown substitution type #{type})
|
126
111
|
end
|
127
112
|
end
|
128
|
-
text = restore_passthroughs text if has_passthroughs
|
129
113
|
|
130
|
-
|
114
|
+
if passthrus
|
115
|
+
text = restore_passthroughs text
|
116
|
+
if clear_passthrus
|
117
|
+
passthrus.clear
|
118
|
+
@passthroughs_locked = nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
is_multiline ? (text.split LF, -1) : text
|
131
123
|
end
|
132
124
|
|
133
125
|
# Public: Apply normal substitutions.
|
@@ -138,316 +130,69 @@ module Substitutors
|
|
138
130
|
#
|
139
131
|
# Returns the String with normal substitutions applied.
|
140
132
|
def apply_normal_subs text
|
141
|
-
apply_subs text
|
142
|
-
end
|
143
|
-
|
144
|
-
# Public: Apply substitutions for titles.
|
145
|
-
#
|
146
|
-
# title - The String title to process
|
147
|
-
#
|
148
|
-
# returns - A String with title substitutions performed
|
149
|
-
def apply_title_subs(title)
|
150
|
-
apply_subs title, TITLE_SUBS
|
151
|
-
end
|
152
|
-
|
153
|
-
# Public: Apply substitutions for reftext.
|
154
|
-
#
|
155
|
-
# text - The String to process
|
156
|
-
#
|
157
|
-
# Returns a String with all substitutions from the reftext substitution group applied
|
158
|
-
def apply_reftext_subs text
|
159
|
-
apply_subs text, REFTEXT_SUBS
|
133
|
+
apply_subs text, NORMAL_SUBS
|
160
134
|
end
|
161
135
|
|
162
136
|
# Public: Apply substitutions for header metadata and attribute assignments
|
163
137
|
#
|
164
138
|
# text - String containing the text process
|
165
139
|
#
|
166
|
-
#
|
167
|
-
def apply_header_subs
|
140
|
+
# Returns A String with header substitutions performed
|
141
|
+
def apply_header_subs text
|
168
142
|
apply_subs text, HEADER_SUBS
|
169
143
|
end
|
170
144
|
|
171
|
-
#
|
145
|
+
# Public: Apply substitutions for titles.
|
172
146
|
#
|
173
|
-
#
|
147
|
+
# title - The String title to process
|
174
148
|
#
|
175
|
-
#
|
176
|
-
|
177
|
-
compat_mode = @document.compat_mode
|
178
|
-
passes = @passthroughs
|
179
|
-
text = text.gsub(InlinePassMacroRx) {
|
180
|
-
# alias match for Ruby 1.8.7 compat
|
181
|
-
m = $~
|
182
|
-
preceding = nil
|
183
|
-
|
184
|
-
if (boundary = m[4]) # $$, ++, or +++
|
185
|
-
# skip ++ in compat mode, handled as normal quoted text
|
186
|
-
if compat_mode && boundary == '++'
|
187
|
-
next m[2] ?
|
188
|
-
%(#{m[1]}[#{m[2]}]#{m[3]}++#{extract_passthroughs m[5]}++) :
|
189
|
-
%(#{m[1]}#{m[3]}++#{extract_passthroughs m[5]}++)
|
190
|
-
end
|
191
|
-
|
192
|
-
attributes = m[2]
|
193
|
-
escape_count = m[3].length
|
194
|
-
content = m[5]
|
195
|
-
old_behavior = false
|
196
|
-
|
197
|
-
if attributes
|
198
|
-
if escape_count > 0
|
199
|
-
# NOTE we don't look for nested unconstrained pass macros
|
200
|
-
next %(#{m[1]}[#{attributes}]#{RS * (escape_count - 1)}#{boundary}#{m[5]}#{boundary})
|
201
|
-
elsif m[1] == RS
|
202
|
-
preceding = %([#{attributes}])
|
203
|
-
attributes = nil
|
204
|
-
else
|
205
|
-
if boundary == '++' && (attributes.end_with? 'x-')
|
206
|
-
old_behavior = true
|
207
|
-
attributes = attributes.slice 0, attributes.length - 2
|
208
|
-
end
|
209
|
-
attributes = parse_quoted_text_attributes attributes
|
210
|
-
end
|
211
|
-
elsif escape_count > 0
|
212
|
-
# NOTE we don't look for nested unconstrained pass macros
|
213
|
-
next %(#{RS * (escape_count - 1)}#{boundary}#{m[5]}#{boundary})
|
214
|
-
end
|
215
|
-
subs = (boundary == '+++' ? [] : BASIC_SUBS)
|
216
|
-
|
217
|
-
pass_key = passes.size
|
218
|
-
if attributes
|
219
|
-
if old_behavior
|
220
|
-
passes[pass_key] = {:text => content, :subs => NORMAL_SUBS, :type => :monospaced, :attributes => attributes}
|
221
|
-
else
|
222
|
-
passes[pass_key] = {:text => content, :subs => subs, :type => :unquoted, :attributes => attributes}
|
223
|
-
end
|
224
|
-
else
|
225
|
-
passes[pass_key] = {:text => content, :subs => subs}
|
226
|
-
end
|
227
|
-
else # pass:[]
|
228
|
-
if m[6] == RS
|
229
|
-
# NOTE we don't look for nested pass:[] macros
|
230
|
-
next m[0].slice 1, m[0].length
|
231
|
-
end
|
232
|
-
|
233
|
-
passes[pass_key = passes.size] = {:text => (unescape_brackets m[8]), :subs => (m[7] ? (resolve_pass_subs m[7]) : nil)}
|
234
|
-
end
|
235
|
-
|
236
|
-
%(#{preceding}#{PASS_START}#{pass_key}#{PASS_END})
|
237
|
-
} if (text.include? '++') || (text.include? '$$') || (text.include? 'ss:')
|
238
|
-
|
239
|
-
pass_inline_char1, pass_inline_char2, pass_inline_rx = InlinePassRx[compat_mode]
|
240
|
-
text = text.gsub(pass_inline_rx) {
|
241
|
-
# alias match for Ruby 1.8.7 compat
|
242
|
-
m = $~
|
243
|
-
preceding = m[1]
|
244
|
-
attributes = m[2]
|
245
|
-
escape_mark = RS if (quoted_text = m[3]).start_with? RS
|
246
|
-
format_mark = m[4]
|
247
|
-
content = m[5]
|
248
|
-
|
249
|
-
if compat_mode
|
250
|
-
old_behavior = true
|
251
|
-
else
|
252
|
-
if (old_behavior = (attributes && (attributes.end_with? 'x-')))
|
253
|
-
attributes = attributes.slice 0, attributes.length - 2
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
if attributes
|
258
|
-
if format_mark == '`' && !old_behavior
|
259
|
-
# extract nested single-plus passthrough; otherwise return unprocessed
|
260
|
-
next (extract_inner_passthrough content, %(#{preceding}[#{attributes}]#{escape_mark}), attributes)
|
261
|
-
end
|
149
|
+
# Returns A String with title substitutions performed
|
150
|
+
alias apply_title_subs apply_subs
|
262
151
|
|
263
|
-
|
264
|
-
# honor the escape of the formatting mark
|
265
|
-
next %(#{preceding}[#{attributes}]#{quoted_text.slice 1, quoted_text.length})
|
266
|
-
elsif preceding == RS
|
267
|
-
# honor the escape of the attributes
|
268
|
-
preceding = %([#{attributes}])
|
269
|
-
attributes = nil
|
270
|
-
else
|
271
|
-
attributes = parse_quoted_text_attributes attributes
|
272
|
-
end
|
273
|
-
elsif format_mark == '`' && !old_behavior
|
274
|
-
# extract nested single-plus passthrough; otherwise return unprocessed
|
275
|
-
next (extract_inner_passthrough content, %(#{preceding}#{escape_mark}))
|
276
|
-
elsif escape_mark
|
277
|
-
# honor the escape of the formatting mark
|
278
|
-
next %(#{preceding}#{quoted_text.slice 1, quoted_text.length})
|
279
|
-
end
|
280
|
-
|
281
|
-
pass_key = passes.size
|
282
|
-
if compat_mode
|
283
|
-
passes[pass_key] = {:text => content, :subs => BASIC_SUBS, :attributes => attributes, :type => :monospaced}
|
284
|
-
elsif attributes
|
285
|
-
if old_behavior
|
286
|
-
subs = (format_mark == '`' ? BASIC_SUBS : NORMAL_SUBS)
|
287
|
-
passes[pass_key] = {:text => content, :subs => subs, :attributes => attributes, :type => :monospaced}
|
288
|
-
else
|
289
|
-
passes[pass_key] = {:text => content, :subs => BASIC_SUBS, :attributes => attributes, :type => :unquoted}
|
290
|
-
end
|
291
|
-
else
|
292
|
-
passes[pass_key] = {:text => content, :subs => BASIC_SUBS}
|
293
|
-
end
|
294
|
-
|
295
|
-
%(#{preceding}#{PASS_START}#{pass_key}#{PASS_END})
|
296
|
-
} if (text.include? pass_inline_char1) || (pass_inline_char2 && (text.include? pass_inline_char2))
|
297
|
-
|
298
|
-
# NOTE we need to do the stem in a subsequent step to allow it to be escaped by the former
|
299
|
-
text = text.gsub(InlineStemMacroRx) {
|
300
|
-
# alias match for Ruby 1.8.7 compat
|
301
|
-
m = $~
|
302
|
-
# honor the escape
|
303
|
-
if $&.start_with? RS
|
304
|
-
next m[0].slice 1, m[0].length
|
305
|
-
end
|
306
|
-
|
307
|
-
if (type = m[1].to_sym) == :stem
|
308
|
-
type = STEM_TYPE_ALIASES[@document.attributes['stem']].to_sym
|
309
|
-
end
|
310
|
-
content = unescape_brackets m[3]
|
311
|
-
subs = m[2] ? (resolve_pass_subs m[2]) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
|
312
|
-
passes[pass_key = passes.size] = {:text => content, :subs => subs, :type => type}
|
313
|
-
%(#{PASS_START}#{pass_key}#{PASS_END})
|
314
|
-
} if (text.include? ':') && ((text.include? 'stem:') || (text.include? 'math:'))
|
315
|
-
|
316
|
-
text
|
317
|
-
end
|
318
|
-
|
319
|
-
def extract_inner_passthrough text, pre, attributes = nil
|
320
|
-
if (text.end_with? '+') && (text.start_with? '+', '\+') && SinglePlusInlinePassRx =~ text
|
321
|
-
if $1
|
322
|
-
%(#{pre}`+#{$2}+`)
|
323
|
-
else
|
324
|
-
@passthroughs[pass_key = @passthroughs.size] = attributes ?
|
325
|
-
{ :text => $2, :subs => BASIC_SUBS, :attributes => attributes, :type => :unquoted } :
|
326
|
-
{ :text => $2, :subs => BASIC_SUBS }
|
327
|
-
%(#{pre}`#{PASS_START}#{pass_key}#{PASS_END}`)
|
328
|
-
end
|
329
|
-
else
|
330
|
-
%(#{pre}`#{text}`)
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
# Internal: Restore the passthrough text by reinserting into the placeholder positions
|
152
|
+
# Public: Apply substitutions for reftext.
|
335
153
|
#
|
336
|
-
# text
|
337
|
-
# outer - A Boolean indicating whether we are in the outer call (default: true)
|
154
|
+
# text - The String to process
|
338
155
|
#
|
339
|
-
#
|
340
|
-
def
|
341
|
-
|
342
|
-
# passthroughs may have been eagerly restored (e.g., footnotes)
|
343
|
-
#if outer && (passes.empty? || !text.include?(PASS_START))
|
344
|
-
# return text
|
345
|
-
#end
|
346
|
-
|
347
|
-
text.gsub(PassSlotRx) {
|
348
|
-
# NOTE we can't remove entry from map because placeholder may have been duplicated by other substitutions
|
349
|
-
pass = passes[$1.to_i]
|
350
|
-
subbed_text = apply_subs(pass[:text], pass[:subs])
|
351
|
-
if (type = pass[:type])
|
352
|
-
subbed_text = Inline.new(self, :quoted, subbed_text, :type => type, :attributes => pass[:attributes]).convert
|
353
|
-
end
|
354
|
-
subbed_text.include?(PASS_START) ? restore_passthroughs(subbed_text, false) : subbed_text
|
355
|
-
}
|
356
|
-
ensure
|
357
|
-
# free memory if in outer call...we don't need these anymore
|
358
|
-
passes.clear if outer
|
359
|
-
end
|
360
|
-
|
361
|
-
if RUBY_ENGINE == 'opal'
|
362
|
-
def sub_quotes text
|
363
|
-
if QuotedTextSniffRx[compat = @document.compat_mode].match? text
|
364
|
-
QUOTE_SUBS[compat].each do |type, scope, pattern|
|
365
|
-
text = text.gsub(pattern) { convert_quoted_text $~, type, scope }
|
366
|
-
end
|
367
|
-
end
|
368
|
-
text
|
369
|
-
end
|
370
|
-
|
371
|
-
def sub_replacements text
|
372
|
-
if ReplaceableTextRx.match? text
|
373
|
-
REPLACEMENTS.each do |pattern, replacement, restore|
|
374
|
-
text = text.gsub(pattern) { do_replacement $~, replacement, restore }
|
375
|
-
end
|
376
|
-
end
|
377
|
-
text
|
378
|
-
end
|
379
|
-
else
|
380
|
-
# Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
|
381
|
-
#
|
382
|
-
# text - The String text to process
|
383
|
-
#
|
384
|
-
# returns The converted String text
|
385
|
-
def sub_quotes text
|
386
|
-
if QuotedTextSniffRx[compat = @document.compat_mode].match? text
|
387
|
-
# NOTE interpolation is faster than String#dup
|
388
|
-
text = %(#{text})
|
389
|
-
QUOTE_SUBS[compat].each do |type, scope, pattern|
|
390
|
-
# NOTE using gsub! here as an MRI Ruby optimization
|
391
|
-
text.gsub!(pattern) { convert_quoted_text $~, type, scope }
|
392
|
-
end
|
393
|
-
end
|
394
|
-
text
|
395
|
-
end
|
396
|
-
|
397
|
-
# Public: Substitute replacement characters (e.g., copyright, trademark, etc)
|
398
|
-
#
|
399
|
-
# text - The String text to process
|
400
|
-
#
|
401
|
-
# returns The String text with the replacement characters substituted
|
402
|
-
def sub_replacements text
|
403
|
-
if ReplaceableTextRx.match? text
|
404
|
-
# NOTE interpolation is faster than String#dup
|
405
|
-
text = %(#{text})
|
406
|
-
REPLACEMENTS.each do |pattern, replacement, restore|
|
407
|
-
# NOTE Using gsub! as optimization
|
408
|
-
text.gsub!(pattern) { do_replacement $~, replacement, restore }
|
409
|
-
end
|
410
|
-
end
|
411
|
-
text
|
412
|
-
end
|
156
|
+
# Returns a String with all substitutions from the reftext substitution group applied
|
157
|
+
def apply_reftext_subs text
|
158
|
+
apply_subs text, REFTEXT_SUBS
|
413
159
|
end
|
414
160
|
|
415
161
|
# Public: Substitute special characters (i.e., encode XML)
|
416
162
|
#
|
417
|
-
# The special characters <, &, and > get replaced with <,
|
418
|
-
# &, and >, respectively.
|
163
|
+
# The special characters <, &, and > get replaced with <, &, and >, respectively.
|
419
164
|
#
|
420
165
|
# text - The String text to process.
|
421
166
|
#
|
422
|
-
#
|
423
|
-
if
|
167
|
+
# Returns The String text with special characters replaced.
|
168
|
+
if RUBY_ENGINE == 'opal'
|
424
169
|
def sub_specialchars text
|
425
|
-
(text.include?
|
170
|
+
(text.include? ?>) || (text.include? ?&) || (text.include? ?<) ? (text.gsub SpecialCharsRx, SpecialCharsTr) : text
|
426
171
|
end
|
427
172
|
else
|
173
|
+
CGI = ::CGI
|
428
174
|
def sub_specialchars text
|
429
|
-
(text.include?
|
175
|
+
if (text.include? ?>) || (text.include? ?&) || (text.include? ?<)
|
176
|
+
(text.include? ?') || (text.include? ?") ? (text.gsub SpecialCharsRx, SpecialCharsTr) : (CGI.escape_html text)
|
177
|
+
else
|
178
|
+
text
|
179
|
+
end
|
430
180
|
end
|
431
181
|
end
|
432
182
|
alias sub_specialcharacters sub_specialchars
|
433
183
|
|
434
|
-
#
|
184
|
+
# Public: Substitute quoted text (includes emphasis, strong, monospaced, etc.)
|
435
185
|
#
|
436
|
-
#
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
when :none
|
444
|
-
replacement
|
445
|
-
when :bounding
|
446
|
-
%(#{m[1]}#{replacement}#{m[2]})
|
447
|
-
else # :leading
|
448
|
-
%(#{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 }
|
449
193
|
end
|
450
194
|
end
|
195
|
+
text
|
451
196
|
end
|
452
197
|
|
453
198
|
# Public: Substitutes attribute references in the specified text
|
@@ -459,12 +204,13 @@ module Substitutors
|
|
459
204
|
#
|
460
205
|
# text - The String text to process
|
461
206
|
# opts - A Hash of options to control processing: (default: {})
|
462
|
-
# * :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)
|
463
209
|
#
|
464
210
|
# Returns the [String] text with the attribute references replaced with resolved values
|
465
211
|
def sub_attributes text, opts = {}
|
466
212
|
doc_attrs = @document.attributes
|
467
|
-
drop = drop_line = drop_empty_line = attribute_undefined = attribute_missing = nil
|
213
|
+
drop = drop_line = drop_line_severity = drop_empty_line = attribute_undefined = attribute_missing = nil
|
468
214
|
text = text.gsub AttributeReferenceRx do
|
469
215
|
# escaped attribute, return unescaped
|
470
216
|
if $1 == RS || $4 == RS
|
@@ -474,7 +220,7 @@ module Substitutors
|
|
474
220
|
when 'set'
|
475
221
|
_, value = Parser.store_attribute args[0], args[1] || '', @document
|
476
222
|
# NOTE since this is an assignment, only drop-line applies here (skip and drop imply the same result)
|
477
|
-
if value || (attribute_undefined ||= doc_attrs['attribute-undefined'] || Compliance.attribute_undefined) != 'drop-line'
|
223
|
+
if value || (attribute_undefined ||= (doc_attrs['attribute-undefined'] || Compliance.attribute_undefined)) != 'drop-line'
|
478
224
|
drop = drop_empty_line = DEL
|
479
225
|
else
|
480
226
|
drop = drop_line = CAN
|
@@ -490,11 +236,15 @@ module Substitutors
|
|
490
236
|
elsif (value = INTRINSIC_ATTRIBUTES[key])
|
491
237
|
value
|
492
238
|
else
|
493
|
-
case (attribute_missing ||= opts[:attribute_missing] || doc_attrs['attribute-missing'] || Compliance.attribute_missing)
|
239
|
+
case (attribute_missing ||= (opts[:attribute_missing] || doc_attrs['attribute-missing'] || Compliance.attribute_missing))
|
494
240
|
when 'drop'
|
495
241
|
drop = drop_empty_line = DEL
|
496
242
|
when 'drop-line'
|
497
|
-
|
243
|
+
if (drop_line_severity ||= (opts[:drop_line_severity] || :info)) == :info
|
244
|
+
logger.info { %(dropping line containing reference to missing attribute: #{key}) }
|
245
|
+
#elsif drop_line_severity == :warn
|
246
|
+
# logger.warn %(dropping line containing reference to missing attribute: #{key})
|
247
|
+
end
|
498
248
|
drop = drop_line = CAN
|
499
249
|
when 'warn'
|
500
250
|
logger.warn %(skipping reference to missing attribute: #{key})
|
@@ -508,7 +258,7 @@ module Substitutors
|
|
508
258
|
if drop
|
509
259
|
# drop lines from text
|
510
260
|
if drop_empty_line
|
511
|
-
lines = (text.
|
261
|
+
lines = (text.squeeze DEL).split LF, -1
|
512
262
|
if drop_line
|
513
263
|
(lines.reject {|line| line == DEL || line == CAN || (line.start_with? CAN) || (line.include? CAN) }.join LF).delete DEL
|
514
264
|
else
|
@@ -524,6 +274,18 @@ module Substitutors
|
|
524
274
|
end
|
525
275
|
end
|
526
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
|
+
|
527
289
|
# Public: Substitute inline macros (e.g., links, images, etc)
|
528
290
|
#
|
529
291
|
# Replace inline macros, which may span multiple lines, in the provided text
|
@@ -531,19 +293,61 @@ module Substitutors
|
|
531
293
|
# source - The String text to process
|
532
294
|
#
|
533
295
|
# returns The converted String text
|
534
|
-
def sub_macros
|
296
|
+
def sub_macros text
|
535
297
|
#return text if text.nil_or_empty?
|
536
298
|
# some look ahead assertions to cut unnecessary regex calls
|
537
|
-
|
538
|
-
found_square_bracket = found[:square_bracket] = (text.include? '[')
|
299
|
+
found_square_bracket = text.include? '['
|
539
300
|
found_colon = text.include? ':'
|
540
|
-
found_macroish =
|
301
|
+
found_macroish = found_square_bracket && found_colon
|
541
302
|
found_macroish_short = found_macroish && (text.include? ':[')
|
542
303
|
doc_attrs = (doc = @document).attributes
|
543
304
|
|
305
|
+
# TODO allow position of substitution to be controlled (before or after other macros)
|
306
|
+
# TODO this handling needs some cleanup
|
307
|
+
if (extensions = doc.extensions) && extensions.inline_macros? # && found_macroish
|
308
|
+
extensions.inline_macros.each do |extension|
|
309
|
+
text = text.gsub extension.instance.regexp do
|
310
|
+
# honor the escape
|
311
|
+
next $&.slice 1, $&.length if (match = $&).start_with? RS
|
312
|
+
if $~.names.empty?
|
313
|
+
target, content = $1, $2
|
314
|
+
else
|
315
|
+
target, content = ($~[:target] rescue nil), ($~[:content] rescue nil)
|
316
|
+
end
|
317
|
+
attributes = (default_attrs = (ext_config = extension.config)[:default_attrs]) ? default_attrs.merge : {}
|
318
|
+
if content
|
319
|
+
if content.empty?
|
320
|
+
attributes['text'] = content unless ext_config[:content_model] == :attributes
|
321
|
+
else
|
322
|
+
content = normalize_text content, true, true
|
323
|
+
# QUESTION should we store the unparsed attrlist in the attrlist key?
|
324
|
+
if ext_config[:content_model] == :attributes
|
325
|
+
parse_attributes content, ext_config[:positional_attrs] || ext_config[:pos_attrs] || [], into: attributes
|
326
|
+
else
|
327
|
+
attributes['text'] = content
|
328
|
+
end
|
329
|
+
end
|
330
|
+
# NOTE for convenience, map content (unparsed attrlist) to target when format is short
|
331
|
+
target ||= ext_config[:format] == :short ? content : target
|
332
|
+
end
|
333
|
+
if Inline === (replacement = extension.process_method[self, target, attributes])
|
334
|
+
if (inline_subs = replacement.attributes.delete 'subs') && (inline_subs = expand_subs inline_subs, 'custom inline macro')
|
335
|
+
replacement.text = apply_subs replacement.text, inline_subs
|
336
|
+
end
|
337
|
+
replacement.convert
|
338
|
+
elsif replacement
|
339
|
+
logger.info { %(expected substitution value for custom inline macro to be of type Inline; got #{replacement.class}: #{match}) }
|
340
|
+
replacement
|
341
|
+
else
|
342
|
+
''
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
544
348
|
if doc_attrs.key? 'experimental'
|
545
349
|
if found_macroish_short && ((text.include? 'kbd:') || (text.include? 'btn:'))
|
546
|
-
text = text.gsub
|
350
|
+
text = text.gsub InlineKbdBtnMacroRx do
|
547
351
|
# honor the escape
|
548
352
|
if $1
|
549
353
|
$&.slice 1, $&.length
|
@@ -557,32 +361,27 @@ module Substitutors
|
|
557
361
|
# NOTE handle special case where keys ends with delimiter (e.g., Ctrl++ or Ctrl,,)
|
558
362
|
if keys.end_with? delim
|
559
363
|
keys = (keys.chop.split delim, -1).map {|key| key.strip }
|
560
|
-
keys[-1]
|
364
|
+
keys[-1] += delim
|
561
365
|
else
|
562
366
|
keys = keys.split(delim).map {|key| key.strip }
|
563
367
|
end
|
564
368
|
else
|
565
369
|
keys = [keys]
|
566
370
|
end
|
567
|
-
(Inline.new self, :kbd, nil, :
|
371
|
+
(Inline.new self, :kbd, nil, attributes: { 'keys' => keys }).convert
|
568
372
|
else # $2 == 'btn'
|
569
|
-
(Inline.new self, :button, (
|
373
|
+
(Inline.new self, :button, (normalize_text $3, true, true)).convert
|
570
374
|
end
|
571
|
-
|
375
|
+
end
|
572
376
|
end
|
573
377
|
|
574
378
|
if found_macroish && (text.include? 'menu:')
|
575
|
-
text = text.gsub
|
576
|
-
# alias match for Ruby 1.8.7 compat
|
577
|
-
m = $~
|
379
|
+
text = text.gsub InlineMenuMacroRx do
|
578
380
|
# honor the escape
|
579
|
-
if $&.start_with? RS
|
580
|
-
next m[0].slice 1, m[0].length
|
581
|
-
end
|
582
|
-
|
583
|
-
menu, items = m[1], m[2]
|
381
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
584
382
|
|
585
|
-
|
383
|
+
menu = $1
|
384
|
+
if (items = $2)
|
586
385
|
items = items.gsub ESC_R_SB, R_SB if items.include? R_SB
|
587
386
|
if (delim = items.include?('>') ? '>' : (items.include?(',') ? ',' : nil))
|
588
387
|
submenus = items.split(delim).map {|it| it.strip }
|
@@ -594,87 +393,42 @@ module Substitutors
|
|
594
393
|
submenus, menuitem = [], nil
|
595
394
|
end
|
596
395
|
|
597
|
-
Inline.new(self, :menu, nil, :
|
598
|
-
|
396
|
+
Inline.new(self, :menu, nil, attributes: { 'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem }).convert
|
397
|
+
end
|
599
398
|
end
|
600
399
|
|
601
400
|
if (text.include? '"') && (text.include? '>')
|
602
|
-
text = text.gsub
|
603
|
-
# alias match for Ruby 1.8.7 compat
|
604
|
-
m = $~
|
401
|
+
text = text.gsub InlineMenuRx do
|
605
402
|
# honor the escape
|
606
|
-
if $&.start_with? RS
|
607
|
-
next m[0].slice 1, m[0].length
|
608
|
-
end
|
609
|
-
|
610
|
-
input = m[1]
|
403
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
611
404
|
|
612
|
-
menu, *submenus =
|
405
|
+
menu, *submenus = $1.split('>').map {|it| it.strip }
|
613
406
|
menuitem = submenus.pop
|
614
|
-
Inline.new(self, :menu, nil, :
|
615
|
-
|
616
|
-
end
|
617
|
-
end
|
618
|
-
|
619
|
-
# FIXME this location is somewhat arbitrary, probably need to be able to control ordering
|
620
|
-
# TODO this handling needs some cleanup
|
621
|
-
if (extensions = doc.extensions) && extensions.inline_macros? # && found_macroish
|
622
|
-
extensions.inline_macros.each do |extension|
|
623
|
-
text = text.gsub(extension.instance.regexp) {
|
624
|
-
# alias match for Ruby 1.8.7 compat
|
625
|
-
m = $~
|
626
|
-
# honor the escape
|
627
|
-
if $&.start_with? RS
|
628
|
-
next m[0].slice 1, m[0].length
|
629
|
-
end
|
630
|
-
|
631
|
-
if (m.names rescue []).empty?
|
632
|
-
target, content, extconf = m[1], m[2], extension.config
|
633
|
-
else
|
634
|
-
target, content, extconf = (m[:target] rescue nil), (m[:content] rescue nil), extension.config
|
635
|
-
end
|
636
|
-
attributes = (attributes = extconf[:default_attrs]) ? attributes.dup : {}
|
637
|
-
if content.nil_or_empty?
|
638
|
-
attributes['text'] = content if content && extconf[:content_model] != :attributes
|
639
|
-
else
|
640
|
-
content = unescape_bracketed_text content
|
641
|
-
if extconf[:content_model] == :attributes
|
642
|
-
# QUESTION should we store the text in the _text key?
|
643
|
-
# NOTE bracked text has already been escaped
|
644
|
-
parse_attributes content, extconf[:pos_attrs] || [], :into => attributes
|
645
|
-
else
|
646
|
-
attributes['text'] = content
|
647
|
-
end
|
648
|
-
end
|
649
|
-
# NOTE use content if target is not set (short form only); deprecated - remove in 1.6.0
|
650
|
-
replacement = extension.process_method[self, target || content, attributes]
|
651
|
-
Inline === replacement ? replacement.convert : replacement
|
652
|
-
}
|
407
|
+
Inline.new(self, :menu, nil, attributes: { 'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem }).convert
|
408
|
+
end
|
653
409
|
end
|
654
410
|
end
|
655
411
|
|
656
412
|
if found_macroish && ((text.include? 'image:') || (text.include? 'icon:'))
|
657
413
|
# image:filename.png[Alt Text]
|
658
|
-
text = text.gsub
|
659
|
-
# alias match for Ruby 1.8.7 compat
|
660
|
-
m = $~
|
414
|
+
text = text.gsub InlineImageMacroRx do
|
661
415
|
# honor the escape
|
662
|
-
if
|
663
|
-
next
|
664
|
-
elsif
|
416
|
+
if $&.start_with? RS
|
417
|
+
next $&.slice 1, $&.length
|
418
|
+
elsif $&.start_with? 'icon:'
|
665
419
|
type, posattrs = 'icon', ['size']
|
666
420
|
else
|
667
421
|
type, posattrs = 'image', ['alt', 'width', 'height']
|
668
422
|
end
|
669
|
-
|
670
|
-
|
671
|
-
|
423
|
+
target = $1
|
424
|
+
attrs = parse_attributes $2, posattrs, unescape_input: true
|
425
|
+
unless type == 'icon'
|
426
|
+
doc.register :images, target
|
427
|
+
attrs['imagesdir'] = doc_attrs['imagesdir']
|
672
428
|
end
|
673
|
-
attrs = parse_attributes m[2], posattrs, :unescape_input => true
|
674
|
-
doc.register :images, [target, (attrs['imagesdir'] = doc_attrs['imagesdir'])] unless type == 'icon'
|
675
429
|
attrs['alt'] ||= (attrs['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
|
676
|
-
Inline.new(self, :image, nil, :
|
677
|
-
|
430
|
+
Inline.new(self, :image, nil, type: type, target: target, attributes: attrs).convert
|
431
|
+
end
|
678
432
|
end
|
679
433
|
|
680
434
|
if ((text.include? '((') && (text.include? '))')) || (found_macroish_short && (text.include? 'dexterm'))
|
@@ -682,151 +436,183 @@ module Substitutors
|
|
682
436
|
# indexterm:[Tigers,Big cats]
|
683
437
|
# ((Tigers))
|
684
438
|
# indexterm2:[Tigers]
|
685
|
-
text = text.gsub
|
686
|
-
captured = $&
|
439
|
+
text = text.gsub InlineIndextermMacroRx do
|
687
440
|
case $1
|
688
441
|
when 'indexterm'
|
689
|
-
text = $2
|
690
442
|
# honor the escape
|
691
|
-
if
|
692
|
-
|
693
|
-
end
|
443
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
444
|
+
|
694
445
|
# indexterm:[Tigers,Big cats]
|
695
|
-
|
696
|
-
|
697
|
-
|
446
|
+
if (attrlist = normalize_text $2, true, true).include? '='
|
447
|
+
if (primary = (attrs = (AttributeList.new attrlist, self).parse)[1])
|
448
|
+
attrs['terms'] = [primary]
|
449
|
+
if (see_also = attrs['see-also'])
|
450
|
+
attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
|
451
|
+
end
|
452
|
+
else
|
453
|
+
attrs = { 'terms' => attrlist }
|
454
|
+
end
|
455
|
+
else
|
456
|
+
attrs = { 'terms' => (split_simple_csv attrlist) }
|
457
|
+
end
|
458
|
+
(Inline.new self, :indexterm, nil, attributes: attrs).convert
|
698
459
|
when 'indexterm2'
|
699
|
-
text = $2
|
700
460
|
# honor the escape
|
701
|
-
if
|
702
|
-
|
703
|
-
end
|
461
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
462
|
+
|
704
463
|
# indexterm2:[Tigers]
|
705
|
-
term =
|
706
|
-
|
707
|
-
|
464
|
+
if (term = normalize_text $2, true, true).include? '='
|
465
|
+
term = (attrs = (AttributeList.new term, self).parse)[1] || (attrs = nil) || term
|
466
|
+
if attrs && (see_also = attrs['see-also'])
|
467
|
+
attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
|
468
|
+
end
|
469
|
+
end
|
470
|
+
(Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
|
708
471
|
else
|
709
|
-
|
472
|
+
encl_text = $3
|
710
473
|
# honor the escape
|
711
|
-
if
|
474
|
+
if $&.start_with? RS
|
712
475
|
# escape concealed index term, but process nested flow index term
|
713
|
-
if (
|
714
|
-
|
476
|
+
if (encl_text.start_with? '(') && (encl_text.end_with? ')')
|
477
|
+
encl_text = encl_text.slice 1, encl_text.length - 2
|
715
478
|
visible, before, after = true, '(', ')'
|
716
479
|
else
|
717
|
-
next
|
480
|
+
next $&.slice 1, $&.length
|
718
481
|
end
|
719
482
|
else
|
720
483
|
visible = true
|
721
|
-
if
|
722
|
-
if
|
723
|
-
|
484
|
+
if encl_text.start_with? '('
|
485
|
+
if encl_text.end_with? ')'
|
486
|
+
encl_text, visible = (encl_text.slice 1, encl_text.length - 2), false
|
724
487
|
else
|
725
|
-
|
488
|
+
encl_text, before, after = (encl_text.slice 1, encl_text.length), '(', ''
|
726
489
|
end
|
727
|
-
elsif
|
728
|
-
|
490
|
+
elsif encl_text.end_with? ')'
|
491
|
+
encl_text, before, after = encl_text.chop, '', ')'
|
729
492
|
end
|
730
493
|
end
|
731
494
|
if visible
|
732
495
|
# ((Tigers))
|
733
|
-
term =
|
734
|
-
|
735
|
-
|
496
|
+
if (term = normalize_text encl_text, true).include? ';&'
|
497
|
+
if term.include? ' >> '
|
498
|
+
term, _, see = term.partition ' >> '
|
499
|
+
attrs = { 'see' => see }
|
500
|
+
elsif term.include? ' &> '
|
501
|
+
term, *see_also = term.split ' &> '
|
502
|
+
attrs = { 'see-also' => see_also }
|
503
|
+
end
|
504
|
+
end
|
505
|
+
subbed_term = (Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
|
736
506
|
else
|
737
507
|
# (((Tigers,Big cats)))
|
738
|
-
|
739
|
-
|
740
|
-
|
508
|
+
attrs = {}
|
509
|
+
if (terms = normalize_text encl_text, true).include? ';&'
|
510
|
+
if terms.include? ' >> '
|
511
|
+
terms, _, see = terms.partition ' >> '
|
512
|
+
attrs['see'] = see
|
513
|
+
elsif terms.include? ' &> '
|
514
|
+
terms, *see_also = terms.split ' &> '
|
515
|
+
attrs['see-also'] = see_also
|
516
|
+
end
|
517
|
+
end
|
518
|
+
attrs['terms'] = split_simple_csv terms
|
519
|
+
subbed_term = (Inline.new self, :indexterm, nil, attributes: attrs).convert
|
741
520
|
end
|
742
521
|
before ? %(#{before}#{subbed_term}#{after}) : subbed_term
|
743
522
|
end
|
744
|
-
|
523
|
+
end
|
745
524
|
end
|
746
525
|
|
747
526
|
if found_colon && (text.include? '://')
|
748
527
|
# inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
|
749
|
-
text = text.gsub
|
750
|
-
# alias match for Ruby 1.8.7 compat
|
751
|
-
m = $~
|
752
|
-
# honor the escape
|
528
|
+
text = text.gsub InlineLinkRx do
|
753
529
|
if (target = $2).start_with? RS
|
754
|
-
|
530
|
+
# honor the escape
|
531
|
+
next %(#{$1}#{target.slice 1, target.length}#{$4})
|
755
532
|
end
|
756
|
-
|
757
|
-
prefix,
|
758
|
-
if
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
533
|
+
|
534
|
+
prefix, suffix = $1, ''
|
535
|
+
# NOTE if $4 is set, we're looking at a formal macro (e.g., https://example.org[])
|
536
|
+
if $4
|
537
|
+
prefix = '' if prefix == 'link:'
|
538
|
+
link_text = nil if (link_text = $4).empty?
|
539
|
+
else
|
540
|
+
# invalid macro syntax (link: prefix w/o trailing square brackets or enclosed in double quotes)
|
541
|
+
# FIXME we probably shouldn't even get here when the link: prefix is present; the regex is doing too much
|
542
|
+
case prefix
|
543
|
+
when 'link:', ?", ?'
|
544
|
+
next $&
|
765
545
|
end
|
766
|
-
|
767
|
-
|
768
|
-
case $&
|
769
|
-
when ')'
|
770
|
-
# strip trailing )
|
546
|
+
case $3
|
547
|
+
when ')', '?', '!'
|
771
548
|
target = target.chop
|
772
|
-
suffix = ')'
|
549
|
+
if (suffix = $3) == ')' && (target.end_with? '.', '?', '!')
|
550
|
+
suffix = target[-1] + suffix
|
551
|
+
target = target.chop
|
552
|
+
end
|
553
|
+
# NOTE handle case when modified target is a URI scheme (e.g., http://)
|
554
|
+
next $& if target.end_with? '://'
|
773
555
|
when ';'
|
774
|
-
|
775
|
-
|
556
|
+
if (prefix.start_with? '<') && (target.end_with? '>')
|
557
|
+
# move surrounding <> out of URL
|
776
558
|
prefix = prefix.slice 4, prefix.length
|
777
559
|
target = target.slice 0, target.length - 4
|
778
|
-
|
779
|
-
|
780
|
-
elsif (target = target.chop).end_with?(')')
|
560
|
+
elsif (target = target.chop).end_with? ')'
|
561
|
+
# move trailing ); out of URL
|
781
562
|
target = target.chop
|
782
563
|
suffix = ');'
|
783
564
|
else
|
565
|
+
# move trailing ; out of URL
|
784
566
|
suffix = ';'
|
785
567
|
end
|
568
|
+
# NOTE handle case when modified target is a URI scheme (e.g., http://)
|
569
|
+
next $& if target.end_with? '://'
|
786
570
|
when ':'
|
787
|
-
|
788
|
-
|
789
|
-
if (target = target.chop).end_with?(')')
|
571
|
+
if (target = target.chop).end_with? ')'
|
572
|
+
# move trailing ): out of URL
|
790
573
|
target = target.chop
|
791
574
|
suffix = '):'
|
792
575
|
else
|
576
|
+
# move trailing : out of URL
|
793
577
|
suffix = ':'
|
794
578
|
end
|
579
|
+
# NOTE handle case when modified target is a URI scheme (e.g., http://)
|
580
|
+
next $& if target.end_with? '://'
|
795
581
|
end
|
796
|
-
# NOTE handle case when remaining target is a URI scheme (e.g., http://)
|
797
|
-
return m[0] if target.end_with? '://'
|
798
582
|
end
|
799
583
|
|
800
|
-
attrs, link_opts = nil, { :
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
584
|
+
attrs, link_opts = nil, { type: :link }
|
585
|
+
|
586
|
+
if link_text
|
587
|
+
new_link_text = link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
|
588
|
+
if !doc.compat_mode && (link_text.include? '=')
|
589
|
+
# NOTE if an equals sign (=) is present, extract attributes from link text
|
590
|
+
link_text, attrs = extract_attributes_from_text link_text, ''
|
591
|
+
new_link_text = link_text
|
592
|
+
link_opts[:id] = attrs['id']
|
806
593
|
end
|
807
594
|
|
808
|
-
|
809
|
-
|
810
|
-
#unless attrs && (attrs.key? 'title')
|
811
|
-
# if text.include? '|'
|
812
|
-
# attrs ||= {}
|
813
|
-
# text, attrs['title'] = text.split '|', 2
|
814
|
-
# end
|
815
|
-
#end
|
816
|
-
|
817
|
-
if text.end_with? '^'
|
818
|
-
text = text.chop
|
595
|
+
if link_text.end_with? '^'
|
596
|
+
new_link_text = link_text = link_text.chop
|
819
597
|
if attrs
|
820
598
|
attrs['window'] ||= '_blank'
|
821
599
|
else
|
822
600
|
attrs = { 'window' => '_blank' }
|
823
601
|
end
|
824
602
|
end
|
825
|
-
end
|
826
603
|
|
827
|
-
|
604
|
+
if new_link_text && new_link_text.empty?
|
605
|
+
# NOTE it's not possible for the URI scheme to be bare in this case
|
606
|
+
link_text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
|
607
|
+
bare = true
|
608
|
+
end
|
609
|
+
else
|
828
610
|
# NOTE it's not possible for the URI scheme to be bare in this case
|
829
|
-
|
611
|
+
link_text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
|
612
|
+
bare = true
|
613
|
+
end
|
614
|
+
|
615
|
+
if bare
|
830
616
|
if attrs
|
831
617
|
attrs['role'] = (attrs.key? 'role') ? %(bare #{attrs['role']}) : 'bare'
|
832
618
|
else
|
@@ -836,51 +622,45 @@ module Substitutors
|
|
836
622
|
|
837
623
|
doc.register :links, (link_opts[:target] = target)
|
838
624
|
link_opts[:attributes] = attrs if attrs
|
839
|
-
%(#{prefix}#{Inline.new
|
840
|
-
|
625
|
+
%(#{prefix}#{(Inline.new self, :anchor, link_text, link_opts).convert}#{suffix})
|
626
|
+
end
|
841
627
|
end
|
842
628
|
|
843
|
-
if found_macroish && ((text.include? 'link:') || (text.include? '
|
629
|
+
if found_macroish && ((text.include? 'link:') || (text.include? 'ilto:'))
|
844
630
|
# inline link macros, link:target[text]
|
845
|
-
text = text.gsub
|
846
|
-
# alias match for Ruby 1.8.7 compat
|
847
|
-
m = $~
|
631
|
+
text = text.gsub InlineLinkMacroRx do
|
848
632
|
# honor the escape
|
849
633
|
if $&.start_with? RS
|
850
|
-
next
|
634
|
+
next $&.slice 1, $&.length
|
635
|
+
elsif (mailto = $1)
|
636
|
+
target = 'mailto:' + (mailto_text = $2)
|
637
|
+
else
|
638
|
+
target = $2
|
851
639
|
end
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
|
640
|
+
attrs, link_opts = nil, { type: :link }
|
641
|
+
unless (link_text = $3).empty?
|
642
|
+
link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
|
856
643
|
if mailto
|
857
|
-
if !doc.compat_mode && (
|
858
|
-
|
859
|
-
|
644
|
+
if !doc.compat_mode && (link_text.include? ',')
|
645
|
+
# NOTE if a comma (,) is present, extract attributes from link text
|
646
|
+
link_text, attrs = extract_attributes_from_text link_text, ''
|
647
|
+
link_opts[:id] = attrs['id']
|
860
648
|
if attrs.key? 2
|
861
649
|
if attrs.key? 3
|
862
|
-
target = %(#{target}?subject=#{Helpers.
|
650
|
+
target = %(#{target}?subject=#{Helpers.encode_uri_component attrs[2]}&body=#{Helpers.encode_uri_component attrs[3]})
|
863
651
|
else
|
864
|
-
target = %(#{target}?subject=#{Helpers.
|
652
|
+
target = %(#{target}?subject=#{Helpers.encode_uri_component attrs[2]})
|
865
653
|
end
|
866
654
|
end
|
867
655
|
end
|
868
|
-
elsif !doc.compat_mode && (
|
869
|
-
|
870
|
-
|
656
|
+
elsif !doc.compat_mode && (link_text.include? '=')
|
657
|
+
# NOTE if an equals sign (=) is present, extract attributes from link text
|
658
|
+
link_text, attrs = extract_attributes_from_text link_text, ''
|
659
|
+
link_opts[:id] = attrs['id']
|
871
660
|
end
|
872
661
|
|
873
|
-
|
874
|
-
|
875
|
-
#unless attrs && (attrs.key? 'title')
|
876
|
-
# if text.include? '|'
|
877
|
-
# attrs ||= {}
|
878
|
-
# text, attrs['title'] = text.split '|', 2
|
879
|
-
# end
|
880
|
-
#end
|
881
|
-
|
882
|
-
if text.end_with? '^'
|
883
|
-
text = text.chop
|
662
|
+
if link_text.end_with? '^'
|
663
|
+
link_text = link_text.chop
|
884
664
|
if attrs
|
885
665
|
attrs['window'] ||= '_blank'
|
886
666
|
else
|
@@ -889,17 +669,17 @@ module Substitutors
|
|
889
669
|
end
|
890
670
|
end
|
891
671
|
|
892
|
-
if
|
672
|
+
if link_text.empty?
|
893
673
|
# mailto is a special case, already processed
|
894
674
|
if mailto
|
895
|
-
|
675
|
+
link_text = mailto_text
|
896
676
|
else
|
897
677
|
if doc_attrs.key? 'hide-uri-scheme'
|
898
|
-
if (
|
899
|
-
|
678
|
+
if (link_text = target.sub UriSniffRx, '').empty?
|
679
|
+
link_text = target
|
900
680
|
end
|
901
681
|
else
|
902
|
-
|
682
|
+
link_text = target
|
903
683
|
end
|
904
684
|
if attrs
|
905
685
|
attrs['role'] = (attrs.key? 'role') ? %(bare #{attrs['role']}) : 'bare'
|
@@ -912,122 +692,64 @@ module Substitutors
|
|
912
692
|
# QUESTION should a mailto be registered as an e-mail address?
|
913
693
|
doc.register :links, (link_opts[:target] = target)
|
914
694
|
link_opts[:attributes] = attrs if attrs
|
915
|
-
Inline.new(self, :anchor,
|
916
|
-
|
695
|
+
Inline.new(self, :anchor, link_text, link_opts).convert
|
696
|
+
end
|
917
697
|
end
|
918
698
|
|
919
699
|
if text.include? '@'
|
920
|
-
text = text.gsub
|
921
|
-
|
922
|
-
if
|
923
|
-
next (tip == RS ? (address.slice 1, address.length) : address)
|
924
|
-
end
|
700
|
+
text = text.gsub InlineEmailRx do
|
701
|
+
# honor the escape
|
702
|
+
next $1 == RS ? ($&.slice 1, $&.length) : $& if $1
|
925
703
|
|
926
|
-
target =
|
704
|
+
target = 'mailto:' + (address = $&)
|
927
705
|
# QUESTION should this be registered as an e-mail address?
|
928
706
|
doc.register(:links, target)
|
929
707
|
|
930
|
-
Inline.new(self, :anchor, address, :
|
931
|
-
|
708
|
+
Inline.new(self, :anchor, address, type: :link, target: target).convert
|
709
|
+
end
|
932
710
|
end
|
933
711
|
|
934
|
-
if
|
935
|
-
text = text.
|
936
|
-
|
937
|
-
|
712
|
+
if found_square_bracket && @context == :list_item && @parent.style == 'bibliography'
|
713
|
+
text = text.sub(InlineBiblioAnchorRx) { (Inline.new self, :anchor, $2, type: :bibref, id: $1).convert }
|
714
|
+
end
|
715
|
+
|
716
|
+
if (found_square_bracket && text.include?('[[')) || (found_macroish && text.include?('or:'))
|
717
|
+
text = text.gsub InlineAnchorRx do
|
938
718
|
# honor the escape
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
if
|
943
|
-
|
719
|
+
next $&.slice 1, $&.length if $1
|
720
|
+
|
721
|
+
# NOTE reftext is only relevant for DocBook output; used as value of xreflabel attribute
|
722
|
+
if (id = $2)
|
723
|
+
reftext = $3
|
944
724
|
else
|
945
|
-
id
|
725
|
+
id = $4
|
726
|
+
if (reftext = $5) && (reftext.include? R_SB)
|
727
|
+
reftext = reftext.gsub ESC_R_SB, R_SB
|
728
|
+
end
|
946
729
|
end
|
947
|
-
|
948
|
-
|
949
|
-
# REVIEW it's a dirty job, but somebody's gotta do it
|
950
|
-
text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string text, true)), false)
|
951
|
-
index = doc.counter('footnote-number')
|
952
|
-
doc.register(:footnotes, Document::Footnote.new(index, id, text))
|
953
|
-
type, target = :ref, nil
|
954
|
-
else
|
955
|
-
if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
|
956
|
-
index, text = footnote.index, footnote.text
|
957
|
-
else
|
958
|
-
logger.warn %(invalid footnote reference: #{id})
|
959
|
-
index, text = nil, id
|
960
|
-
end
|
961
|
-
type, target, id = :xref, id, nil
|
962
|
-
end
|
963
|
-
elsif text
|
964
|
-
# REVIEW it's a dirty job, but somebody's gotta do it
|
965
|
-
text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string text, true)), false)
|
966
|
-
index = doc.counter('footnote-number')
|
967
|
-
doc.register(:footnotes, Document::Footnote.new(index, id, text))
|
968
|
-
type = target = nil
|
969
|
-
else
|
970
|
-
next m[0]
|
971
|
-
end
|
972
|
-
Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).convert
|
973
|
-
}
|
974
|
-
end
|
975
|
-
|
976
|
-
sub_inline_xrefs(sub_inline_anchors(text, found), found)
|
977
|
-
end
|
978
|
-
|
979
|
-
# Internal: Substitute normal and bibliographic anchors
|
980
|
-
def sub_inline_anchors(text, found = nil)
|
981
|
-
if @context == :list_item && @parent.style == 'bibliography'
|
982
|
-
text = text.sub(InlineBiblioAnchorRx) {
|
983
|
-
# NOTE target property on :bibref is deprecated
|
984
|
-
Inline.new(self, :anchor, %([#{$2 || $1}]), :type => :bibref, :id => $1, :target => $1).convert
|
985
|
-
}
|
730
|
+
Inline.new(self, :anchor, reftext, type: :ref, id: id).convert
|
731
|
+
end
|
986
732
|
end
|
987
733
|
|
988
|
-
if (
|
989
|
-
|
990
|
-
text = text.gsub
|
734
|
+
#if (text.include? ';&l') || (found_macroish && (text.include? 'xref:'))
|
735
|
+
if ((text.include? '&') && (text.include? ';&l')) || (found_macroish && (text.include? 'xref:'))
|
736
|
+
text = text.gsub InlineXrefMacroRx do
|
991
737
|
# honor the escape
|
992
|
-
next $&.slice 1, $&.length if
|
993
|
-
# NOTE reftext is only relevant for DocBook output; used as value of xreflabel attribute
|
994
|
-
if (id = $2)
|
995
|
-
reftext = $3
|
996
|
-
else
|
997
|
-
id = $4
|
998
|
-
if (reftext = $5) && (reftext.include? R_SB)
|
999
|
-
reftext = reftext.gsub ESC_R_SB, R_SB
|
1000
|
-
end
|
1001
|
-
end
|
1002
|
-
# NOTE target property on :ref is deprecated
|
1003
|
-
Inline.new(self, :anchor, reftext, :type => :ref, :id => id, :target => id).convert
|
1004
|
-
}
|
1005
|
-
end
|
1006
|
-
|
1007
|
-
text
|
1008
|
-
end
|
738
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
1009
739
|
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
# honor the escape
|
1017
|
-
if $&.start_with? RS
|
1018
|
-
next m[0].slice 1, m[0].length
|
1019
|
-
end
|
1020
|
-
attrs, doc = {}, @document
|
1021
|
-
if (refid = m[1])
|
1022
|
-
refid, text = refid.split ',', 2
|
1023
|
-
text = text.lstrip if text
|
740
|
+
attrs = {}
|
741
|
+
if (refid = $1)
|
742
|
+
if refid.include? ','
|
743
|
+
refid, _, link_text = refid.partition ','
|
744
|
+
link_text = nil if (link_text = link_text.lstrip).empty?
|
745
|
+
end
|
1024
746
|
else
|
1025
747
|
macro = true
|
1026
|
-
refid =
|
1027
|
-
if (
|
1028
|
-
|
1029
|
-
# NOTE if an
|
1030
|
-
|
748
|
+
refid = $2
|
749
|
+
if (link_text = $3)
|
750
|
+
link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
|
751
|
+
# NOTE if an equals sign (=) is present, extract attributes from link text
|
752
|
+
link_text, attrs = extract_attributes_from_text link_text if !doc.compat_mode && (link_text.include? '=')
|
1031
753
|
end
|
1032
754
|
end
|
1033
755
|
|
@@ -1035,21 +757,33 @@ module Substitutors
|
|
1035
757
|
fragment = refid
|
1036
758
|
elsif (hash_idx = refid.index '#')
|
1037
759
|
if hash_idx > 0
|
1038
|
-
if (fragment_len = refid.length -
|
760
|
+
if (fragment_len = refid.length - 1 - hash_idx) > 0
|
1039
761
|
path, fragment = (refid.slice 0, hash_idx), (refid.slice hash_idx + 1, fragment_len)
|
1040
762
|
else
|
1041
|
-
path = refid.
|
763
|
+
path = refid.chop
|
1042
764
|
end
|
1043
|
-
if
|
765
|
+
if macro
|
766
|
+
if path.end_with? '.adoc'
|
767
|
+
src2src = path = path.slice 0, path.length - 5
|
768
|
+
elsif !(Helpers.extname? path)
|
769
|
+
src2src = path
|
770
|
+
end
|
771
|
+
elsif path.end_with?(*ASCIIDOC_EXTENSIONS.keys)
|
772
|
+
src2src = path = path.slice 0, (path.rindex '.')
|
773
|
+
else
|
1044
774
|
src2src = path
|
1045
|
-
elsif ASCIIDOC_EXTENSIONS[ext]
|
1046
|
-
src2src = (path = path.slice 0, path.length - ext.length)
|
1047
775
|
end
|
1048
776
|
else
|
1049
777
|
target, fragment = refid, (refid.slice 1, refid.length)
|
1050
778
|
end
|
1051
|
-
elsif macro
|
1052
|
-
|
779
|
+
elsif macro
|
780
|
+
if refid.end_with? '.adoc'
|
781
|
+
src2src = path = refid.slice 0, refid.length - 5
|
782
|
+
elsif Helpers.extname? refid
|
783
|
+
path = refid
|
784
|
+
else
|
785
|
+
fragment = refid
|
786
|
+
end
|
1053
787
|
else
|
1054
788
|
fragment = refid
|
1055
789
|
end
|
@@ -1057,19 +791,19 @@ module Substitutors
|
|
1057
791
|
# handles: #id
|
1058
792
|
if target
|
1059
793
|
refid = fragment
|
1060
|
-
logger.
|
794
|
+
logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
|
1061
795
|
elsif path
|
1062
796
|
# handles: path#, path#id, path.adoc#, path.adoc#id, or path.adoc (xref macro only)
|
1063
797
|
# the referenced path is the current document, or its contents have been included in the current document
|
1064
798
|
if src2src && (doc.attributes['docname'] == path || doc.catalog[:includes][path])
|
1065
799
|
if fragment
|
1066
800
|
refid, path, target = fragment, nil, %(##{fragment})
|
1067
|
-
logger.
|
801
|
+
logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
|
1068
802
|
else
|
1069
803
|
refid, path, target = nil, nil, '#'
|
1070
804
|
end
|
1071
805
|
else
|
1072
|
-
refid, path = path, %(#{doc.attributes['relfileprefix']}#{path}#{src2src ? (doc.attributes.fetch 'relfilesuffix', doc.outfilesuffix) : ''})
|
806
|
+
refid, path = path, %(#{doc.attributes['relfileprefix'] || ''}#{path}#{src2src ? (doc.attributes.fetch 'relfilesuffix', doc.outfilesuffix) : ''})
|
1073
807
|
if fragment
|
1074
808
|
refid, target = %(#{refid}##{fragment}), %(#{path}##{fragment})
|
1075
809
|
else
|
@@ -1079,43 +813,70 @@ module Substitutors
|
|
1079
813
|
# handles: id (in compat mode or when natural xrefs are disabled)
|
1080
814
|
elsif doc.compat_mode || !Compliance.natural_xrefs
|
1081
815
|
refid, target = fragment, %(##{fragment})
|
1082
|
-
logger.
|
816
|
+
logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
|
1083
817
|
# handles: id
|
1084
|
-
elsif doc.catalog[:
|
818
|
+
elsif doc.catalog[:refs][fragment]
|
1085
819
|
refid, target = fragment, %(##{fragment})
|
1086
820
|
# handles: Node Title or Reference Text
|
1087
821
|
# do reverse lookup on fragment if not a known ID and resembles reftext (contains a space or uppercase char)
|
1088
|
-
elsif (
|
822
|
+
elsif ((fragment.include? ' ') || fragment.downcase != fragment) && (refid = doc.resolve_id fragment)
|
1089
823
|
fragment, target = refid, %(##{refid})
|
1090
824
|
else
|
1091
825
|
refid, target = fragment, %(##{fragment})
|
1092
|
-
logger.
|
826
|
+
logger.info %(possible invalid reference: #{refid}) if logger.info?
|
1093
827
|
end
|
1094
|
-
attrs['path']
|
1095
|
-
|
1096
|
-
|
828
|
+
attrs['path'] = path
|
829
|
+
attrs['fragment'] = fragment
|
830
|
+
attrs['refid'] = refid
|
831
|
+
Inline.new(self, :anchor, link_text, type: :xref, target: target, attributes: attrs).convert
|
832
|
+
end
|
1097
833
|
end
|
1098
834
|
|
1099
|
-
|
1100
|
-
|
835
|
+
if found_macroish && (text.include? 'tnote')
|
836
|
+
text = text.gsub InlineFootnoteMacroRx do
|
837
|
+
# honor the escape
|
838
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
1101
839
|
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
840
|
+
# footnoteref
|
841
|
+
if $1
|
842
|
+
if $3
|
843
|
+
id, content = $3.split ',', 2
|
844
|
+
logger.warn %(found deprecated footnoteref macro: #{$&}; use footnote macro with target instead) unless doc.compat_mode
|
845
|
+
else
|
846
|
+
next $&
|
847
|
+
end
|
848
|
+
# footnote
|
849
|
+
else
|
850
|
+
id = $2
|
851
|
+
content = $3
|
852
|
+
end
|
853
|
+
|
854
|
+
if id
|
855
|
+
if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
|
856
|
+
index, content = footnote.index, footnote.text
|
857
|
+
type, target, id = :xref, id, nil
|
858
|
+
elsif content
|
859
|
+
content = restore_passthroughs(normalize_text content, true, true)
|
860
|
+
index = doc.counter('footnote-number')
|
861
|
+
doc.register(:footnotes, Document::Footnote.new(index, id, content))
|
862
|
+
type, target = :ref, nil
|
863
|
+
else
|
864
|
+
logger.warn %(invalid footnote reference: #{id})
|
865
|
+
type, target, content, id = :xref, id, id, nil
|
866
|
+
end
|
867
|
+
elsif content
|
868
|
+
content = restore_passthroughs(normalize_text content, true, true)
|
869
|
+
index = doc.counter('footnote-number')
|
870
|
+
doc.register(:footnotes, Document::Footnote.new(index, id, content))
|
871
|
+
type = target = nil
|
872
|
+
else
|
873
|
+
next $&
|
874
|
+
end
|
875
|
+
Inline.new(self, :footnote, content, attributes: { 'index' => index }, id: id, target: target, type: type).convert
|
1117
876
|
end
|
1118
|
-
|
877
|
+
end
|
878
|
+
|
879
|
+
text
|
1119
880
|
end
|
1120
881
|
|
1121
882
|
# Public: Substitute post replacements
|
@@ -1123,216 +884,286 @@ module Substitutors
|
|
1123
884
|
# text - The String text to process
|
1124
885
|
#
|
1125
886
|
# Returns the converted String text
|
1126
|
-
def sub_post_replacements
|
1127
|
-
if
|
887
|
+
def sub_post_replacements text
|
888
|
+
#if attr? 'hardbreaks-option', nil, true
|
889
|
+
if @attributes['hardbreaks-option'] || @document.attributes['hardbreaks-option']
|
1128
890
|
lines = text.split LF, -1
|
1129
891
|
return text if lines.size < 2
|
1130
892
|
last = lines.pop
|
1131
|
-
(lines.map
|
1132
|
-
Inline.new(self, :break, (line.end_with? HARD_LINE_BREAK) ? (line.slice 0, line.length - 2) : line, :
|
1133
|
-
|
893
|
+
(lines.map do |line|
|
894
|
+
Inline.new(self, :break, (line.end_with? HARD_LINE_BREAK) ? (line.slice 0, line.length - 2) : line, type: :line).convert
|
895
|
+
end << last).join LF
|
1134
896
|
elsif (text.include? PLUS) && (text.include? HARD_LINE_BREAK)
|
1135
|
-
text.gsub(HardLineBreakRx) { Inline.new(self, :break, $1, :
|
897
|
+
text.gsub(HardLineBreakRx) { Inline.new(self, :break, $1, type: :line).convert }
|
1136
898
|
else
|
1137
899
|
text
|
1138
900
|
end
|
1139
901
|
end
|
1140
902
|
|
1141
|
-
#
|
903
|
+
# Public: Apply verbatim substitutions on source (for use when highlighting is disabled).
|
1142
904
|
#
|
1143
|
-
#
|
1144
|
-
#
|
1145
|
-
# scope - The scope of the quoting (constrained or unconstrained)
|
905
|
+
# source - the source code String on which to apply verbatim substitutions
|
906
|
+
# process_callouts - a Boolean flag indicating whether callout marks should be substituted
|
1146
907
|
#
|
1147
|
-
# Returns
|
1148
|
-
def
|
1149
|
-
|
1150
|
-
|
1151
|
-
unescaped_attrs = %([#{attrs}])
|
1152
|
-
else
|
1153
|
-
return match[0].slice 1, match[0].length
|
1154
|
-
end
|
1155
|
-
end
|
908
|
+
# Returns the substituted source
|
909
|
+
def sub_source source, process_callouts
|
910
|
+
process_callouts ? sub_callouts(sub_specialchars source) : (sub_specialchars source)
|
911
|
+
end
|
1156
912
|
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
913
|
+
# Public: Substitute callout source references
|
914
|
+
#
|
915
|
+
# text - The String text to process
|
916
|
+
#
|
917
|
+
# Returns the converted String text
|
918
|
+
def sub_callouts text
|
919
|
+
callout_rx = (attr? 'line-comment') ? CalloutSourceRxMap[attr 'line-comment'] : CalloutSourceRx
|
920
|
+
autonum = 0
|
921
|
+
text.gsub callout_rx do
|
922
|
+
# honor the escape
|
923
|
+
if $2
|
924
|
+
# use sub since it might be behind a line comment
|
925
|
+
$&.sub RS, ''
|
1160
926
|
else
|
1161
|
-
|
1162
|
-
id = (attributes = parse_quoted_text_attributes attrlist).delete 'id'
|
1163
|
-
type = :unquoted if type == :mark
|
1164
|
-
end
|
1165
|
-
%(#{match[1]}#{Inline.new(self, :quoted, match[3], :type => type, :id => id, :attributes => attributes).convert})
|
927
|
+
Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 || ($3 == '--' ? ['<!--', '-->'] : nil) }).convert
|
1166
928
|
end
|
1167
|
-
else
|
1168
|
-
if (attrlist = match[1])
|
1169
|
-
id = (attributes = parse_quoted_text_attributes attrlist).delete 'id'
|
1170
|
-
type = :unquoted if type == :mark
|
1171
|
-
end
|
1172
|
-
Inline.new(self, :quoted, match[2], :type => type, :id => id, :attributes => attributes).convert
|
1173
929
|
end
|
1174
930
|
end
|
1175
931
|
|
1176
|
-
#
|
932
|
+
# Public: Highlight (i.e., colorize) the source code during conversion using a syntax highlighter, if activated by the
|
933
|
+
# source-highlighter document attribute. Otherwise return the text with verbatim substitutions applied.
|
1177
934
|
#
|
1178
|
-
#
|
1179
|
-
#
|
935
|
+
# If the process_callouts argument is true, this method will extract the callout marks from the source before passing
|
936
|
+
# it to the syntax highlighter, then subsequently restore those callout marks to the highlighted source so the callout
|
937
|
+
# marks don't confuse the syntax highlighter.
|
1180
938
|
#
|
1181
|
-
#
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
939
|
+
# source - the source code String to syntax highlight
|
940
|
+
# process_callouts - a Boolean flag indicating whether callout marks should be located and substituted
|
941
|
+
#
|
942
|
+
# Returns the highlighted source code, if a syntax highlighter is defined on the document, otherwise the source with
|
943
|
+
# verbatim substitutions applied
|
944
|
+
def highlight_source source, process_callouts
|
945
|
+
# NOTE the call to highlight? is a defensive check since, normally, we wouldn't arrive here unless it returns true
|
946
|
+
return sub_source source, process_callouts unless (syntax_hl = @document.syntax_highlighter) && syntax_hl.highlight?
|
1187
947
|
|
1188
|
-
|
1189
|
-
{}
|
1190
|
-
elsif (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
|
1191
|
-
segments = str.split('#', 2)
|
948
|
+
source, callout_marks = extract_callouts source if process_callouts
|
1192
949
|
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
950
|
+
doc_attrs = @document.attributes
|
951
|
+
syntax_hl_name = syntax_hl.name
|
952
|
+
if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil) &&
|
953
|
+
(start_line_number = (attr 'start', 1).to_i) < 1
|
954
|
+
start_line_number = 1
|
955
|
+
end
|
956
|
+
highlight_lines = resolve_lines_to_highlight source, (attr 'highlight'), start_line_number if attr? 'highlight'
|
1199
957
|
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
958
|
+
highlighted, source_offset = syntax_hl.highlight self, source, (attr 'language'),
|
959
|
+
callouts: callout_marks,
|
960
|
+
css_mode: (doc_attrs[%(#{syntax_hl_name}-css)] || :class).to_sym,
|
961
|
+
highlight_lines: highlight_lines,
|
962
|
+
number_lines: linenums_mode,
|
963
|
+
start_line_number: start_line_number,
|
964
|
+
style: doc_attrs[%(#{syntax_hl_name}-style)]
|
1204
965
|
|
1205
|
-
|
1206
|
-
|
1207
|
-
end
|
966
|
+
# fix passthrough placeholders that got caught up in syntax highlighting
|
967
|
+
highlighted = highlighted.gsub HighlightedPassSlotRx, %(#{PASS_START}\\1#{PASS_END}) unless @passthroughs.empty?
|
1208
968
|
|
1209
|
-
|
1210
|
-
|
1211
|
-
attrs['role'] = roles.join ' ' unless roles.empty?
|
1212
|
-
attrs
|
1213
|
-
else
|
1214
|
-
{'role' => str}
|
1215
|
-
end
|
969
|
+
# NOTE highlight method may have depleted callouts
|
970
|
+
callout_marks.nil_or_empty? ? highlighted : (restore_callouts highlighted, callout_marks, source_offset)
|
1216
971
|
end
|
1217
972
|
|
1218
|
-
#
|
973
|
+
# Public: Resolve the line numbers in the specified source to highlight from the provided spec.
|
1219
974
|
#
|
1220
|
-
#
|
1221
|
-
# posattrs - An Array of positional attribute names (default: []).
|
1222
|
-
# opts - A Hash of options to control how the string is parsed (default: {}):
|
1223
|
-
# :into - The Hash to parse the attributes into (optional, default: false).
|
1224
|
-
# :sub_input - A Boolean that indicates whether to substitute attributes prior to
|
1225
|
-
# parsing (optional, default: false).
|
1226
|
-
# :sub_result - A Boolean that indicates whether to apply substitutions
|
1227
|
-
# single-quoted attribute values (optional, default: true).
|
1228
|
-
# :unescape_input - A Boolean that indicates whether to unescape square brackets prior
|
1229
|
-
# to parsing (optional, default: false).
|
975
|
+
# e.g., highlight="1-5, !2, 10" or highlight=1-5;!2,10
|
1230
976
|
#
|
1231
|
-
#
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
if
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
977
|
+
# source - The String source.
|
978
|
+
# spec - The lines specifier (e.g., "1-5, !2, 10" or "1..5;!2;10")
|
979
|
+
# start - The line number of the first line (optional, default: false)
|
980
|
+
#
|
981
|
+
# Returns an [Array] of unique, sorted line numbers.
|
982
|
+
def resolve_lines_to_highlight source, spec, start = nil
|
983
|
+
lines = []
|
984
|
+
spec = spec.delete ' ' if spec.include? ' '
|
985
|
+
((spec.include? ',') ? (spec.split ',') : (spec.split ';')).map do |entry|
|
986
|
+
if entry.start_with? '!'
|
987
|
+
entry = entry.slice 1, entry.length
|
988
|
+
negate = true
|
989
|
+
end
|
990
|
+
if (delim = (entry.include? '..') ? '..' : ((entry.include? '-') ? '-' : nil))
|
991
|
+
from, _, to = entry.partition delim
|
992
|
+
to = (source.count LF) + 1 if to.empty? || (to = to.to_i) < 0
|
993
|
+
if negate
|
994
|
+
lines -= (from.to_i..to).to_a
|
995
|
+
else
|
996
|
+
lines |= (from.to_i..to).to_a
|
997
|
+
end
|
998
|
+
elsif negate
|
999
|
+
lines.delete entry.to_i
|
1000
|
+
elsif !lines.include?(line = entry.to_i)
|
1001
|
+
lines << line
|
1002
|
+
end
|
1242
1003
|
end
|
1004
|
+
# If the start attribute is defined, then the lines to highlight specified by the provided spec should be relative to the start value.
|
1005
|
+
unless (shift = start ? start - 1 : 0) == 0
|
1006
|
+
lines = lines.map {|it| it - shift }
|
1007
|
+
end
|
1008
|
+
lines.sort
|
1243
1009
|
end
|
1244
1010
|
|
1245
|
-
#
|
1011
|
+
# Public: Extract the passthrough text from the document for reinsertion after processing.
|
1246
1012
|
#
|
1247
|
-
#
|
1013
|
+
# text - The String from which to extract passthrough fragments
|
1248
1014
|
#
|
1249
|
-
# Returns
|
1250
|
-
def
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
if (
|
1260
|
-
|
1015
|
+
# Returns the String text with passthrough regions substituted with placeholders
|
1016
|
+
def extract_passthroughs text
|
1017
|
+
compat_mode = @document.compat_mode
|
1018
|
+
passthrus = @passthroughs
|
1019
|
+
text = text.gsub InlinePassMacroRx do
|
1020
|
+
if (boundary = $4) # $$, ++, or +++
|
1021
|
+
# skip ++ in compat mode, handled as normal quoted text
|
1022
|
+
next %(#{$2 ? "#{$1}[#{$2}]#{$3}" : "#{$1}#{$3}"}++#{extract_passthroughs $5}++) if compat_mode && boundary == '++'
|
1023
|
+
|
1024
|
+
if (attrlist = $2)
|
1025
|
+
if (escape_count = $3.length) > 0
|
1026
|
+
# NOTE we don't look for nested unconstrained pass macros
|
1027
|
+
next %(#{$1}[#{attrlist}]#{RS * (escape_count - 1)}#{boundary}#{$5}#{boundary})
|
1028
|
+
elsif $1 == RS
|
1029
|
+
preceding = %([#{attrlist}])
|
1261
1030
|
else
|
1262
|
-
|
1031
|
+
if boundary == '++' && (attrlist.end_with? 'x-')
|
1032
|
+
old_behavior = true
|
1033
|
+
attrlist = attrlist.slice 0, attrlist.length - 2
|
1034
|
+
end
|
1035
|
+
attributes = parse_quoted_text_attributes attrlist
|
1263
1036
|
end
|
1037
|
+
elsif (escape_count = $3.length) > 0
|
1038
|
+
# NOTE we don't look for nested unconstrained pass macros
|
1039
|
+
next %(#{RS * (escape_count - 1)}#{boundary}#{$5}#{boundary})
|
1040
|
+
end
|
1041
|
+
subs = (boundary == '+++' ? [] : BASIC_SUBS)
|
1042
|
+
|
1043
|
+
if attributes
|
1044
|
+
if old_behavior
|
1045
|
+
passthrus[passthru_key = passthrus.size] = { text: $5, subs: NORMAL_SUBS, type: :monospaced, attributes: attributes }
|
1046
|
+
else
|
1047
|
+
passthrus[passthru_key = passthrus.size] = { text: $5, subs: subs, type: :unquoted, attributes: attributes }
|
1048
|
+
end
|
1049
|
+
else
|
1050
|
+
passthrus[passthru_key = passthrus.size] = { text: $5, subs: subs }
|
1051
|
+
end
|
1052
|
+
else # pass:[]
|
1053
|
+
# NOTE we don't look for nested pass:[] macros
|
1054
|
+
# honor the escape
|
1055
|
+
next $&.slice 1, $&.length if $6 == RS
|
1056
|
+
if (subs = $7)
|
1057
|
+
passthrus[passthru_key = passthrus.size] = { text: (normalize_text $8, nil, true), subs: (resolve_pass_subs subs) }
|
1058
|
+
else
|
1059
|
+
passthrus[passthru_key = passthrus.size] = { text: (normalize_text $8, nil, true) }
|
1264
1060
|
end
|
1265
1061
|
end
|
1266
1062
|
|
1267
|
-
|
1268
|
-
end
|
1269
|
-
end
|
1063
|
+
%(#{preceding || ''}#{PASS_START}#{passthru_key}#{PASS_END})
|
1064
|
+
end if (text.include? '++') || (text.include? '$$') || (text.include? 'ss:')
|
1270
1065
|
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
end
|
1066
|
+
pass_inline_char1, pass_inline_char2, pass_inline_rx = InlinePassRx[compat_mode]
|
1067
|
+
text = text.gsub pass_inline_rx do
|
1068
|
+
preceding = $1
|
1069
|
+
attrlist = $2
|
1070
|
+
escape_mark = RS if (quoted_text = $3).start_with? RS
|
1071
|
+
format_mark = $4
|
1072
|
+
content = $5
|
1279
1073
|
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
end
|
1286
|
-
str
|
1287
|
-
end
|
1074
|
+
if compat_mode
|
1075
|
+
old_behavior = true
|
1076
|
+
elsif (old_behavior = attrlist && (attrlist.end_with? 'x-'))
|
1077
|
+
attrlist = attrlist.slice 0, attrlist.length - 2
|
1078
|
+
end
|
1288
1079
|
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1080
|
+
if attrlist
|
1081
|
+
if format_mark == '`' && !old_behavior
|
1082
|
+
next extract_inner_passthrough content, %(#{preceding}[#{attrlist}]#{escape_mark})
|
1083
|
+
elsif escape_mark
|
1084
|
+
# honor the escape of the formatting mark
|
1085
|
+
next %(#{preceding}[#{attrlist}]#{quoted_text.slice 1, quoted_text.length})
|
1086
|
+
elsif preceding == RS
|
1087
|
+
# honor the escape of the attributes
|
1088
|
+
preceding = %([#{attrlist}])
|
1089
|
+
else
|
1090
|
+
attributes = parse_quoted_text_attributes attrlist
|
1091
|
+
end
|
1092
|
+
elsif format_mark == '`' && !old_behavior
|
1093
|
+
next extract_inner_passthrough content, %(#{preceding}#{escape_mark})
|
1094
|
+
elsif escape_mark
|
1095
|
+
# honor the escape of the formatting mark
|
1096
|
+
next %(#{preceding}#{quoted_text.slice 1, quoted_text.length})
|
1097
|
+
end
|
1297
1098
|
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
values = []
|
1305
|
-
current = []
|
1306
|
-
quote_open = false
|
1307
|
-
str.each_char do |c|
|
1308
|
-
case c
|
1309
|
-
when ','
|
1310
|
-
if quote_open
|
1311
|
-
current << c
|
1312
|
-
else
|
1313
|
-
values << current.join.strip
|
1314
|
-
current = []
|
1315
|
-
end
|
1316
|
-
when '"'
|
1317
|
-
quote_open = !quote_open
|
1099
|
+
if compat_mode
|
1100
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS, attributes: attributes, type: :monospaced }
|
1101
|
+
elsif attributes
|
1102
|
+
if old_behavior
|
1103
|
+
subs = (format_mark == '`' ? BASIC_SUBS : NORMAL_SUBS)
|
1104
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, attributes: attributes, type: :monospaced }
|
1318
1105
|
else
|
1319
|
-
|
1106
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS, attributes: attributes, type: :unquoted }
|
1320
1107
|
end
|
1108
|
+
else
|
1109
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: BASIC_SUBS }
|
1321
1110
|
end
|
1322
1111
|
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1112
|
+
%(#{preceding}#{PASS_START}#{passthru_key}#{PASS_END})
|
1113
|
+
end if (text.include? pass_inline_char1) || (pass_inline_char2 && (text.include? pass_inline_char2))
|
1114
|
+
|
1115
|
+
# NOTE we need to do the stem in a subsequent step to allow it to be escaped by the former
|
1116
|
+
text = text.gsub InlineStemMacroRx do
|
1117
|
+
# honor the escape
|
1118
|
+
next $&.slice 1, $&.length if $&.start_with? RS
|
1327
1119
|
|
1328
|
-
|
1120
|
+
if (type = $1.to_sym) == :stem
|
1121
|
+
type = STEM_TYPE_ALIASES[@document.attributes['stem']].to_sym
|
1122
|
+
end
|
1123
|
+
subs = $2
|
1124
|
+
content = normalize_text $3, nil, true
|
1125
|
+
# NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc.py
|
1126
|
+
content = content.slice 1, content.length - 2 if type == :latexmath && (content.start_with? '$') && (content.end_with? '$')
|
1127
|
+
subs = subs ? (resolve_pass_subs subs) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
|
1128
|
+
passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: type }
|
1129
|
+
%(#{PASS_START}#{passthru_key}#{PASS_END})
|
1130
|
+
end if (text.include? ':') && ((text.include? 'stem:') || (text.include? 'math:'))
|
1131
|
+
|
1132
|
+
text
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
# Public: Restore the passthrough text by reinserting into the placeholder positions
|
1136
|
+
#
|
1137
|
+
# text - The String text into which to restore the passthrough text
|
1138
|
+
#
|
1139
|
+
# returns The String text with the passthrough text restored
|
1140
|
+
def restore_passthroughs text
|
1141
|
+
passthrus = @passthroughs
|
1142
|
+
text.gsub PassSlotRx do
|
1143
|
+
if (pass = passthrus[$1.to_i])
|
1144
|
+
subbed_text = apply_subs(pass[:text], pass[:subs])
|
1145
|
+
if (type = pass[:type])
|
1146
|
+
if (attributes = pass[:attributes])
|
1147
|
+
id = attributes['id']
|
1148
|
+
end
|
1149
|
+
subbed_text = Inline.new(self, :quoted, subbed_text, type: type, id: id, attributes: attributes).convert
|
1150
|
+
end
|
1151
|
+
subbed_text.include?(PASS_START) ? restore_passthroughs(subbed_text) : subbed_text
|
1152
|
+
else
|
1153
|
+
logger.error %(unresolved passthrough detected: #{text})
|
1154
|
+
'??pass??'
|
1155
|
+
end
|
1156
|
+
end
|
1329
1157
|
end
|
1330
1158
|
|
1331
|
-
#
|
1159
|
+
# Public: Resolve the list of comma-delimited subs against the possible options.
|
1332
1160
|
#
|
1333
|
-
# subs
|
1161
|
+
# subs - The comma-delimited String of substitution names or aliases.
|
1162
|
+
# type - A Symbol representing the context for which the subs are being resolved (default: :block).
|
1163
|
+
# defaults - An Array of substitutions to start with when computing incremental substitutions (default: nil).
|
1164
|
+
# subject - The String to use in log messages to communicate the subject for which subs are being resolved (default: nil)
|
1334
1165
|
#
|
1335
|
-
#
|
1166
|
+
# Returns An Array of Symbols representing the substitution operation or nothing if no subs are found.
|
1336
1167
|
def resolve_subs subs, type = :block, defaults = nil, subject = nil
|
1337
1168
|
return if subs.nil_or_empty?
|
1338
1169
|
# QUESTION should we store candidates as a Set instead of an Array?
|
@@ -1386,7 +1217,7 @@ module Substitutors
|
|
1386
1217
|
end
|
1387
1218
|
end
|
1388
1219
|
return unless candidates
|
1389
|
-
# weed out invalid options and remove duplicates (order is preserved; first
|
1220
|
+
# weed out invalid options and remove duplicates (order is preserved; first occurrence wins)
|
1390
1221
|
resolved = candidates & SUB_OPTIONS[type]
|
1391
1222
|
unless (candidates - resolved).empty?
|
1392
1223
|
invalid = candidates - resolved
|
@@ -1395,254 +1226,327 @@ module Substitutors
|
|
1395
1226
|
resolved
|
1396
1227
|
end
|
1397
1228
|
|
1229
|
+
# Public: Call resolve_subs for the :block type.
|
1398
1230
|
def resolve_block_subs subs, defaults, subject
|
1399
1231
|
resolve_subs subs, :block, defaults, subject
|
1400
1232
|
end
|
1401
1233
|
|
1234
|
+
# Public: Call resolve_subs for the :inline type with the subject set as passthrough macro.
|
1402
1235
|
def resolve_pass_subs subs
|
1403
1236
|
resolve_subs subs, :inline, nil, 'passthrough macro'
|
1404
1237
|
end
|
1405
1238
|
|
1406
|
-
# Public:
|
1407
|
-
# on the document, otherwise return the text unprocessed
|
1239
|
+
# Public: Expand all groups in the subs list and return. If no subs are resolved, return nil.
|
1408
1240
|
#
|
1409
|
-
#
|
1410
|
-
#
|
1411
|
-
# incorrectly processed by the source highlighter.
|
1241
|
+
# subs - The substitutions to expand; can be a Symbol, Symbol Array, or String
|
1242
|
+
# subject - The String to use in log messages to communicate the subject for which subs are being resolved (default: nil)
|
1412
1243
|
#
|
1413
|
-
#
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
(
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
highlighter_loaded = true
|
1244
|
+
# Returns a Symbol Array of substitutions to pass to apply_subs or nil if no substitutions were resolved.
|
1245
|
+
def expand_subs subs, subject = nil
|
1246
|
+
case subs
|
1247
|
+
when ::Symbol
|
1248
|
+
subs == :none ? nil : SUB_GROUPS[subs] || [subs]
|
1249
|
+
when ::Array
|
1250
|
+
expanded_subs = []
|
1251
|
+
subs.each do |key|
|
1252
|
+
unless key == :none
|
1253
|
+
if (sub_group = SUB_GROUPS[key])
|
1254
|
+
expanded_subs += sub_group
|
1255
|
+
else
|
1256
|
+
expanded_subs << key
|
1257
|
+
end
|
1428
1258
|
end
|
1429
1259
|
end
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1260
|
+
expanded_subs.empty? ? nil : expanded_subs
|
1261
|
+
else
|
1262
|
+
resolve_subs subs, :inline, nil, subject
|
1263
|
+
end
|
1264
|
+
end
|
1265
|
+
|
1266
|
+
# Internal: Commit the requested substitutions to this block.
|
1267
|
+
#
|
1268
|
+
# Looks for an attribute named "subs". If present, resolves substitutions
|
1269
|
+
# from the value of that attribute and assigns them to the subs property on
|
1270
|
+
# this block. Otherwise, uses the substitutions assigned to the default_subs
|
1271
|
+
# property, if specified, or selects a default set of substitutions based on
|
1272
|
+
# the content model of the block.
|
1273
|
+
#
|
1274
|
+
# Returns nothing
|
1275
|
+
def commit_subs
|
1276
|
+
unless (default_subs = @default_subs)
|
1277
|
+
case @content_model
|
1278
|
+
when :simple
|
1279
|
+
default_subs = NORMAL_SUBS
|
1280
|
+
when :verbatim
|
1281
|
+
# NOTE :literal with listparagraph-option gets folded into text of list item later
|
1282
|
+
default_subs = @context == :verse ? NORMAL_SUBS : VERBATIM_SUBS
|
1283
|
+
when :raw
|
1284
|
+
# TODO make pass subs a compliance setting; AsciiDoc.py performs :attributes and :macros on a pass block
|
1285
|
+
default_subs = @context == :stem ? BASIC_SUBS : NO_SUBS
|
1286
|
+
else
|
1287
|
+
return @subs
|
1439
1288
|
end
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
if (custom_subs = @attributes['subs'])
|
1292
|
+
@subs = (resolve_block_subs custom_subs, default_subs, @context) || []
|
1440
1293
|
else
|
1441
|
-
|
1442
|
-
|
1294
|
+
@subs = default_subs.drop 0
|
1295
|
+
end
|
1296
|
+
|
1297
|
+
# QUESION delegate this logic to a method?
|
1298
|
+
if @context == :listing && @style == 'source' && (syntax_hl = @document.syntax_highlighter) &&
|
1299
|
+
syntax_hl.highlight? && (idx = @subs.index :specialcharacters)
|
1300
|
+
@subs[idx] = :highlight
|
1443
1301
|
end
|
1444
1302
|
|
1445
|
-
|
1303
|
+
nil
|
1304
|
+
end
|
1446
1305
|
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
callout_marks = nil if callout_marks.empty?
|
1306
|
+
# Internal: Parse attributes in name or name=value format from a comma-separated String
|
1307
|
+
#
|
1308
|
+
# attrlist - A comma-separated String list of attributes in name or name=value format.
|
1309
|
+
# posattrs - An Array of positional attribute names (default: []).
|
1310
|
+
# opts - A Hash of options to control how the string is parsed (default: {}):
|
1311
|
+
# :into - The Hash to parse the attributes into (optional, default: false).
|
1312
|
+
# :sub_input - A Boolean that indicates whether to substitute attributes prior to
|
1313
|
+
# parsing (optional, default: false).
|
1314
|
+
# :sub_result - A Boolean that indicates whether to apply substitutions
|
1315
|
+
# single-quoted attribute values (optional, default: true).
|
1316
|
+
# :unescape_input - A Boolean that indicates whether to unescape square brackets prior
|
1317
|
+
# to parsing (optional, default: false).
|
1318
|
+
#
|
1319
|
+
# Returns an empty Hash if attrlist is nil or empty, otherwise a Hash of parsed attributes.
|
1320
|
+
def parse_attributes attrlist, posattrs = [], opts = {}
|
1321
|
+
return {} if attrlist ? attrlist.empty? : true
|
1322
|
+
attrlist = normalize_text attrlist, true, true if opts[:unescape_input]
|
1323
|
+
attrlist = @document.sub_attributes attrlist if opts[:sub_input] && (attrlist.include? ATTR_REF_HEAD)
|
1324
|
+
# substitutions are only performed on attribute values if block is not nil
|
1325
|
+
block = self if opts[:sub_result]
|
1326
|
+
if (into = opts[:into])
|
1327
|
+
AttributeList.new(attrlist, block).parse_into(into, posattrs)
|
1470
1328
|
else
|
1471
|
-
|
1329
|
+
AttributeList.new(attrlist, block).parse(posattrs)
|
1472
1330
|
end
|
1331
|
+
end
|
1473
1332
|
|
1474
|
-
|
1475
|
-
highlight_lines = nil
|
1333
|
+
private
|
1476
1334
|
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
end
|
1505
|
-
end
|
1506
|
-
# NOTE highlight can return nil if something goes wrong; fallback to source if this happens
|
1507
|
-
# TODO we could add the line numbers in ourselves instead of having to strip out the junk
|
1508
|
-
if (attr? 'linenums', nil, false) && (opts[:linenostart] = (start = attr 'start', 1, false).to_i < 1 ? 1 : start) &&
|
1509
|
-
(opts[:linenos] = @document.attributes['pygments-linenums-mode'] || 'table') == 'table'
|
1510
|
-
linenums_mode = :table
|
1511
|
-
if (result = lexer.highlight source, :options => opts)
|
1512
|
-
result = (result.sub PygmentsWrapperDivRx, '\1').gsub PygmentsWrapperPreRx, '\1'
|
1335
|
+
# This method is used in cases when the attrlist can be mixed with the text of a macro.
|
1336
|
+
# If no attributes are detected aside from the first positional attribute, and the first positional
|
1337
|
+
# attribute matches the attrlist, then the original text is returned.
|
1338
|
+
def extract_attributes_from_text text, default_text = nil
|
1339
|
+
attrlist = (text.include? LF) ? (text.tr LF, ' ') : text
|
1340
|
+
if (resolved_text = (attrs = (AttributeList.new attrlist, self).parse)[1])
|
1341
|
+
# NOTE if resolved text remains unchanged, clear attributes and return unparsed text
|
1342
|
+
resolved_text == attrlist ? [text, attrs.clear] : [resolved_text, attrs]
|
1343
|
+
else
|
1344
|
+
[default_text, attrs]
|
1345
|
+
end
|
1346
|
+
end
|
1347
|
+
|
1348
|
+
# Internal: Extract the callout numbers from the source to prepare it for syntax highlighting.
|
1349
|
+
def extract_callouts source
|
1350
|
+
callout_marks = {}
|
1351
|
+
autonum = lineno = 0
|
1352
|
+
last_lineno = nil
|
1353
|
+
callout_rx = (attr? 'line-comment') ? CalloutExtractRxMap[attr 'line-comment'] : CalloutExtractRx
|
1354
|
+
# extract callout marks, indexed by line number
|
1355
|
+
source = (source.split LF, -1).map do |line|
|
1356
|
+
lineno += 1
|
1357
|
+
line.gsub callout_rx do
|
1358
|
+
# honor the escape
|
1359
|
+
if $2
|
1360
|
+
# use sub since it might be behind a line comment
|
1361
|
+
$&.sub RS, ''
|
1513
1362
|
else
|
1514
|
-
|
1363
|
+
(callout_marks[lineno] ||= []) << [$1 || ($3 == '--' ? ['<!--', '-->'] : nil), $4 == '.' ? (autonum += 1).to_s : $4]
|
1364
|
+
last_lineno = lineno
|
1365
|
+
''
|
1515
1366
|
end
|
1516
|
-
elsif (result = lexer.highlight source, :options => opts)
|
1517
|
-
if PygmentsWrapperPreRx =~ result
|
1518
|
-
result = $1
|
1519
|
-
end
|
1520
|
-
else
|
1521
|
-
result = sub_specialchars source
|
1522
1367
|
end
|
1368
|
+
end.join LF
|
1369
|
+
if last_lineno
|
1370
|
+
source = %(#{source}#{LF}) if last_lineno == lineno
|
1371
|
+
else
|
1372
|
+
callout_marks = nil
|
1523
1373
|
end
|
1374
|
+
[source, callout_marks]
|
1375
|
+
end
|
1524
1376
|
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
tail = nil
|
1540
|
-
if callout_on_last && callout_marks.empty? && linenums_mode == :table
|
1541
|
-
if highlighter == 'coderay' && (pos = line.index '</pre>')
|
1542
|
-
line, tail = (line.slice 0, pos), (line.slice pos, line.length)
|
1543
|
-
elsif highlighter == 'pygments' && (pos = line.start_with? '</td>')
|
1544
|
-
line, tail = '', line
|
1545
|
-
end
|
1546
|
-
end
|
1547
|
-
if conums.size == 1
|
1548
|
-
guard, conum = conums[0]
|
1549
|
-
%(#{line}#{Inline.new(self, :callout, conum == '.' ? (autonum += 1).to_s : conum, :id => @document.callouts.read_next_id, :attributes => { 'guard' => guard }).convert}#{tail})
|
1550
|
-
else
|
1551
|
-
conums_markup = conums.map {|guard_it, conum_it| Inline.new(self, :callout, conum_it == '.' ? (autonum += 1).to_s : conum_it, :id => @document.callouts.read_next_id, :attributes => { 'guard' => guard_it }).convert }.join ' '
|
1552
|
-
%(#{line}#{conums_markup}#{tail})
|
1553
|
-
end
|
1377
|
+
# Internal: Restore the callout numbers to the highlighted source.
|
1378
|
+
def restore_callouts source, callout_marks, source_offset = nil
|
1379
|
+
if source_offset
|
1380
|
+
preamble = source.slice 0, source_offset
|
1381
|
+
source = source.slice source_offset, source.length
|
1382
|
+
else
|
1383
|
+
preamble = ''
|
1384
|
+
end
|
1385
|
+
lineno = 0
|
1386
|
+
preamble + ((source.split LF, -1).map do |line|
|
1387
|
+
if (conums = callout_marks.delete lineno += 1)
|
1388
|
+
if conums.size == 1
|
1389
|
+
guard, numeral = conums[0]
|
1390
|
+
%(#{line}#{Inline.new(self, :callout, numeral, id: @document.callouts.read_next_id, attributes: { 'guard' => guard }).convert})
|
1554
1391
|
else
|
1555
|
-
line
|
1392
|
+
%(#{line}#{conums.map do |guard_it, numeral_it|
|
1393
|
+
Inline.new(self, :callout, numeral_it, id: @document.callouts.read_next_id, attributes: { 'guard' => guard_it }).convert
|
1394
|
+
end.join ' '})
|
1556
1395
|
end
|
1557
|
-
|
1396
|
+
else
|
1397
|
+
line
|
1398
|
+
end
|
1399
|
+
end.join LF)
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
# Internal: Extract nested single-plus passthrough; otherwise return unprocessed
|
1403
|
+
def extract_inner_passthrough text, pre
|
1404
|
+
if (text.end_with? '+') && (text.start_with? '+', '\+') && SinglePlusInlinePassRx =~ text
|
1405
|
+
if $1
|
1406
|
+
%(#{pre}`+#{$2}+`)
|
1407
|
+
else
|
1408
|
+
@passthroughs[passthru_key = @passthroughs.size] = { text: $2, subs: BASIC_SUBS }
|
1409
|
+
%(#{pre}`#{PASS_START}#{passthru_key}#{PASS_END}`)
|
1410
|
+
end
|
1558
1411
|
else
|
1559
|
-
|
1412
|
+
%(#{pre}`#{text}`)
|
1560
1413
|
end
|
1561
1414
|
end
|
1562
1415
|
|
1563
|
-
#
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1416
|
+
# Internal: Convert a quoted text region
|
1417
|
+
#
|
1418
|
+
# match - The MatchData for the quoted text region
|
1419
|
+
# type - The quoting type (single, double, strong, emphasis, monospaced, etc)
|
1420
|
+
# scope - The scope of the quoting (constrained or unconstrained)
|
1421
|
+
#
|
1422
|
+
# Returns The converted String text for the quoted text region
|
1423
|
+
def convert_quoted_text match, type, scope
|
1424
|
+
if match[0].start_with? RS
|
1425
|
+
if scope == :constrained && (attrs = match[2])
|
1426
|
+
unescaped_attrs = %([#{attrs}])
|
1427
|
+
else
|
1428
|
+
return match[0].slice 1, match[0].length
|
1572
1429
|
end
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
lines -= line_nums
|
1579
|
-
else
|
1580
|
-
lines.concat line_nums
|
1581
|
-
end
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
if scope == :constrained
|
1433
|
+
if unescaped_attrs
|
1434
|
+
%(#{unescaped_attrs}#{Inline.new(self, :quoted, match[3], type: type).convert})
|
1582
1435
|
else
|
1583
|
-
if
|
1584
|
-
|
1585
|
-
|
1586
|
-
lines << entry.to_i
|
1436
|
+
if (attrlist = match[2])
|
1437
|
+
id = (attributes = parse_quoted_text_attributes attrlist)['id']
|
1438
|
+
type = :unquoted if type == :mark
|
1587
1439
|
end
|
1440
|
+
%(#{match[1]}#{Inline.new(self, :quoted, match[3], type: type, id: id, attributes: attributes).convert})
|
1588
1441
|
end
|
1442
|
+
else
|
1443
|
+
if (attrlist = match[1])
|
1444
|
+
id = (attributes = parse_quoted_text_attributes attrlist)['id']
|
1445
|
+
type = :unquoted if type == :mark
|
1446
|
+
end
|
1447
|
+
Inline.new(self, :quoted, match[2], type: type, id: id, attributes: attributes).convert
|
1589
1448
|
end
|
1590
|
-
lines.sort.uniq
|
1591
1449
|
end
|
1592
1450
|
|
1593
|
-
#
|
1594
|
-
#
|
1595
|
-
# source - the source code String on which to apply verbatim substitutions
|
1596
|
-
# process_callouts - a Boolean flag indicating whether callout marks should be substituted
|
1451
|
+
# Internal: Substitute replacement text for matched location
|
1597
1452
|
#
|
1598
|
-
# returns the substituted
|
1599
|
-
def
|
1600
|
-
|
1453
|
+
# returns The String text with the replacement characters substituted
|
1454
|
+
def do_replacement m, replacement, restore
|
1455
|
+
if (captured = m[0]).include? RS
|
1456
|
+
# we have to use sub since we aren't sure it's the first char
|
1457
|
+
captured.sub RS, ''
|
1458
|
+
else
|
1459
|
+
case restore
|
1460
|
+
when :none
|
1461
|
+
replacement
|
1462
|
+
when :bounding
|
1463
|
+
m[1] + replacement + m[2]
|
1464
|
+
else # :leading
|
1465
|
+
m[1] + replacement
|
1466
|
+
end
|
1467
|
+
end
|
1601
1468
|
end
|
1602
1469
|
|
1603
|
-
# Internal:
|
1470
|
+
# Internal: Inserts text into a formatted text enclosure; used by xreftext
|
1471
|
+
alias sub_placeholder sprintf unless RUBY_ENGINE == 'opal'
|
1472
|
+
|
1473
|
+
# Internal: Parse the attributes that are defined on quoted (aka formatted) text
|
1604
1474
|
#
|
1605
|
-
#
|
1606
|
-
#
|
1607
|
-
# this block. Otherwise, uses the substitutions assigned to the default_subs
|
1608
|
-
# property, if specified, or selects a default set of substitutions based on
|
1609
|
-
# the content model of the block.
|
1475
|
+
# str - A non-nil String of unprocessed attributes;
|
1476
|
+
# space-separated roles (e.g., role1 role2) or the id/role shorthand syntax (e.g., #idname.role)
|
1610
1477
|
#
|
1611
|
-
# Returns
|
1612
|
-
def
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1478
|
+
# Returns a Hash of attributes (role and id only)
|
1479
|
+
def parse_quoted_text_attributes str
|
1480
|
+
# NOTE attributes are typically resolved after quoted text, so substitute eagerly
|
1481
|
+
str = sub_attributes str if str.include? ATTR_REF_HEAD
|
1482
|
+
# for compliance, only consider first positional attribute (very unlikely)
|
1483
|
+
str = str.slice 0, (str.index ',') if str.include? ','
|
1484
|
+
if (str = str.strip).empty?
|
1485
|
+
{}
|
1486
|
+
elsif (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
|
1487
|
+
before, _, after = str.partition '#'
|
1488
|
+
attrs = {}
|
1489
|
+
if after.empty?
|
1490
|
+
attrs['role'] = (before.tr '.', ' ').lstrip if before.length > 1
|
1491
|
+
else
|
1492
|
+
id, _, roles = after.partition '.'
|
1493
|
+
attrs['id'] = id unless id.empty?
|
1494
|
+
if roles.empty?
|
1495
|
+
attrs['role'] = (before.tr '.', ' ').lstrip if before.length > 1
|
1496
|
+
elsif before.length > 1
|
1497
|
+
attrs['role'] = ((before + '.' + roles).tr '.', ' ').lstrip
|
1622
1498
|
else
|
1623
|
-
|
1499
|
+
attrs['role'] = roles.tr '.', ' '
|
1624
1500
|
end
|
1625
|
-
when :raw
|
1626
|
-
# TODO make pass subs a compliance setting; AsciiDoc Python performs :attributes and :macros on a pass block
|
1627
|
-
default_subs = @context == :stem ? BASIC_SUBS : NONE_SUBS
|
1628
|
-
else
|
1629
|
-
return @subs
|
1630
1501
|
end
|
1631
|
-
|
1632
|
-
|
1633
|
-
if (custom_subs = @attributes['subs'])
|
1634
|
-
@subs = (resolve_block_subs custom_subs, default_subs, @context) || []
|
1502
|
+
attrs
|
1635
1503
|
else
|
1636
|
-
|
1504
|
+
{ 'role' => str }
|
1637
1505
|
end
|
1506
|
+
end
|
1638
1507
|
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1508
|
+
# Internal: Normalize text to prepare it for parsing.
|
1509
|
+
#
|
1510
|
+
# If normalize_whitespace is true, strip surrounding whitespace and fold newlines. If unescape_closing_square_bracket
|
1511
|
+
# is set, unescape any escaped closing square brackets.
|
1512
|
+
#
|
1513
|
+
# Returns the normalized text String
|
1514
|
+
def normalize_text text, normalize_whitespace = nil, unescape_closing_square_brackets = nil
|
1515
|
+
unless text.empty?
|
1516
|
+
text = text.strip.tr LF, ' ' if normalize_whitespace
|
1517
|
+
text = text.gsub ESC_R_SB, R_SB if unescape_closing_square_brackets && (text.include? R_SB)
|
1643
1518
|
end
|
1519
|
+
text
|
1520
|
+
end
|
1644
1521
|
|
1645
|
-
|
1522
|
+
# Internal: Split text formatted as CSV with support
|
1523
|
+
# for double-quoted values (in which commas are ignored)
|
1524
|
+
def split_simple_csv str
|
1525
|
+
if str.empty?
|
1526
|
+
[]
|
1527
|
+
elsif str.include? '"'
|
1528
|
+
values = []
|
1529
|
+
accum = ''
|
1530
|
+
quote_open = nil
|
1531
|
+
str.each_char do |c|
|
1532
|
+
case c
|
1533
|
+
when ','
|
1534
|
+
if quote_open
|
1535
|
+
accum += c
|
1536
|
+
else
|
1537
|
+
values << accum.strip
|
1538
|
+
accum = ''
|
1539
|
+
end
|
1540
|
+
when '"'
|
1541
|
+
quote_open = !quote_open
|
1542
|
+
else
|
1543
|
+
accum += c
|
1544
|
+
end
|
1545
|
+
end
|
1546
|
+
values << accum.strip
|
1547
|
+
else
|
1548
|
+
str.split(',').map {|it| it.strip }
|
1549
|
+
end
|
1646
1550
|
end
|
1647
1551
|
end
|
1648
1552
|
end
|