asciidoctor 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- data/README.asciidoc +11 -2
- data/asciidoctor.gemspec +3 -2
- data/lib/asciidoctor.rb +95 -62
- data/lib/asciidoctor/abstract_block.rb +7 -5
- data/lib/asciidoctor/abstract_node.rb +63 -15
- data/lib/asciidoctor/attribute_list.rb +3 -1
- data/lib/asciidoctor/backends/base_template.rb +17 -7
- data/lib/asciidoctor/backends/docbook45.rb +182 -150
- data/lib/asciidoctor/backends/html5.rb +138 -110
- data/lib/asciidoctor/block.rb +21 -18
- data/lib/asciidoctor/callouts.rb +3 -1
- data/lib/asciidoctor/cli/invoker.rb +3 -3
- data/lib/asciidoctor/cli/options.rb +6 -6
- data/lib/asciidoctor/debug.rb +7 -6
- data/lib/asciidoctor/document.rb +197 -25
- data/lib/asciidoctor/errors.rb +1 -1
- data/lib/asciidoctor/helpers.rb +29 -0
- data/lib/asciidoctor/inline.rb +11 -4
- data/lib/asciidoctor/lexer.rb +338 -182
- data/lib/asciidoctor/list_item.rb +14 -12
- data/lib/asciidoctor/reader.rb +423 -206
- data/lib/asciidoctor/renderer.rb +59 -15
- data/lib/asciidoctor/section.rb +7 -4
- data/lib/asciidoctor/substituters.rb +536 -511
- data/lib/asciidoctor/table.rb +473 -472
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +23 -14
- data/man/asciidoctor.ad +13 -7
- data/test/attributes_test.rb +42 -8
- data/test/blocks_test.rb +161 -1
- data/test/document_test.rb +134 -16
- data/test/invoker_test.rb +14 -6
- data/test/lexer_test.rb +45 -18
- data/test/lists_test.rb +79 -0
- data/test/paragraphs_test.rb +9 -1
- data/test/reader_test.rb +456 -19
- data/test/sections_test.rb +19 -0
- data/test/substitutions_test.rb +14 -12
- data/test/tables_test.rb +10 -10
- metadata +3 -5
data/lib/asciidoctor/renderer.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
module Asciidoctor
|
1
2
|
# Public: Methods for rendering Asciidoc Documents, Sections, and Blocks
|
2
3
|
# using eRuby templates.
|
3
|
-
class
|
4
|
+
class Renderer
|
4
5
|
attr_reader :compact
|
5
6
|
|
6
7
|
# Public: Initialize an Asciidoctor::Renderer object.
|
@@ -15,10 +16,10 @@ class Asciidoctor::Renderer
|
|
15
16
|
case backend
|
16
17
|
when 'html5', 'docbook45'
|
17
18
|
eruby = load_eruby options[:eruby]
|
18
|
-
#
|
19
|
+
#Helpers.require_library 'asciidoctor/backends/' + backend
|
19
20
|
require 'asciidoctor/backends/' + backend
|
20
21
|
# Load up all the template classes that we know how to render for this backend
|
21
|
-
|
22
|
+
BaseTemplate.template_classes.each do |tc|
|
22
23
|
if tc.to_s.downcase.include?('::' + backend + '::') # optimization
|
23
24
|
view_name, view_backend = self.class.extract_view_mapping(tc)
|
24
25
|
if view_backend == backend
|
@@ -27,29 +28,71 @@ class Asciidoctor::Renderer
|
|
27
28
|
end
|
28
29
|
end
|
29
30
|
else
|
30
|
-
|
31
|
+
Debug.debug { "No built-in templates for backend: #{backend}" }
|
31
32
|
end
|
32
33
|
|
33
34
|
# If user passed in a template dir, let them override our base templates
|
34
35
|
if template_dir = options.delete(:template_dir)
|
35
|
-
|
36
|
+
Helpers.require_library 'tilt'
|
37
|
+
|
38
|
+
template_glob = '*'
|
39
|
+
if (engine = options[:template_engine])
|
40
|
+
template_glob = "*.#{engine}"
|
41
|
+
# example: templates/haml
|
42
|
+
if File.directory? File.join(template_dir, engine)
|
43
|
+
template_dir = File.join template_dir, engine
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# example: templates/html5 or templates/haml/html5
|
48
|
+
if File.directory? File.join(template_dir, options[:backend])
|
49
|
+
template_dir = File.join template_dir, options[:backend]
|
50
|
+
end
|
36
51
|
|
37
|
-
|
52
|
+
view_opts = {
|
53
|
+
:erb => { :trim => '<>' },
|
54
|
+
:haml => { :attr_wrapper => '"', :ugly => true, :escape_attrs => false },
|
55
|
+
:slim => { :disable_escape => true, :sort_attrs => false, :pretty => false }
|
56
|
+
}
|
57
|
+
|
58
|
+
if backend == 'html5'
|
59
|
+
view_opts[:haml][:format] = view_opts[:slim][:format] = :html5
|
60
|
+
end
|
61
|
+
|
62
|
+
Debug.debug {
|
38
63
|
msg = []
|
39
64
|
msg << "Views going in are like so:"
|
40
65
|
msg << @views.map {|k, v| "#{k}: #{v}"}
|
41
66
|
msg << '=' * 60
|
42
67
|
msg * "\n"
|
43
68
|
}
|
69
|
+
|
70
|
+
slim_loaded = false
|
71
|
+
helpers = nil
|
44
72
|
|
45
73
|
# Grab the files in the top level of the directory (we're not traversing)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
74
|
+
Dir.glob(File.join(template_dir, template_glob)).
|
75
|
+
select{|f| File.file? f }.each do |template|
|
76
|
+
basename = File.basename(template)
|
77
|
+
if basename == 'helpers.rb'
|
78
|
+
helpers = template
|
79
|
+
next
|
80
|
+
end
|
81
|
+
name_parts = basename.split('.')
|
82
|
+
next if name_parts.size < 2
|
83
|
+
view_name = name_parts.first
|
84
|
+
ext_name = name_parts.last
|
85
|
+
if ext_name == 'slim' && !slim_loaded
|
86
|
+
# slim doesn't get loaded by Tilt
|
87
|
+
Helpers.require_library 'slim'
|
88
|
+
end
|
89
|
+
next unless Tilt.registered? ext_name
|
90
|
+
@views[view_name] = Tilt.new(template, nil, view_opts[ext_name.to_sym])
|
50
91
|
end
|
51
92
|
|
52
|
-
|
93
|
+
require helpers unless helpers.nil?
|
94
|
+
|
95
|
+
Debug.debug {
|
53
96
|
msg = []
|
54
97
|
msg << "Views going in are like so:"
|
55
98
|
msg << @views.map {|k, v| "#{k}: #{v}"}
|
@@ -72,7 +115,7 @@ class Asciidoctor::Renderer
|
|
72
115
|
if !@views.has_key? view
|
73
116
|
raise "Couldn't find a view in @views for #{view}"
|
74
117
|
else
|
75
|
-
|
118
|
+
Debug.debug { "View for #{view} is #{@views[view]}, object is #{object}" }
|
76
119
|
end
|
77
120
|
|
78
121
|
ret = @views[view].render(object, locals)
|
@@ -83,8 +126,8 @@ class Asciidoctor::Renderer
|
|
83
126
|
STDERR.puts "Rendering:"
|
84
127
|
@render_stack.each do |stack_view, stack_obj|
|
85
128
|
obj_info = case stack_obj
|
86
|
-
when
|
87
|
-
when
|
129
|
+
when Section; "SECTION #{stack_obj.title}"
|
130
|
+
when Block;
|
88
131
|
if stack_obj.context == :dlist
|
89
132
|
dt_list = stack_obj.buffer.map{|dt,dd| dt.content.strip}.join(', ')
|
90
133
|
"BLOCK :dlist (#{dt_list})"
|
@@ -122,7 +165,7 @@ class Asciidoctor::Renderer
|
|
122
165
|
name = 'erb'
|
123
166
|
end
|
124
167
|
|
125
|
-
|
168
|
+
Helpers.require_library name
|
126
169
|
|
127
170
|
if name == 'erb'
|
128
171
|
::ERB
|
@@ -176,3 +219,4 @@ class Asciidoctor::Renderer
|
|
176
219
|
end
|
177
220
|
|
178
221
|
end
|
222
|
+
end
|
data/lib/asciidoctor/section.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
module Asciidoctor
|
1
2
|
# Public: Methods for managing sections of AsciiDoc content in a document.
|
2
3
|
# The section responds as an Array of content blocks by delegating
|
3
4
|
# block-related methods to its @blocks Array.
|
@@ -17,7 +18,7 @@
|
|
17
18
|
# section << new_block
|
18
19
|
# section.size
|
19
20
|
# => 1
|
20
|
-
class
|
21
|
+
class Section < AbstractBlock
|
21
22
|
|
22
23
|
# Public: Get/Set the Integer index of this section within the parent block
|
23
24
|
attr_accessor :index
|
@@ -36,7 +37,7 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
|
|
36
37
|
if level.nil? && !parent.nil?
|
37
38
|
@level = parent.level + 1
|
38
39
|
end
|
39
|
-
if parent.is_a?(
|
40
|
+
if parent.is_a?(Section) && parent.special
|
40
41
|
@special = true
|
41
42
|
else
|
42
43
|
@special = false
|
@@ -89,7 +90,8 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
|
|
89
90
|
# Public: Get the rendered String content for this Section and all its child
|
90
91
|
# Blocks.
|
91
92
|
def render
|
92
|
-
|
93
|
+
Debug.debug { "Now rendering section for #{self}" }
|
94
|
+
@document.playback_attributes @attributes
|
93
95
|
renderer.render('section', self)
|
94
96
|
end
|
95
97
|
|
@@ -152,7 +154,7 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
|
|
152
154
|
# Returns the section number as a String
|
153
155
|
def sectnum(delimiter = '.', append = nil)
|
154
156
|
append ||= (append == false ? '' : delimiter)
|
155
|
-
if !@level.nil? && @level > 1 && @parent.is_a?(
|
157
|
+
if !@level.nil? && @level > 1 && @parent.is_a?(Section)
|
156
158
|
"#{@parent.sectnum(delimiter)}#{@index + 1}#{append}"
|
157
159
|
else
|
158
160
|
"#{@index + 1}#{append}"
|
@@ -171,3 +173,4 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
|
|
171
173
|
end
|
172
174
|
end
|
173
175
|
end
|
176
|
+
end
|
@@ -1,587 +1,612 @@
|
|
1
|
+
module Asciidoctor
|
1
2
|
# Public: Methods to perform substitutions on lines of AsciiDoc text. This module
|
2
3
|
# is intented to be mixed-in to Section and Block to provide operations for performing
|
3
4
|
# the necessary substitutions.
|
4
|
-
module
|
5
|
-
|
5
|
+
module Substituters
|
6
|
+
|
7
|
+
COMPOSITE_SUBS = {
|
8
|
+
:none => [],
|
9
|
+
:normal => [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements],
|
10
|
+
:verbatim => [:specialcharacters, :callouts]
|
11
|
+
}
|
12
|
+
|
13
|
+
SUB_OPTIONS = COMPOSITE_SUBS.keys + COMPOSITE_SUBS[:normal]
|
14
|
+
|
15
|
+
# Internal: A String Array of passthough (unprocessed) text captured from this block
|
16
|
+
attr_reader :passthroughs
|
17
|
+
|
18
|
+
# Public: Apply the specified substitutions to the lines of text
|
19
|
+
#
|
20
|
+
# lines - The lines of text to process. Can be a String or a String Array
|
21
|
+
# subs - The substitutions to perform. Can be a Symbol or a Symbol Array (default: COMPOSITE_SUBS[:normal])
|
22
|
+
#
|
23
|
+
# returns Either a String or String Array, whichever matches the type of the first argument
|
24
|
+
def apply_subs(lines, subs = COMPOSITE_SUBS[:normal])
|
25
|
+
if subs.nil?
|
26
|
+
subs = []
|
27
|
+
elsif subs.is_a? Symbol
|
28
|
+
subs = [subs]
|
29
|
+
end
|
6
30
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
31
|
+
if !subs.empty?
|
32
|
+
# QUESTION is this most efficient operation?
|
33
|
+
subs = subs.map {|key|
|
34
|
+
COMPOSITE_SUBS.has_key?(key) ? COMPOSITE_SUBS[key] : key
|
35
|
+
}.flatten
|
36
|
+
end
|
37
|
+
|
38
|
+
return lines if subs.empty?
|
39
|
+
|
40
|
+
multiline = lines.is_a?(Array)
|
41
|
+
text = multiline ? lines.join : lines
|
42
|
+
|
43
|
+
passthroughs = subs.include?(:macros)
|
44
|
+
text = extract_passthroughs(text) if passthroughs
|
45
|
+
|
46
|
+
subs.each {|type|
|
47
|
+
case type
|
48
|
+
when :specialcharacters
|
49
|
+
text = sub_specialcharacters(text)
|
50
|
+
when :quotes
|
51
|
+
text = sub_quotes(text)
|
52
|
+
when :attributes
|
53
|
+
text = sub_attributes(text.lines.entries).join
|
54
|
+
when :replacements
|
55
|
+
text = sub_replacements(text)
|
56
|
+
when :macros
|
57
|
+
text = sub_macros(text)
|
58
|
+
when :callouts
|
59
|
+
text = sub_callouts(text)
|
60
|
+
when :post_replacements
|
61
|
+
text = sub_post_replacements(text)
|
62
|
+
else
|
63
|
+
puts "asciidoctor: WARNING: unknown substitution type #{type}"
|
64
|
+
end
|
11
65
|
}
|
66
|
+
text = restore_passthroughs(text) if passthroughs
|
67
|
+
|
68
|
+
multiline ? text.lines.entries : text
|
69
|
+
end
|
12
70
|
|
13
|
-
|
71
|
+
# Public: Apply normal substitutions.
|
72
|
+
#
|
73
|
+
# lines - The lines of text to process. Can be a String or a String Array
|
74
|
+
#
|
75
|
+
# returns - A String with normal substitutions performed
|
76
|
+
def apply_normal_subs(lines)
|
77
|
+
apply_subs(lines.is_a?(Array) ? lines.join : lines)
|
78
|
+
end
|
14
79
|
|
15
|
-
|
16
|
-
|
80
|
+
# Public: Apply substitutions for titles.
|
81
|
+
#
|
82
|
+
# title - The String title to process
|
83
|
+
#
|
84
|
+
# returns - A String with title substitutions performed
|
85
|
+
def apply_title_subs(title)
|
86
|
+
apply_subs(title, [:specialcharacters, :quotes, :replacements, :macros, :attributes, :post_replacements])
|
87
|
+
end
|
17
88
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
89
|
+
# Public: Apply substitutions for titles
|
90
|
+
#
|
91
|
+
# lines - A String Array containing the lines of text process
|
92
|
+
#
|
93
|
+
# returns - A String with literal (verbatim) substitutions performed
|
94
|
+
def apply_literal_subs(lines)
|
95
|
+
if @document.attributes['basebackend'] == 'html' && attr('style') == 'source' &&
|
96
|
+
@document.attributes['source-highlighter'] == 'coderay' && attr?('language')
|
97
|
+
sub_callouts(highlight_source(lines.join))
|
98
|
+
else
|
99
|
+
apply_subs(lines.join, COMPOSITE_SUBS[:verbatim])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Apply substitutions for header metadata and attribute assignments
|
104
|
+
#
|
105
|
+
# text - String containing the text process
|
106
|
+
#
|
107
|
+
# returns - A String with header substitutions performed
|
108
|
+
def apply_header_subs(text)
|
109
|
+
apply_subs(text, [:specialcharacters, :attributes])
|
110
|
+
end
|
111
|
+
|
112
|
+
# Public: Apply substitutions for passthrough text
|
113
|
+
#
|
114
|
+
# lines - A String Array containing the lines of text process
|
115
|
+
#
|
116
|
+
# returns - A String Array with passthrough substitutions performed
|
117
|
+
def apply_passthrough_subs(lines)
|
118
|
+
if attr? 'subs'
|
119
|
+
subs = resolve_subs(attr('subs'))
|
120
|
+
else
|
121
|
+
subs = [:attributes, :macros]
|
122
|
+
end
|
123
|
+
apply_subs(lines.join, subs)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Internal: Extract the passthrough text from the document for reinsertion after processing.
|
127
|
+
#
|
128
|
+
# text - The String from which to extract passthrough fragements
|
129
|
+
#
|
130
|
+
# returns - The text with the passthrough region substituted with placeholders
|
131
|
+
def extract_passthroughs(text)
|
132
|
+
result = text.dup
|
133
|
+
|
134
|
+
result.gsub!(REGEXP[:pass_macro]) {
|
135
|
+
# alias match for Ruby 1.8.7 compat
|
136
|
+
m = $~
|
137
|
+
# honor the escape
|
138
|
+
if m[0].start_with? '\\'
|
139
|
+
next m[0][1..-1]
|
29
140
|
end
|
30
141
|
|
31
|
-
if
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
142
|
+
if m[1] == '$$'
|
143
|
+
subs = [:specialcharacters]
|
144
|
+
elsif !m[3].nil? && !m[3].empty?
|
145
|
+
subs = resolve_subs(m[3])
|
146
|
+
else
|
147
|
+
subs = []
|
36
148
|
end
|
37
149
|
|
38
|
-
|
150
|
+
# TODO move unescaping closing square bracket to an operation
|
151
|
+
@passthroughs << {:text => m[2] || m[4].gsub('\]', ']'), :subs => subs}
|
152
|
+
index = @passthroughs.size - 1
|
153
|
+
"\x0#{index}\x0"
|
154
|
+
} unless !(result.include?('+++') || result.include?('$$') || result.include?('pass:'))
|
39
155
|
|
40
|
-
|
41
|
-
|
156
|
+
result.gsub!(REGEXP[:pass_lit]) {
|
157
|
+
# alias match for Ruby 1.8.7 compat
|
158
|
+
m = $~
|
42
159
|
|
43
|
-
|
44
|
-
|
160
|
+
# honor the escape
|
161
|
+
if m[2].start_with? '\\'
|
162
|
+
next "#{m[1]}#{m[2][1..-1]}"
|
163
|
+
end
|
45
164
|
|
46
|
-
subs
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
when :quotes
|
51
|
-
text = sub_quotes(text)
|
52
|
-
when :attributes
|
53
|
-
text = sub_attributes(text.lines.entries).join
|
54
|
-
when :replacements
|
55
|
-
text = sub_replacements(text)
|
56
|
-
when :macros
|
57
|
-
text = sub_macros(text)
|
58
|
-
when :callouts
|
59
|
-
text = sub_callouts(text)
|
60
|
-
when :post_replacements
|
61
|
-
text = sub_post_replacements(text)
|
62
|
-
else
|
63
|
-
puts "asciidoctor: WARNING: unknown substitution type #{type}"
|
64
|
-
end
|
65
|
-
}
|
66
|
-
text = restore_passthroughs(text) if passthroughs
|
165
|
+
@passthroughs << {:text => m[3], :subs => [:specialcharacters], :literal => true}
|
166
|
+
index = @passthroughs.size - 1
|
167
|
+
"#{m[1]}\x0#{index}\x0"
|
168
|
+
} unless !result.include?('`')
|
67
169
|
|
68
|
-
|
69
|
-
|
170
|
+
result
|
171
|
+
end
|
70
172
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
173
|
+
# Internal: Restore the passthrough text by reinserting into the placeholder positions
|
174
|
+
#
|
175
|
+
# text - The String text into which to restore the passthrough text
|
176
|
+
#
|
177
|
+
# returns The String text with the passthrough text restored
|
178
|
+
def restore_passthroughs(text)
|
179
|
+
return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\x0")
|
180
|
+
|
181
|
+
text.gsub(REGEXP[:pass_placeholder]) {
|
182
|
+
pass = @passthroughs[$1.to_i];
|
183
|
+
text = apply_subs(pass[:text], pass.fetch(:subs, []))
|
184
|
+
pass[:literal] ? Inline.new(self, :quoted, text, :type => :monospaced).render : text
|
185
|
+
}
|
186
|
+
end
|
79
187
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
188
|
+
# Public: Substitute special characters (i.e., encode XML)
|
189
|
+
#
|
190
|
+
# Special characters are defined in the Asciidoctor::SPECIAL_CHARS Array constant
|
191
|
+
#
|
192
|
+
# text - The String text to process
|
193
|
+
#
|
194
|
+
# returns The String text with special characters replaced
|
195
|
+
def sub_specialcharacters(text)
|
196
|
+
# this syntax only available in Ruby 1.9
|
197
|
+
#text.gsub(SPECIAL_CHARS_PATTERN, SPECIAL_CHARS)
|
198
|
+
|
199
|
+
text.gsub(SPECIAL_CHARS_PATTERN) { SPECIAL_CHARS[$&] }
|
200
|
+
end
|
88
201
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
@document.attr('source-highlighter') == 'coderay' && attr?('language')
|
97
|
-
sub_callouts(highlight_source(lines.join))
|
98
|
-
else
|
99
|
-
apply_subs(lines.join, COMPOSITE_SUBS[:verbatim])
|
100
|
-
end
|
101
|
-
end
|
202
|
+
# Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
|
203
|
+
#
|
204
|
+
# text - The String text to process
|
205
|
+
#
|
206
|
+
# returns The String text with quoted text rendered using the backend templates
|
207
|
+
def sub_quotes(text)
|
208
|
+
result = text.dup
|
102
209
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
apply_subs(text, [:specialcharacters, :attributes])
|
110
|
-
end
|
210
|
+
QUOTE_SUBS.each {|type, scope, pattern|
|
211
|
+
result.gsub!(pattern) { transform_quoted_text($~, type, scope) }
|
212
|
+
}
|
213
|
+
|
214
|
+
result
|
215
|
+
end
|
111
216
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
subs = resolve_subs(attr('subs'))
|
120
|
-
else
|
121
|
-
subs = [:attributes, :macros]
|
122
|
-
end
|
123
|
-
apply_subs(lines.join, subs)
|
124
|
-
end
|
217
|
+
# Public: Substitute replacement characters (e.g., copyright, trademark, etc)
|
218
|
+
#
|
219
|
+
# text - The String text to process
|
220
|
+
#
|
221
|
+
# returns The String text with the replacement characters substituted
|
222
|
+
def sub_replacements(text)
|
223
|
+
result = text.dup
|
125
224
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
result = text.dup
|
225
|
+
REPLACEMENTS.each {|pattern, replacement|
|
226
|
+
result.gsub!(pattern, replacement)
|
227
|
+
}
|
228
|
+
|
229
|
+
result
|
230
|
+
end
|
133
231
|
|
134
|
-
|
232
|
+
# Public: Substitute attribute references
|
233
|
+
#
|
234
|
+
# Attribute references are in the format {name}.
|
235
|
+
#
|
236
|
+
# If an attribute referenced in the line is missing, the line is dropped.
|
237
|
+
#
|
238
|
+
# text - The String text to process
|
239
|
+
#
|
240
|
+
# returns The String text with the attribute references replaced with attribute values
|
241
|
+
#--
|
242
|
+
# NOTE it's necessary to perform this substitution line-by-line
|
243
|
+
# so that a missing key doesn't wipe out the whole block of data
|
244
|
+
def sub_attributes(data)
|
245
|
+
return data if data.nil? || data.empty?
|
246
|
+
|
247
|
+
# normalizes data type to an array (string becomes single-element array)
|
248
|
+
lines = Array(data)
|
249
|
+
|
250
|
+
result = lines.map {|line|
|
251
|
+
reject = false
|
252
|
+
subject = line.dup
|
253
|
+
subject.gsub!(REGEXP[:attr_ref]) {
|
135
254
|
# alias match for Ruby 1.8.7 compat
|
136
255
|
m = $~
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
elsif
|
145
|
-
|
256
|
+
key = m[2].downcase
|
257
|
+
# escaped attribute
|
258
|
+
if !$1.empty? || !$3.empty?
|
259
|
+
"{#$2}"
|
260
|
+
elsif m[2].start_with?('counter:')
|
261
|
+
args = m[2].split(':')
|
262
|
+
@document.counter(args[1], args[2])
|
263
|
+
elsif m[2].start_with?('counter2:')
|
264
|
+
args = m[2].split(':')
|
265
|
+
@document.counter(args[1], args[2])
|
266
|
+
''
|
267
|
+
elsif document.attributes.has_key? key
|
268
|
+
@document.attributes[key]
|
269
|
+
elsif INTRINSICS.has_key? key
|
270
|
+
INTRINSICS[key]
|
146
271
|
else
|
147
|
-
|
272
|
+
Debug.debug { "Missing attribute: #{m[2]}, line marked for removal" }
|
273
|
+
reject = true
|
274
|
+
break '{undefined}'
|
148
275
|
end
|
276
|
+
} if subject.include?('{')
|
149
277
|
|
150
|
-
|
151
|
-
|
152
|
-
"\x0#{index}\x0"
|
153
|
-
} unless !(result.include?('+++') || result.include?('$$') || result.include?('pass:'))
|
278
|
+
!reject ? subject : nil
|
279
|
+
}.compact
|
154
280
|
|
155
|
-
|
281
|
+
data.is_a?(String) ? result.join : result
|
282
|
+
end
|
283
|
+
|
284
|
+
# Public: Substitute inline macros (e.g., links, images, etc)
|
285
|
+
#
|
286
|
+
# Replace inline macros, which may span multiple lines, in the provided text
|
287
|
+
#
|
288
|
+
# text - The String text to process
|
289
|
+
#
|
290
|
+
# returns The String with the inline macros rendered using the backend templates
|
291
|
+
def sub_macros(text)
|
292
|
+
return text if text.nil? || text.empty?
|
293
|
+
|
294
|
+
result = text.dup
|
295
|
+
|
296
|
+
# some look ahead assertions to cut unnecessary regex calls
|
297
|
+
found = {}
|
298
|
+
found[:square_bracket] = result.include?('[')
|
299
|
+
found[:round_bracket] = result.include?('(')
|
300
|
+
found[:colon] = result.include?(':')
|
301
|
+
found[:macroish] = (found[:square_bracket] && found[:colon])
|
302
|
+
found[:macroish_short_form] = (found[:square_bracket] && found[:colon] && result.include?(':['))
|
303
|
+
found[:uri] = (found[:colon] && result.include?('://'))
|
304
|
+
link_attrs = @document.attributes.has_key?('linkattrs')
|
305
|
+
|
306
|
+
if found[:macroish] && result.include?('image:')
|
307
|
+
# image:filename.png[Alt Text]
|
308
|
+
result.gsub!(REGEXP[:image_macro]) {
|
156
309
|
# alias match for Ruby 1.8.7 compat
|
157
310
|
m = $~
|
158
|
-
|
159
311
|
# honor the escape
|
160
|
-
if m[
|
161
|
-
next
|
312
|
+
if m[0].start_with? '\\'
|
313
|
+
next m[0][1..-1]
|
162
314
|
end
|
163
|
-
|
164
|
-
@
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
171
|
-
|
172
|
-
# Internal: Restore the passthrough text by reinserting into the placeholder positions
|
173
|
-
#
|
174
|
-
# text - The String text into which to restore the passthrough text
|
175
|
-
#
|
176
|
-
# returns The String text with the passthrough text restored
|
177
|
-
def restore_passthroughs(text)
|
178
|
-
return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\x0")
|
179
|
-
|
180
|
-
text.gsub(REGEXP[:pass_placeholder]) {
|
181
|
-
pass = @passthroughs[$1.to_i];
|
182
|
-
text = apply_subs(pass[:text], pass.fetch(:subs, []))
|
183
|
-
pass[:literal] ? Inline.new(self, :quoted, text, :type => :monospaced).render : text
|
315
|
+
target = sub_attributes(m[1])
|
316
|
+
@document.register(:images, target)
|
317
|
+
attrs = parse_attributes(m[2], ['alt', 'width', 'height'])
|
318
|
+
if !attrs.has_key?('alt') || attrs['alt'].empty?
|
319
|
+
attrs['alt'] = File.basename(target, File.extname(target))
|
320
|
+
end
|
321
|
+
Inline.new(self, :image, nil, :target => target, :attributes => attrs).render
|
184
322
|
}
|
185
323
|
end
|
186
324
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
text.gsub(SPECIAL_CHARS_PATTERN) { SPECIAL_CHARS[$&] }
|
199
|
-
end
|
200
|
-
|
201
|
-
# Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
|
202
|
-
#
|
203
|
-
# text - The String text to process
|
204
|
-
#
|
205
|
-
# returns The String text with quoted text rendered using the backend templates
|
206
|
-
def sub_quotes(text)
|
207
|
-
result = text.dup
|
325
|
+
if found[:macroish_short_form] || found[:round_bracket]
|
326
|
+
# indexterm:[Tigers,Big cats]
|
327
|
+
# (((Tigers,Big cats)))
|
328
|
+
result.gsub!(REGEXP[:indexterm_macro]) {
|
329
|
+
# alias match for Ruby 1.8.7 compat
|
330
|
+
m = $~
|
331
|
+
# honor the escape
|
332
|
+
if m[0].start_with? '\\'
|
333
|
+
next m[0][1..-1]
|
334
|
+
end
|
208
335
|
|
209
|
-
|
210
|
-
|
336
|
+
terms = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']').split(REGEXP[:csv_delimiter])
|
337
|
+
document.register(:indexterms, [*terms])
|
338
|
+
Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
|
211
339
|
}
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
result = text.dup
|
340
|
+
|
341
|
+
# indexterm2:[Tigers]
|
342
|
+
# ((Tigers))
|
343
|
+
result.gsub!(REGEXP[:indexterm2_macro]) {
|
344
|
+
# alias match for Ruby 1.8.7 compat
|
345
|
+
m = $~
|
346
|
+
# honor the escape
|
347
|
+
if m[0].start_with? '\\'
|
348
|
+
next m[0][1..-1]
|
349
|
+
end
|
223
350
|
|
224
|
-
|
225
|
-
|
351
|
+
text = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']')
|
352
|
+
document.register(:indexterms, [text])
|
353
|
+
Inline.new(self, :indexterm, text, :type => :visible).render
|
226
354
|
}
|
227
|
-
|
228
|
-
result
|
229
355
|
end
|
230
356
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
@document.counter(args[1], args[2])
|
262
|
-
elsif m[2].start_with?('counter2:')
|
263
|
-
args = m[2].split(':')
|
264
|
-
@document.counter(args[1], args[2])
|
265
|
-
''
|
266
|
-
elsif document.attributes.has_key? key
|
267
|
-
@document.attributes[key]
|
268
|
-
elsif INTRINSICS.has_key? key
|
269
|
-
INTRINSICS[key]
|
357
|
+
if found[:uri]
|
358
|
+
# inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
|
359
|
+
result.gsub!(REGEXP[:link_inline]) {
|
360
|
+
# alias match for Ruby 1.8.7 compat
|
361
|
+
m = $~
|
362
|
+
# honor the escape
|
363
|
+
if m[2].start_with? '\\'
|
364
|
+
next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
|
365
|
+
# not a valid macro syntax w/o trailing square brackets
|
366
|
+
# we probably shouldn't even get here...our regex is doing too much
|
367
|
+
elsif m[1] == 'link:' && m[3].nil?
|
368
|
+
next m[0]
|
369
|
+
end
|
370
|
+
prefix = (m[1] != 'link:' ? m[1] : '')
|
371
|
+
target = m[2]
|
372
|
+
# strip the <> around the link
|
373
|
+
if prefix.end_with? '<'
|
374
|
+
prefix = prefix[0..-5]
|
375
|
+
end
|
376
|
+
if target.end_with? '>'
|
377
|
+
target = target[0..-5]
|
378
|
+
end
|
379
|
+
@document.register(:links, target)
|
380
|
+
|
381
|
+
attrs = nil
|
382
|
+
#text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
|
383
|
+
if !m[3].to_s.empty?
|
384
|
+
if link_attrs && (m[3].start_with?('"') || m[3].include?(','))
|
385
|
+
attrs = parse_attributes(sub_attributes(m[3].gsub('\]', ']')))
|
386
|
+
text = attrs[1]
|
270
387
|
else
|
271
|
-
|
272
|
-
reject = true
|
273
|
-
break '{undefined}'
|
388
|
+
text = sub_attributes(m[3].gsub('\]', ']'))
|
274
389
|
end
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
}.compact
|
390
|
+
else
|
391
|
+
text = ''
|
392
|
+
end
|
279
393
|
|
280
|
-
|
394
|
+
"#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render}"
|
395
|
+
}
|
281
396
|
end
|
282
397
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
found[:uri] = result.include?('://')
|
302
|
-
|
303
|
-
if found[:macroish] && result.include?('image:')
|
304
|
-
# image:filename.png[Alt Text]
|
305
|
-
result.gsub!(REGEXP[:image_macro]) {
|
306
|
-
# alias match for Ruby 1.8.7 compat
|
307
|
-
m = $~
|
308
|
-
# honor the escape
|
309
|
-
if m[0].start_with? '\\'
|
310
|
-
next m[0][1..-1]
|
311
|
-
end
|
312
|
-
target = sub_attributes(m[1])
|
313
|
-
@document.register(:images, target)
|
314
|
-
attrs = parse_attributes(m[2], ['alt', 'width', 'height'])
|
315
|
-
if !attrs.has_key?('alt') || attrs['alt'].empty?
|
316
|
-
attrs['alt'] = File.basename(target, File.extname(target))
|
317
|
-
end
|
318
|
-
Inline.new(self, :image, nil, :target => target, :attributes => attrs).render
|
319
|
-
}
|
320
|
-
end
|
321
|
-
|
322
|
-
if found[:macroish_short_form] || found[:round_bracket]
|
323
|
-
# indexterm:[Tigers,Big cats]
|
324
|
-
# (((Tigers,Big cats)))
|
325
|
-
result.gsub!(REGEXP[:indexterm_macro]) {
|
326
|
-
# alias match for Ruby 1.8.7 compat
|
327
|
-
m = $~
|
328
|
-
# honor the escape
|
329
|
-
if m[0].start_with? '\\'
|
330
|
-
next m[0][1..-1]
|
331
|
-
end
|
332
|
-
|
333
|
-
terms = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']').split(REGEXP[:csv_delimiter])
|
334
|
-
document.register(:indexterms, [*terms])
|
335
|
-
Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
|
336
|
-
}
|
337
|
-
|
338
|
-
# indexterm2:[Tigers]
|
339
|
-
# ((Tigers))
|
340
|
-
result.gsub!(REGEXP[:indexterm2_macro]) {
|
341
|
-
# alias match for Ruby 1.8.7 compat
|
342
|
-
m = $~
|
343
|
-
# honor the escape
|
344
|
-
if m[0].start_with? '\\'
|
345
|
-
next m[0][1..-1]
|
346
|
-
end
|
347
|
-
|
348
|
-
text = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']')
|
349
|
-
document.register(:indexterms, [text])
|
350
|
-
Inline.new(self, :indexterm, text, :type => :visible).render
|
351
|
-
}
|
352
|
-
end
|
353
|
-
|
354
|
-
if found[:uri]
|
355
|
-
# inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
|
356
|
-
result.gsub!(REGEXP[:link_inline]) {
|
357
|
-
# alias match for Ruby 1.8.7 compat
|
358
|
-
m = $~
|
359
|
-
# honor the escape
|
360
|
-
if m[2].start_with? '\\'
|
361
|
-
next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
|
362
|
-
# not a valid macro syntax w/o trailing square brackets
|
363
|
-
# we probably shouldn't even get here...our regex is doing too much
|
364
|
-
elsif m[1] == 'link:' && m[3].nil?
|
365
|
-
next m[0]
|
366
|
-
end
|
367
|
-
prefix = (m[1] != 'link:' ? m[1] : '')
|
368
|
-
target = m[2]
|
369
|
-
# strip the <> around the link
|
370
|
-
if prefix.end_with? '<'
|
371
|
-
prefix = prefix[0..-5]
|
372
|
-
end
|
373
|
-
if target.end_with? '>'
|
374
|
-
target = target[0..-5]
|
375
|
-
end
|
376
|
-
@document.register(:links, target)
|
377
|
-
text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
|
378
|
-
"#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target).render}"
|
379
|
-
}
|
380
|
-
end
|
381
|
-
|
382
|
-
if found[:macroish] && result.include?('link:')
|
383
|
-
# inline link macros, link:target[text]
|
384
|
-
result.gsub!(REGEXP[:link_macro]) {
|
385
|
-
# alias match for Ruby 1.8.7 compat
|
386
|
-
m = $~
|
387
|
-
# honor the escape
|
388
|
-
if m[0].start_with? '\\'
|
389
|
-
next m[0][1..-1]
|
390
|
-
end
|
391
|
-
target = m[1]
|
392
|
-
@document.register(:links, target)
|
398
|
+
if found[:macroish] && result.include?('link:')
|
399
|
+
# inline link macros, link:target[text]
|
400
|
+
result.gsub!(REGEXP[:link_macro]) {
|
401
|
+
# alias match for Ruby 1.8.7 compat
|
402
|
+
m = $~
|
403
|
+
# honor the escape
|
404
|
+
if m[0].start_with? '\\'
|
405
|
+
next m[0][1..-1]
|
406
|
+
end
|
407
|
+
target = m[1]
|
408
|
+
@document.register(:links, target)
|
409
|
+
|
410
|
+
attrs = nil
|
411
|
+
#text = sub_attributes(m[2].gsub('\]', ']'))
|
412
|
+
if link_attrs && (m[2].start_with?('"') || m[2].include?(','))
|
413
|
+
attrs = parse_attributes(sub_attributes(m[2].gsub('\]', ']')))
|
414
|
+
text = attrs[1]
|
415
|
+
else
|
393
416
|
text = sub_attributes(m[2].gsub('\]', ']'))
|
394
|
-
|
395
|
-
}
|
396
|
-
end
|
417
|
+
end
|
397
418
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
419
|
+
Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render
|
420
|
+
}
|
421
|
+
end
|
422
|
+
|
423
|
+
if found[:macroish_short_form] && result.include?('footnote')
|
424
|
+
result.gsub!(REGEXP[:footnote_macro]) {
|
425
|
+
# alias match for Ruby 1.8.7 compat
|
426
|
+
m = $~
|
427
|
+
# honor the escape
|
428
|
+
if m[0].start_with? '\\'
|
429
|
+
next m[0][1..-1]
|
430
|
+
end
|
431
|
+
if m[1] == 'footnote'
|
432
|
+
# hmmmm
|
433
|
+
text = restore_passthroughs(m[2])
|
434
|
+
id = nil
|
435
|
+
index = @document.counter('footnote-number')
|
436
|
+
@document.register(:footnotes, Document::Footnote.new(index, id, text))
|
437
|
+
type = nil
|
438
|
+
target = nil
|
439
|
+
else
|
440
|
+
id, text = m[2].split(REGEXP[:csv_delimiter], 2)
|
441
|
+
if !text.nil?
|
407
442
|
# hmmmm
|
408
|
-
text = restore_passthroughs(
|
409
|
-
id = nil
|
443
|
+
text = restore_passthroughs(text)
|
410
444
|
index = @document.counter('footnote-number')
|
411
445
|
@document.register(:footnotes, Document::Footnote.new(index, id, text))
|
412
|
-
type =
|
446
|
+
type = :ref
|
413
447
|
target = nil
|
414
448
|
else
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
type = :ref
|
422
|
-
target = nil
|
423
|
-
else
|
424
|
-
fn = @document.references[:footnotes].find {|fn| fn.id == id }
|
425
|
-
target = id
|
426
|
-
id = nil
|
427
|
-
index = fn.index
|
428
|
-
text = fn.text
|
429
|
-
type = :xref
|
430
|
-
end
|
431
|
-
end
|
432
|
-
Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
|
433
|
-
}
|
434
|
-
end
|
435
|
-
|
436
|
-
if found[:macroish] || result.include?('<<')
|
437
|
-
result.gsub!(REGEXP[:xref_macro]) {
|
438
|
-
# alias match for Ruby 1.8.7 compat
|
439
|
-
m = $~
|
440
|
-
# honor the escape
|
441
|
-
if m[0].start_with? '\\'
|
442
|
-
next m[0][1..-1]
|
443
|
-
end
|
444
|
-
if !m[1].nil?
|
445
|
-
id, reftext = m[1].split(REGEXP[:csv_delimiter], 2)
|
446
|
-
id.sub!(/^("|)(.*)\1$/, '\2')
|
447
|
-
reftext.sub!(/^("|)(.*)\1$/m, '\2') unless reftext.nil?
|
448
|
-
else
|
449
|
-
id = m[2]
|
450
|
-
reftext = !m[3].empty? ? m[3] : nil
|
451
|
-
end
|
452
|
-
Inline.new(self, :anchor, reftext, :type => :xref, :target => id).render
|
453
|
-
}
|
454
|
-
end
|
455
|
-
|
456
|
-
if found[:square_bracket] && result.include?('[[[')
|
457
|
-
result.gsub!(REGEXP[:biblio_macro]) {
|
458
|
-
# alias match for Ruby 1.8.7 compat
|
459
|
-
m = $~
|
460
|
-
# honor the escape
|
461
|
-
if m[0].start_with? '\\'
|
462
|
-
next m[0][1..-1]
|
463
|
-
end
|
464
|
-
id = reftext = m[1]
|
465
|
-
Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
|
466
|
-
}
|
467
|
-
end
|
468
|
-
|
469
|
-
if found[:square_bracket] && result.include?('[[')
|
470
|
-
result.gsub!(REGEXP[:anchor_macro]) {
|
471
|
-
# alias match for Ruby 1.8.7 compat
|
472
|
-
m = $~
|
473
|
-
# honor the escape
|
474
|
-
if m[0].start_with? '\\'
|
475
|
-
next m[0][1..-1]
|
476
|
-
end
|
477
|
-
id, reftext = m[1].split(REGEXP[:csv_delimiter])
|
478
|
-
id.sub!(/^("|)(.*)\1$/, '\2')
|
479
|
-
if reftext.nil?
|
480
|
-
reftext = "[#{id}]"
|
481
|
-
else
|
482
|
-
reftext.sub!(/^("|)(.*)\1$/m, '\2')
|
483
|
-
end
|
484
|
-
# NOTE the reftext should also match what's in our references dic
|
485
|
-
if !@document.references[:ids].has_key? id
|
486
|
-
Asciidoctor.debug { "Missing reference for anchor #{id}" }
|
449
|
+
footnote = @document.references[:footnotes].find {|fn| fn.id == id }
|
450
|
+
target = id
|
451
|
+
id = nil
|
452
|
+
index = footnote.index
|
453
|
+
text = footnote.text
|
454
|
+
type = :xref
|
487
455
|
end
|
488
|
-
|
489
|
-
}
|
490
|
-
|
456
|
+
end
|
457
|
+
Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
|
458
|
+
}
|
459
|
+
end
|
491
460
|
|
492
|
-
|
461
|
+
if found[:macroish] || result.include?('<<')
|
462
|
+
result.gsub!(REGEXP[:xref_macro]) {
|
463
|
+
# alias match for Ruby 1.8.7 compat
|
464
|
+
m = $~
|
465
|
+
# honor the escape
|
466
|
+
if m[0].start_with? '\\'
|
467
|
+
next m[0][1..-1]
|
468
|
+
end
|
469
|
+
if !m[1].nil?
|
470
|
+
id, reftext = m[1].split(REGEXP[:csv_delimiter], 2)
|
471
|
+
id.sub!(REGEXP[:dbl_quoted], '\2')
|
472
|
+
reftext.sub!(REGEXP[:m_dbl_quoted], '\2') unless reftext.nil?
|
473
|
+
else
|
474
|
+
id = m[2]
|
475
|
+
reftext = !m[3].empty? ? m[3] : nil
|
476
|
+
end
|
477
|
+
Inline.new(self, :anchor, reftext, :type => :xref, :target => id).render
|
478
|
+
}
|
493
479
|
end
|
494
480
|
|
495
|
-
|
496
|
-
|
497
|
-
# text - The String text to process
|
498
|
-
#
|
499
|
-
# returns The String with the callout references rendered using the backend templates
|
500
|
-
def sub_callouts(text)
|
501
|
-
text.gsub(REGEXP[:callout_render]) {
|
481
|
+
if found[:square_bracket] && result.include?('[[[')
|
482
|
+
result.gsub!(REGEXP[:biblio_macro]) {
|
502
483
|
# alias match for Ruby 1.8.7 compat
|
503
484
|
m = $~
|
504
485
|
# honor the escape
|
505
486
|
if m[0].start_with? '\\'
|
506
|
-
next
|
487
|
+
next m[0][1..-1]
|
507
488
|
end
|
508
|
-
|
489
|
+
id = reftext = m[1]
|
490
|
+
Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
|
509
491
|
}
|
510
492
|
end
|
511
493
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
494
|
+
if found[:square_bracket] && result.include?('[[')
|
495
|
+
result.gsub!(REGEXP[:anchor_macro]) {
|
496
|
+
# alias match for Ruby 1.8.7 compat
|
497
|
+
m = $~
|
498
|
+
# honor the escape
|
499
|
+
if m[0].start_with? '\\'
|
500
|
+
next m[0][1..-1]
|
501
|
+
end
|
502
|
+
id, reftext = m[1].split(REGEXP[:csv_delimiter])
|
503
|
+
id.sub!(REGEXP[:dbl_quoted], '\2')
|
504
|
+
if reftext.nil?
|
505
|
+
reftext = "[#{id}]"
|
506
|
+
else
|
507
|
+
reftext.sub!(REGEXP[:m_dbl_quoted], '\2')
|
508
|
+
end
|
509
|
+
# NOTE the reftext should also match what's in our references dic
|
510
|
+
if !@document.references[:ids].has_key? id
|
511
|
+
Debug.debug { "Missing reference for anchor #{id}" }
|
512
|
+
end
|
513
|
+
Inline.new(self, :anchor, reftext, :type => :ref, :target => id).render
|
514
|
+
}
|
526
515
|
end
|
527
516
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
517
|
+
result
|
518
|
+
end
|
519
|
+
|
520
|
+
# Public: Substitute callout references
|
521
|
+
#
|
522
|
+
# text - The String text to process
|
523
|
+
#
|
524
|
+
# returns The String with the callout references rendered using the backend templates
|
525
|
+
def sub_callouts(text)
|
526
|
+
text.gsub(REGEXP[:callout_render]) {
|
527
|
+
# alias match for Ruby 1.8.7 compat
|
528
|
+
m = $~
|
529
|
+
# honor the escape
|
530
|
+
if m[0].start_with? '\\'
|
531
|
+
next "<#{m[1]}>"
|
542
532
|
end
|
543
|
-
|
533
|
+
Inline.new(self, :callout, m[1], :id => document.callouts.read_next_id).render
|
534
|
+
}
|
535
|
+
end
|
544
536
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
return
|
554
|
-
|
555
|
-
|
537
|
+
# Public: Substitute post replacements
|
538
|
+
#
|
539
|
+
# text - The String text to process
|
540
|
+
#
|
541
|
+
# returns The String with the post replacements rendered using the backend templates
|
542
|
+
def sub_post_replacements(text)
|
543
|
+
if @document.attr? 'hardbreaks'
|
544
|
+
lines = text.lines.entries
|
545
|
+
return text if lines.size == 1
|
546
|
+
last = lines.pop
|
547
|
+
"#{lines.map {|line| Inline.new(self, :break, line.rstrip.chomp(LINE_BREAK), :type => :line).render } * "\n"}\n#{last}"
|
548
|
+
else
|
549
|
+
text.gsub(REGEXP[:line_break]) { Inline.new(self, :break, $1, :type => :line).render }
|
556
550
|
end
|
551
|
+
end
|
557
552
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
553
|
+
# Internal: Transform (render) a quoted text region
|
554
|
+
#
|
555
|
+
# match - The MatchData for the quoted text region
|
556
|
+
# type - The quoting type (single, double, strong, emphasis, monospaced, etc)
|
557
|
+
# scope - The scope of the quoting (constrained or unconstrained)
|
558
|
+
#
|
559
|
+
# returns The rendered text for the quoted text region
|
560
|
+
def transform_quoted_text(match, type, scope)
|
561
|
+
if match[0].start_with? '\\'
|
562
|
+
match[0][1..-1]
|
563
|
+
elsif scope == :constrained
|
564
|
+
"#{match[1]}#{Inline.new(self, :quoted, match[3], :type => type, :attributes => parse_attributes(match[2])).render}"
|
565
|
+
else
|
566
|
+
Inline.new(self, :quoted, match[2], :type => type, :attributes => parse_attributes(match[1])).render
|
570
567
|
end
|
568
|
+
end
|
571
569
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
:line_numbers => (attr?('linenums') ? @document.attr('coderay-linenums-mode', 'table').to_sym : nil),
|
584
|
-
:line_number_anchors => false}].highlight(source).chomp
|
585
|
-
end
|
570
|
+
# Internal: Parse the attributes in the attribute line
|
571
|
+
#
|
572
|
+
# attrline - A String of unprocessed attributes (key/value pairs)
|
573
|
+
# posattrs - The keys for positional attributes
|
574
|
+
#
|
575
|
+
# returns nil if attrline is nil, an empty Hash if attrline is empty, otherwise a Hash of parsed attributes
|
576
|
+
def parse_attributes(attrline, posattrs = ['role'])
|
577
|
+
return nil if attrline.nil?
|
578
|
+
return {} if attrline.empty?
|
579
|
+
|
580
|
+
AttributeList.new(attrline, self).parse(posattrs)
|
586
581
|
end
|
582
|
+
|
583
|
+
# Internal: Resolve the list of comma-delimited subs against the possible options.
|
584
|
+
#
|
585
|
+
# subs - A comma-delimited String of substitution aliases
|
586
|
+
#
|
587
|
+
# returns An Array of Symbols representing the substitution operation
|
588
|
+
def resolve_subs(subs)
|
589
|
+
candidates = subs.split(',').map {|sub| sub.strip.to_sym}
|
590
|
+
resolved = candidates & SUB_OPTIONS
|
591
|
+
if (invalid = candidates - resolved).size > 0
|
592
|
+
puts "asciidoctor: WARNING: invalid passthrough macro substitution operation#{invalid.size > 1 ? 's' : ''}: #{invalid * ', '}"
|
593
|
+
end
|
594
|
+
resolved
|
595
|
+
end
|
596
|
+
|
597
|
+
# Public: Highlight the source code if a source highlighter is defined
|
598
|
+
# on the document, otherwise return the text unprocessed
|
599
|
+
#
|
600
|
+
# source - the source code String to highlight
|
601
|
+
#
|
602
|
+
# returns the highlighted source code, if a source highlighter is defined
|
603
|
+
# on the document, otherwise the unprocessed text
|
604
|
+
def highlight_source(source)
|
605
|
+
Helpers.require_library 'coderay'
|
606
|
+
::CodeRay::Duo[attr('language', 'text').to_sym, :html, {
|
607
|
+
:css => @document.attributes.fetch('coderay-css', 'class').to_sym,
|
608
|
+
:line_numbers => (attr?('linenums') ? @document.attributes.fetch('coderay-linenums-mode', 'table').to_sym : nil),
|
609
|
+
:line_number_anchors => false}].highlight(source).chomp
|
610
|
+
end
|
611
|
+
end
|
587
612
|
end
|