asciidoctor-rhrev 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +25 -0
- data/README.adoc +136 -0
- data/USAGE.adoc +653 -0
- data/lib/asciidoctor/rhrev/catalog.rb +92 -0
- data/lib/asciidoctor/rhrev/converter.rb +437 -0
- data/lib/asciidoctor/rhrev/exporter.rb +242 -0
- data/lib/asciidoctor/rhrev/helpers.rb +117 -0
- data/lib/asciidoctor/rhrev/processors.rb +48 -0
- data/lib/asciidoctor/rhrev/renderer.rb +697 -0
- data/lib/asciidoctor/rhrev/rhrev_html.rb +334 -0
- data/lib/asciidoctor/rhrev/rhrev_pdf.rb +7 -0
- data/lib/asciidoctor/rhrev.rb +34 -0
- data/lib/asciidoctor-rhrev.rb +3 -0
- metadata +69 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Asciidoctor
|
|
4
|
+
module PDF
|
|
5
|
+
module Rhrev
|
|
6
|
+
module Renderer
|
|
7
|
+
include Helpers
|
|
8
|
+
|
|
9
|
+
def allocate_revision_history_extent doc
|
|
10
|
+
# Use accessor method, not instance variable
|
|
11
|
+
return unless revision_history
|
|
12
|
+
|
|
13
|
+
start_page_number = page_number
|
|
14
|
+
|
|
15
|
+
extent = dry_run onto: self do
|
|
16
|
+
ink_revision_history_content doc
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
extent.each_page { |first_page| start_new_page unless first_page }
|
|
20
|
+
move_cursor_to extent.to.cursor
|
|
21
|
+
|
|
22
|
+
end_page_number = page_number
|
|
23
|
+
@rhrev_deferred_pages = (extent.from.page..end_page_number)
|
|
24
|
+
|
|
25
|
+
unless doc.attr? 'rhrev-suppress-new-page-after'
|
|
26
|
+
start_new_page
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
extent
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ink_revision_history doc, extent
|
|
33
|
+
return if scratch?
|
|
34
|
+
|
|
35
|
+
go_to_page extent.from.page
|
|
36
|
+
move_cursor_to extent.from.cursor
|
|
37
|
+
|
|
38
|
+
ink_revision_history_content doc
|
|
39
|
+
|
|
40
|
+
end_page = page_number
|
|
41
|
+
start_page = extent.from.page
|
|
42
|
+
|
|
43
|
+
has_foreground = @theme.page_foreground_image || doc.attr('page-foreground-image')
|
|
44
|
+
|
|
45
|
+
if has_foreground
|
|
46
|
+
fg_image = resolve_background_image doc, @theme, 'page-foreground-image'
|
|
47
|
+
if fg_image && fg_image[0]
|
|
48
|
+
(start_page..end_page).each do |pg|
|
|
49
|
+
go_to_page pg
|
|
50
|
+
next if page.imported_page?
|
|
51
|
+
stamp_name = %(foreground-image-#{page.layout})
|
|
52
|
+
stamp stamp_name if (stamp_dictionary_registry.key? stamp_name rescue false)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@rhrev_deferred_pages = nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def stamp_foreground_image doc, has_front_cover
|
|
61
|
+
pages = state.pages
|
|
62
|
+
if (first_page = (has_front_cover ? (pages.drop 1) : pages).find {|it| !it.imported_page? }) &&
|
|
63
|
+
(first_page_num = (pages.index first_page) + 1) &&
|
|
64
|
+
(fg_image = resolve_background_image doc, @theme, 'page-foreground-image') && fg_image[0]
|
|
65
|
+
stamps = ::Set.new
|
|
66
|
+
rhrev_range = @rhrev_deferred_pages || (0..0)
|
|
67
|
+
(first_page_num..page_count).each do |num|
|
|
68
|
+
next if rhrev_range.include?(num)
|
|
69
|
+
go_to_page num
|
|
70
|
+
next if page.imported_page?
|
|
71
|
+
unless stamps.include? (stamp_name = %(foreground-image-#{page.layout}))
|
|
72
|
+
create_stamp stamp_name do
|
|
73
|
+
canvas { image fg_image[0], ({ position: :center, vposition: :center }.merge fg_image[1]) }
|
|
74
|
+
end
|
|
75
|
+
stamps << stamp_name
|
|
76
|
+
end
|
|
77
|
+
stamp stamp_name
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def table_contains_pagerhref? node
|
|
83
|
+
return false unless node.context == :table
|
|
84
|
+
node.rows[:body].each do |row|
|
|
85
|
+
row.each do |cell|
|
|
86
|
+
return true if cell.text.to_s.include?('pagerhref:')
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def allocate_pagerhref_table_extent node
|
|
93
|
+
extent = dry_run onto: self do
|
|
94
|
+
@rendering_deferred_table = true
|
|
95
|
+
super_convert_table node
|
|
96
|
+
@rendering_deferred_table = false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
@pagerhref_tables ||= []
|
|
100
|
+
@pagerhref_tables << {
|
|
101
|
+
node: node,
|
|
102
|
+
extent: extent,
|
|
103
|
+
page: page_number
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
move_cursor_to extent.to.cursor
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def ink_pagerhref_tables
|
|
110
|
+
@anchor_catalog ||= {}
|
|
111
|
+
|
|
112
|
+
@pagerhref_tables.each do |table_info|
|
|
113
|
+
extent = table_info[:extent]
|
|
114
|
+
node = table_info[:node]
|
|
115
|
+
|
|
116
|
+
# Resolve pagerhrefs before rendering
|
|
117
|
+
resolve_pagerhrefs_in_table node
|
|
118
|
+
|
|
119
|
+
go_to_page extent.from.page
|
|
120
|
+
move_cursor_to extent.from.cursor
|
|
121
|
+
|
|
122
|
+
@rendering_deferred_table = true
|
|
123
|
+
super_convert_table node
|
|
124
|
+
@rendering_deferred_table = false
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def resolve_pagerhrefs_in_table table
|
|
129
|
+
# Recursively search for pagerhref markers in the table and replace them
|
|
130
|
+
debug_log "Resolving pagerhrefs. Anchor catalog has #{@anchor_catalog.keys.size} entries: #{@anchor_catalog.keys.take(10)}", @document
|
|
131
|
+
|
|
132
|
+
table.rows[:body].each do |row|
|
|
133
|
+
row.each do |cell|
|
|
134
|
+
next unless cell.text && cell.text.to_s.include?('pagerhref:')
|
|
135
|
+
|
|
136
|
+
# Replace [pagerhref:anchor] with page number
|
|
137
|
+
# We use a regex that matches the marker created by the inline macro
|
|
138
|
+
cell.text = cell.text.to_s.gsub(/\[pagerhref:(.*?)\]/) do |match|
|
|
139
|
+
anchor = $1
|
|
140
|
+
if (entry = @anchor_catalog[anchor]) && entry[:dest]
|
|
141
|
+
# TODO: Convert physical page to logical page if needed
|
|
142
|
+
entry[:dest][:page].to_s
|
|
143
|
+
else
|
|
144
|
+
debug_log "Pagerhref resolution failed for anchor '#{anchor}'. Catalog has entry? #{@anchor_catalog.key?(anchor)}", @document
|
|
145
|
+
"??"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def super_convert_table node
|
|
153
|
+
self.class.superclass.instance_method(:convert_table).bind(self).call(node)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def render_adoc_include doc, filepath
|
|
157
|
+
return unless filepath && File.exist?(filepath)
|
|
158
|
+
begin
|
|
159
|
+
content = File.read(filepath)
|
|
160
|
+
subdoc = ::Asciidoctor.load content, safe: :safe, backend: 'pdf'
|
|
161
|
+
subdoc.blocks.each { |block| traverse block }
|
|
162
|
+
rescue => e
|
|
163
|
+
warn "asciidoctor: WARNING: Failed to render include file #{filepath}: #{e.message}"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def ink_revision_history_content doc
|
|
168
|
+
collect_document_level_entries doc
|
|
169
|
+
|
|
170
|
+
if doc.attr? 'rhrev-export-to-file'
|
|
171
|
+
export_to_adoc_file doc
|
|
172
|
+
return
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
return if doc.attr('rhrev') == 'manual'
|
|
176
|
+
|
|
177
|
+
if is_initial_release?(doc)
|
|
178
|
+
initial_handling = doc.attr 'rhrev-initial-handling', 'initial-release'
|
|
179
|
+
case initial_handling
|
|
180
|
+
when 'skip'; return
|
|
181
|
+
when 'normal'; # Continue
|
|
182
|
+
else
|
|
183
|
+
render_initial_release_table doc
|
|
184
|
+
return
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
block_type = doc.attr 'rhrev-block-type', 'section'
|
|
189
|
+
|
|
190
|
+
if block_type == 'table' && !(doc.attr? 'rhrev-disable-caption')
|
|
191
|
+
unless doc.attr? 'table-caption'
|
|
192
|
+
doc.set_attr 'table-caption', (doc.attr 'table-caption', 'Table')
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if block_type == 'section'
|
|
197
|
+
version_label = doc.attr('version-label')
|
|
198
|
+
default_title = "#{version_label} History"
|
|
199
|
+
if (title = doc.attr 'rhrev-customization-title', default_title)
|
|
200
|
+
unless title.empty?
|
|
201
|
+
heading_opts = { align: :left, margin_bottom: (@theme.heading_margin_bottom || 12) }
|
|
202
|
+
theme_font :heading, level: 2 do
|
|
203
|
+
ink_prose title, heading_opts
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
create_revision_table_properly doc if revision_history
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def is_initial_release? doc
|
|
213
|
+
revnumber = doc.attr('revnumber')
|
|
214
|
+
return false unless revnumber
|
|
215
|
+
revnumber.to_s.strip == '1.0' || revnumber.to_s.strip == '1'
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def render_initial_release_table doc
|
|
219
|
+
block_type = doc.attr 'rhrev-block-type', 'section'
|
|
220
|
+
|
|
221
|
+
if block_type == 'section'
|
|
222
|
+
version_label = doc.attr('version-label')
|
|
223
|
+
default_title = "#{version_label} History"
|
|
224
|
+
if (title = doc.attr 'rhrev-customization-title', default_title)
|
|
225
|
+
unless title.empty?
|
|
226
|
+
heading_opts = { align: :left, margin_bottom: (@theme.heading_margin_bottom || 12) }
|
|
227
|
+
theme_font :heading, level: 2 do
|
|
228
|
+
ink_prose title, heading_opts
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
column_widths = get_column_widths doc
|
|
235
|
+
table_data = []
|
|
236
|
+
|
|
237
|
+
page_label = doc.attr 'rhrev-localization-page', 'Page'
|
|
238
|
+
major_changes_text = doc.attr 'rhrev-localization-major-changes', 'Major changes since'
|
|
239
|
+
|
|
240
|
+
revision_label = doc.attr('version-label', 'Revision')
|
|
241
|
+
revnumber = doc.attr('revnumber')
|
|
242
|
+
|
|
243
|
+
header_text = "*#{major_changes_text} #{revision_label} #{revnumber}*"
|
|
244
|
+
table_data << ["*#{page_label}*", header_text]
|
|
245
|
+
|
|
246
|
+
initial_text = doc.attr 'rhrev-customization-initial-release-text', 'Initial Release'
|
|
247
|
+
table_data << ["", initial_text]
|
|
248
|
+
|
|
249
|
+
formatted_widths = column_widths.map { |w| w.to_i.to_s }.join(',')
|
|
250
|
+
markup_lines = []
|
|
251
|
+
markup_lines << "[cols='#{formatted_widths}']"
|
|
252
|
+
markup_lines << "|==="
|
|
253
|
+
table_data.each do |row|
|
|
254
|
+
markup_lines << "|#{row[0]} |#{row[1]}"
|
|
255
|
+
end
|
|
256
|
+
markup_lines << "|==="
|
|
257
|
+
|
|
258
|
+
parsed_doc = ::Asciidoctor.load(markup_lines.join("\n"), safe: :safe, backend: 'pdf')
|
|
259
|
+
table = parsed_doc.blocks[0]
|
|
260
|
+
|
|
261
|
+
table.set_attr 'frame', (doc.attr 'rhrev-table-frame', 'all')
|
|
262
|
+
table.set_attr 'grid', (doc.attr 'rhrev-table-grid', 'all')
|
|
263
|
+
if (stripes = doc.attr 'rhrev-table-stripes')
|
|
264
|
+
table.set_attr 'stripes', stripes
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
convert_table table
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def ink_prose text, opts = {}
|
|
271
|
+
layout_prose text, opts
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def format_location_for_display location_text, entry, doc
|
|
275
|
+
%(<span class="rhrevpage">#{location_text}</span>)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def format_with_role text, role
|
|
279
|
+
%(<span class="#{role}">#{text}</span>)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def build_consolidated_list doc, items
|
|
283
|
+
return "" if items.empty?
|
|
284
|
+
items.map { |item| "* #{item}" }.join("\n")
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def build_description_xrefs anchor, doc, entry
|
|
288
|
+
# Build description xref text directly using <a anchor="..."> markup
|
|
289
|
+
# This is necessary because xref: syntax won't resolve in a parsed subdocument
|
|
290
|
+
xrefstyle_config = doc.attr 'rhrev-description-xrefstyle', ''
|
|
291
|
+
styles = xrefstyle_config.split(/\s+/).reject(&:empty?)
|
|
292
|
+
|
|
293
|
+
has_reftext = entry && entry[:reftext]
|
|
294
|
+
|
|
295
|
+
if styles.empty?
|
|
296
|
+
# Default: use basic style
|
|
297
|
+
display_text = build_xref_text(anchor, 'basic', entry, doc)
|
|
298
|
+
formatted_text = format_with_role(display_text, 'rhrevdescription')
|
|
299
|
+
"<a anchor=\"#{anchor}\">#{formatted_text}</a>"
|
|
300
|
+
elsif styles.length == 1
|
|
301
|
+
display_text = build_xref_text(anchor, styles[0], entry, doc)
|
|
302
|
+
formatted_text = format_with_role(display_text, 'rhrevdescription')
|
|
303
|
+
"<a anchor=\"#{anchor}\">#{formatted_text}</a>"
|
|
304
|
+
elsif styles.join(' ') == 'short basic'
|
|
305
|
+
# Special case: add both short and basic xrefs
|
|
306
|
+
# For sections: check if numbered (respects :!sectnum:)
|
|
307
|
+
is_numbered = entry ? check_if_numbered(entry, anchor, doc) : false
|
|
308
|
+
|
|
309
|
+
if has_reftext || !is_numbered
|
|
310
|
+
# If reftext is defined OR section is unnumbered, just use basic to avoid duplication
|
|
311
|
+
display_text = build_xref_text(anchor, 'basic', entry, doc)
|
|
312
|
+
formatted_text = format_with_role(display_text, 'rhrevdescription')
|
|
313
|
+
"<a anchor=\"#{anchor}\">#{formatted_text}</a>"
|
|
314
|
+
else
|
|
315
|
+
# No reftext and numbered section, safe to add both
|
|
316
|
+
display_short = build_xref_text(anchor, 'short', entry, doc)
|
|
317
|
+
display_basic = build_xref_text(anchor, 'basic', entry, doc)
|
|
318
|
+
formatted_short = format_with_role(display_short, 'rhrevdescription')
|
|
319
|
+
formatted_basic = format_with_role(display_basic, 'rhrevdescription')
|
|
320
|
+
"<a anchor=\"#{anchor}\">#{formatted_short}</a> <a anchor=\"#{anchor}\">#{formatted_basic}</a>"
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
# Multiple styles - output each
|
|
324
|
+
xrefs = styles.map do |style|
|
|
325
|
+
display_text = build_xref_text(anchor, style, entry, doc)
|
|
326
|
+
formatted_text = format_with_role(display_text, 'rhrevdescription')
|
|
327
|
+
"<a anchor=\"#{anchor}\">#{formatted_text}</a>"
|
|
328
|
+
end
|
|
329
|
+
xrefs.join(" ")
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def create_revision_table_properly doc
|
|
334
|
+
asciidoc_table = build_table_via_parsing doc
|
|
335
|
+
|
|
336
|
+
disable_caption = doc.attr? 'rhrev-disable-caption'
|
|
337
|
+
if disable_caption
|
|
338
|
+
saved_caption = doc.attr 'table-caption'
|
|
339
|
+
doc.set_attr 'table-caption', ''
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
convert_table asciidoc_table
|
|
343
|
+
|
|
344
|
+
if disable_caption
|
|
345
|
+
if saved_caption
|
|
346
|
+
doc.set_attr 'table-caption', saved_caption
|
|
347
|
+
else
|
|
348
|
+
doc.delete_attr 'table-caption'
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def add_custom_first_row_to_markup markup_lines, doc
|
|
354
|
+
if (first_row_merged = doc.attr 'rhrev-customization-first-row')
|
|
355
|
+
processed = preprocess_attribute_content(first_row_merged)
|
|
356
|
+
cell_prefix = needs_asciidoc_cell?(processed) ? "2+a|" : "2+|"
|
|
357
|
+
markup_lines << ""
|
|
358
|
+
markup_lines << "#{cell_prefix}#{processed}"
|
|
359
|
+
else
|
|
360
|
+
first_row_left = doc.attr 'rhrev-customization-first-row-left'
|
|
361
|
+
first_row_right = doc.attr 'rhrev-customization-first-row-right'
|
|
362
|
+
|
|
363
|
+
if first_row_left || first_row_right
|
|
364
|
+
left_processed = first_row_left ? preprocess_attribute_content(first_row_left) : ''
|
|
365
|
+
right_processed = first_row_right ? preprocess_attribute_content(first_row_right) : ''
|
|
366
|
+
left_prefix = needs_asciidoc_cell?(left_processed) ? "a|" : "|"
|
|
367
|
+
right_prefix = needs_asciidoc_cell?(right_processed) ? "a|" : "|"
|
|
368
|
+
markup_lines << ""
|
|
369
|
+
markup_lines << "#{left_prefix}#{left_processed}"
|
|
370
|
+
markup_lines << "#{right_prefix}#{right_processed}"
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def build_table_via_parsing doc
|
|
376
|
+
column_widths = get_column_widths doc
|
|
377
|
+
markup_lines = []
|
|
378
|
+
formatted_widths = column_widths.map { |w| w.to_i.to_s }.join(',')
|
|
379
|
+
markup_lines << "[cols='#{formatted_widths}']"
|
|
380
|
+
markup_lines << "|==="
|
|
381
|
+
|
|
382
|
+
add_custom_first_row_to_markup markup_lines, doc
|
|
383
|
+
|
|
384
|
+
markup_lines << "|Placeholder"
|
|
385
|
+
markup_lines << "a|* Placeholder list"
|
|
386
|
+
markup_lines << "|==="
|
|
387
|
+
|
|
388
|
+
markup_str = markup_lines.join("\n")
|
|
389
|
+
parsed_doc = ::Asciidoctor.load(markup_str, safe: :safe, backend: 'pdf')
|
|
390
|
+
table = parsed_doc.blocks[0]
|
|
391
|
+
|
|
392
|
+
table.set_attr 'frame', (doc.attr 'rhrev-table-frame', 'all')
|
|
393
|
+
table.set_attr 'grid', (doc.attr 'rhrev-table-grid', 'all')
|
|
394
|
+
if (stripes = doc.attr 'rhrev-table-stripes')
|
|
395
|
+
table.set_attr 'stripes', stripes
|
|
396
|
+
end
|
|
397
|
+
if (caption = doc.attr 'rhrev-table-caption')
|
|
398
|
+
table.title = caption
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
has_custom_first_row = (doc.attr? 'rhrev-customization-first-row') ||
|
|
402
|
+
(doc.attr? 'rhrev-customization-first-row-left') ||
|
|
403
|
+
(doc.attr? 'rhrev-customization-first-row-right')
|
|
404
|
+
|
|
405
|
+
custom_first_row = has_custom_first_row ? table.rows.body[0] : nil
|
|
406
|
+
|
|
407
|
+
placeholder_row = has_custom_first_row ? table.rows.body[1] : table.rows.body[0]
|
|
408
|
+
template_location_cell = placeholder_row[0]
|
|
409
|
+
template_asciidoc_cell = placeholder_row[1]
|
|
410
|
+
|
|
411
|
+
table.rows.body.clear
|
|
412
|
+
table.rows.body << custom_first_row if custom_first_row
|
|
413
|
+
|
|
414
|
+
page_label = doc.attr 'rhrev-localization-page', 'Page'
|
|
415
|
+
major_changes_text = doc.attr 'rhrev-localization-major-changes', 'Major changes since'
|
|
416
|
+
all_text = doc.attr 'rhrev-localization-all', 'All'
|
|
417
|
+
cover_text = doc.attr 'rhrev-localization-cover', 'Cover'
|
|
418
|
+
|
|
419
|
+
revision_history.sorted_revisions.each do |revision|
|
|
420
|
+
prevrev_attr = "#{revision}-prevrev"
|
|
421
|
+
prevrevdate_attr = "#{revision}-prevrevdate"
|
|
422
|
+
revision_label = doc.attr('version-label', 'Revision')
|
|
423
|
+
prev_title = doc.attr(prevrev_attr) || "#{revision.tr('-', '.')}"
|
|
424
|
+
prev_date = doc.attr(prevrevdate_attr) || ""
|
|
425
|
+
prev_rev_text = format_prev_rev(prev_title, prev_date, doc)
|
|
426
|
+
|
|
427
|
+
if prev_title.strip.downcase.start_with?(revision_label.downcase)
|
|
428
|
+
header_text = "*#{major_changes_text} #{prev_rev_text}*"
|
|
429
|
+
else
|
|
430
|
+
header_text = "*#{major_changes_text} #{revision_label} #{prev_rev_text}*"
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
page_cell = build_text_cell table, 0, "*#{page_label}*"
|
|
434
|
+
header_cell = build_text_cell table, 1, header_text
|
|
435
|
+
table.rows.body << [page_cell, header_cell]
|
|
436
|
+
|
|
437
|
+
if (all_change = revision_history.instance_variable_get(:@all_entries)[revision])
|
|
438
|
+
row = build_list_row table, all_text, all_change, nil, nil, template_location_cell, template_asciidoc_cell, doc, is_all_or_cover: true
|
|
439
|
+
table.rows.body << row
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
if (cover_change = revision_history.instance_variable_get(:@cover_entries)[revision])
|
|
443
|
+
unless doc.backend.to_s == 'html5'
|
|
444
|
+
row = build_list_row table, cover_text, cover_change, nil, nil, template_location_cell, template_asciidoc_cell, doc, is_all_or_cover: true
|
|
445
|
+
table.rows.body << row
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
entries = revision_history.entries[revision] || []
|
|
450
|
+
entries = entries.sort_by do |e|
|
|
451
|
+
page_num = e[:dest] ? e[:dest][:page_sortable] : Float::INFINITY
|
|
452
|
+
y_pos = e[:dest] && e[:dest][:y] ? e[:dest][:y] : 0
|
|
453
|
+
# Sort by page (ASC) then y_pos (DESC - top to bottom) then sequence (ASC)
|
|
454
|
+
[page_num, -y_pos, e[:sequence] || 0]
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
entries.each do |entry|
|
|
458
|
+
location = build_location_text entry, doc
|
|
459
|
+
row = build_list_row table, location, entry[:change], entry[:anchor], entry, template_location_cell, template_asciidoc_cell, doc
|
|
460
|
+
table.rows.body << row
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
if doc.attr? 'rhrev-table-extra-blank-row'
|
|
465
|
+
blank_row = build_text_row table, ["\u00A0", "\u00A0"]
|
|
466
|
+
table.rows.body << blank_row
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
table
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def build_text_row table, cells_data
|
|
473
|
+
cells_data.map.with_index do |text, col_idx|
|
|
474
|
+
build_cell table, col_idx, text
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def format_as_list text
|
|
479
|
+
text_str = text.to_s
|
|
480
|
+
# Don't skip if it doesn't have * - we might want to force list
|
|
481
|
+
|
|
482
|
+
tokens = text_str.split(/\s(?=\*+\s)/)
|
|
483
|
+
return text_str if tokens.empty?
|
|
484
|
+
|
|
485
|
+
lines = []
|
|
486
|
+
first = tokens.shift
|
|
487
|
+
|
|
488
|
+
# Check if first item already starts with *
|
|
489
|
+
if first.strip.start_with?('*')
|
|
490
|
+
lines << first.strip
|
|
491
|
+
else
|
|
492
|
+
lines << "* #{first.strip}"
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
tokens.each do |token|
|
|
496
|
+
lines << token.strip
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
lines.join("\n")
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def build_list_row table, location_text, change_text, anchor, entry, template_location_cell, template_asciidoc_cell, doc, is_all_or_cover: false
|
|
503
|
+
if anchor && location_text != '???'
|
|
504
|
+
location_cell = ::Asciidoctor::Table::Cell.new(template_location_cell.column, "", template_asciidoc_cell.attributes.dup)
|
|
505
|
+
location_cell.inner_document.blocks.clear
|
|
506
|
+
para_block = ::Asciidoctor::Block.new(location_cell.inner_document, :paragraph)
|
|
507
|
+
formatted_location = format_with_role(location_text, 'rhrevpage')
|
|
508
|
+
para_block.lines = ["<a anchor=\"#{anchor}\">#{formatted_location}</a>"]
|
|
509
|
+
location_cell.inner_document.blocks << para_block
|
|
510
|
+
else
|
|
511
|
+
location_cell = ::Asciidoctor::Table::Cell.new(template_location_cell.column, location_text, template_location_cell.attributes.dup)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
change_cell = ::Asciidoctor::Table::Cell.new(template_asciidoc_cell.column, "", template_asciidoc_cell.attributes.dup)
|
|
515
|
+
change_cell.inner_document.blocks.clear
|
|
516
|
+
|
|
517
|
+
cell_doc = change_cell.inner_document
|
|
518
|
+
|
|
519
|
+
if is_all_or_cover
|
|
520
|
+
# For all/cover entries, just add the change text
|
|
521
|
+
processed = preprocess_attribute_content(change_text)
|
|
522
|
+
if processed.include?('* ')
|
|
523
|
+
processed = format_as_list(processed)
|
|
524
|
+
end
|
|
525
|
+
if needs_asciidoc_cell?(processed)
|
|
526
|
+
parsed = ::Asciidoctor.load(processed, safe: :safe, backend: 'pdf')
|
|
527
|
+
parsed.blocks.each do |block|
|
|
528
|
+
block.parent = cell_doc
|
|
529
|
+
cell_doc.blocks << block
|
|
530
|
+
end
|
|
531
|
+
else
|
|
532
|
+
para = ::Asciidoctor::Block.new(cell_doc, :paragraph, source: processed)
|
|
533
|
+
cell_doc.blocks << para
|
|
534
|
+
end
|
|
535
|
+
else
|
|
536
|
+
description_config = doc.attr 'rhrev-customization-description-format', 'xref,desc'
|
|
537
|
+
show_desc = description_config.include?('desc')
|
|
538
|
+
show_xref = description_config.include?('xref')
|
|
539
|
+
|
|
540
|
+
# Add xref as a direct paragraph block (not parsed) - <a anchor="..."> is PDF converter syntax
|
|
541
|
+
if show_xref && anchor
|
|
542
|
+
xref_markup = build_description_xrefs(anchor, doc, entry)
|
|
543
|
+
xref_block = ::Asciidoctor::Block.new(cell_doc, :paragraph)
|
|
544
|
+
xref_block.lines = [xref_markup]
|
|
545
|
+
cell_doc.blocks << xref_block
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# Add change description text
|
|
549
|
+
if show_desc && !change_text.to_s.empty?
|
|
550
|
+
processed = preprocess_attribute_content(change_text)
|
|
551
|
+
processed = format_as_list(processed)
|
|
552
|
+
|
|
553
|
+
if needs_asciidoc_cell?(processed)
|
|
554
|
+
parsed = ::Asciidoctor.load(processed, safe: :safe, backend: 'pdf')
|
|
555
|
+
parsed.blocks.each do |block|
|
|
556
|
+
block.parent = cell_doc
|
|
557
|
+
cell_doc.blocks << block
|
|
558
|
+
end
|
|
559
|
+
else
|
|
560
|
+
para = ::Asciidoctor::Block.new(cell_doc, :paragraph, source: processed)
|
|
561
|
+
cell_doc.blocks << para
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
[location_cell, change_cell]
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def build_cell table, col_idx, text
|
|
570
|
+
column = table.columns[col_idx]
|
|
571
|
+
::Asciidoctor::Table::Cell.new column, text
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def build_text_cell table, col_idx, text
|
|
575
|
+
build_cell table, col_idx, text
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def build_location_text entry, doc
|
|
579
|
+
if entry[:dest]
|
|
580
|
+
entry[:dest][:page]
|
|
581
|
+
else
|
|
582
|
+
"???"
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def check_if_numbered(entry, anchor, doc)
|
|
587
|
+
context = entry[:context]
|
|
588
|
+
if context == :section
|
|
589
|
+
return true unless entry[:sectnum]
|
|
590
|
+
if doc.respond_to?(:catalog) && doc.catalog && doc.catalog[:refs]
|
|
591
|
+
section = doc.catalog[:refs][anchor]
|
|
592
|
+
if section && section.respond_to?(:numbered)
|
|
593
|
+
return section.numbered
|
|
594
|
+
elsif section && section.respond_to?(:numbered?)
|
|
595
|
+
return section.numbered?
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
return true
|
|
599
|
+
end
|
|
600
|
+
if entry[:caption_number]
|
|
601
|
+
return true
|
|
602
|
+
end
|
|
603
|
+
false
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def build_xref_text(anchor, style, entry, doc)
|
|
607
|
+
# If reftext is defined, it overrides everything
|
|
608
|
+
return entry[:reftext] if entry && entry[:reftext]
|
|
609
|
+
|
|
610
|
+
title = entry && entry[:title] ? entry[:title] : nil
|
|
611
|
+
sectnum = entry && entry[:sectnum] ? entry[:sectnum] : nil
|
|
612
|
+
context = entry && entry[:context] ? entry[:context] : nil
|
|
613
|
+
is_chapter = entry && entry[:is_chapter]
|
|
614
|
+
caption_number = entry && entry[:caption_number]
|
|
615
|
+
|
|
616
|
+
# Fallback if no title - convert anchor to title case
|
|
617
|
+
unless title
|
|
618
|
+
# Handle Antora ::: separator - extract fragment for display
|
|
619
|
+
display_anchor = anchor.include?(':::') ? anchor.split(':::').last : anchor
|
|
620
|
+
clean_anchor = display_anchor.sub(/^_/, '')
|
|
621
|
+
return clean_anchor.tr('-', ' ').split.map(&:capitalize).join(' ')
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# For sections with numbers, construct appropriate xreftext
|
|
625
|
+
if sectnum && context == :section
|
|
626
|
+
numbered = check_if_numbered(entry, anchor, doc)
|
|
627
|
+
|
|
628
|
+
if numbered
|
|
629
|
+
if doc.attr? 'rhrev-customization-xrefstyleshort-remove-trailling-period'
|
|
630
|
+
clean_sectnum = sectnum.to_s.sub(/\.$/, '')
|
|
631
|
+
else
|
|
632
|
+
clean_sectnum = sectnum.to_s
|
|
633
|
+
clean_sectnum += '.' unless clean_sectnum.end_with?('.')
|
|
634
|
+
end
|
|
635
|
+
signifier = is_chapter ? 'Chapter' : 'Section'
|
|
636
|
+
|
|
637
|
+
case style
|
|
638
|
+
when 'full'
|
|
639
|
+
"#{signifier} #{clean_sectnum}, \"#{title}\""
|
|
640
|
+
when 'short'
|
|
641
|
+
"#{signifier} #{clean_sectnum}"
|
|
642
|
+
when 'basic'
|
|
643
|
+
title
|
|
644
|
+
else
|
|
645
|
+
title
|
|
646
|
+
end
|
|
647
|
+
else
|
|
648
|
+
# Section numbering is disabled, just return the title
|
|
649
|
+
title
|
|
650
|
+
end
|
|
651
|
+
elsif caption_number && [:example, :table, :listing, :image].include?(context)
|
|
652
|
+
# For numbered blocks (examples, tables, listings, images), the period must be added to match AsciiDoctor-PDF's default built-in theming/presentation behavior.
|
|
653
|
+
if doc.attr? 'rhrev-customization-xrefstyleshort-remove-trailling-period'
|
|
654
|
+
clean_caption_number = caption_number.to_s.sub(/\.$/, '')
|
|
655
|
+
else
|
|
656
|
+
clean_caption_number = caption_number.to_s
|
|
657
|
+
clean_caption_number += '.' unless clean_caption_number.end_with?('.')
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
signifier = case context
|
|
661
|
+
when :example
|
|
662
|
+
doc.attr('example-caption', 'Example').sub(/\.?\s*$/, '')
|
|
663
|
+
when :table
|
|
664
|
+
doc.attr('table-caption', 'Table').sub(/\.?\s*$/, '')
|
|
665
|
+
when :listing
|
|
666
|
+
doc.attr('listing-caption', 'Listing').sub(/\.?\s*$/, '')
|
|
667
|
+
when :image
|
|
668
|
+
doc.attr('figure-caption', 'Figure').sub(/\.?\s*$/, '')
|
|
669
|
+
else
|
|
670
|
+
context.to_s.capitalize
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
case style
|
|
674
|
+
when 'full'
|
|
675
|
+
"#{signifier} #{clean_caption_number}, \"#{title}\""
|
|
676
|
+
when 'short'
|
|
677
|
+
"#{signifier} #{clean_caption_number}"
|
|
678
|
+
when 'basic'
|
|
679
|
+
title
|
|
680
|
+
else
|
|
681
|
+
title
|
|
682
|
+
end
|
|
683
|
+
else
|
|
684
|
+
# No section number or caption number, just return title for all styles
|
|
685
|
+
title
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def get_column_widths doc
|
|
690
|
+
widths_str = doc.attr 'rhrev-table-column-width', Asciidoctor::PDF::Rhrev::Catalog.default_columns
|
|
691
|
+
widths_str.split(',').map(&:strip)
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
end
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
end
|