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,92 @@
|
|
|
1
|
+
module Asciidoctor
|
|
2
|
+
module Rhrev
|
|
3
|
+
class Catalog
|
|
4
|
+
attr_accessor :entries
|
|
5
|
+
attr_accessor :all_entries
|
|
6
|
+
attr_accessor :cover_entries
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@entries = {} # Hash of revision => [entries]
|
|
10
|
+
@all_entries = {} # Hash of revision => text for 'all' entries
|
|
11
|
+
@cover_entries = {} # Hash of revision => text for 'cover' entries
|
|
12
|
+
@sequence_counter = 0 # Counter to track document order
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.default_labels
|
|
16
|
+
{
|
|
17
|
+
'page' => 'Page',
|
|
18
|
+
'major_changes' => 'Major changes since'
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.default_columns
|
|
23
|
+
'30,70' # location, change
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_entry revision, anchor, change_text, reftext: nil, title: nil, sectnum: nil, context: nil, is_chapter: false, sectname: nil, caption_number: nil, source_line: nil
|
|
27
|
+
@entries[revision] ||= []
|
|
28
|
+
|
|
29
|
+
# Prevent duplicates (e.g. if collected multiple times)
|
|
30
|
+
return if @entries[revision].any? { |e| e[:anchor] == anchor }
|
|
31
|
+
|
|
32
|
+
@sequence_counter += 1
|
|
33
|
+
# Use source_line if available, otherwise fall back to sequence counter
|
|
34
|
+
sequence = source_line || @sequence_counter
|
|
35
|
+
@entries[revision] << {
|
|
36
|
+
anchor: anchor,
|
|
37
|
+
change: change_text,
|
|
38
|
+
dest: nil,
|
|
39
|
+
sequence: sequence,
|
|
40
|
+
reftext: reftext,
|
|
41
|
+
title: title,
|
|
42
|
+
sectnum: sectnum,
|
|
43
|
+
context: context,
|
|
44
|
+
is_chapter: is_chapter,
|
|
45
|
+
sectname: sectname,
|
|
46
|
+
caption_number: caption_number
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def add_all_entry revision, change_text
|
|
51
|
+
@all_entries[revision] = change_text
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_cover_entry revision, change_text
|
|
55
|
+
@cover_entries[revision] = change_text
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def link_dest_to_page anchor, physical_page_number, start_page_number, y: nil
|
|
59
|
+
@entries.each do |revision, entry_list|
|
|
60
|
+
entry_list.each do |entry|
|
|
61
|
+
next unless entry[:anchor] == anchor
|
|
62
|
+
|
|
63
|
+
virtual_page_number = physical_page_number - (start_page_number - 1)
|
|
64
|
+
entry[:dest] = {
|
|
65
|
+
anchor: anchor,
|
|
66
|
+
page: (virtual_page_number < 1 ?
|
|
67
|
+
(Asciidoctor::PDF::RomanNumeral.new physical_page_number, :lower) :
|
|
68
|
+
virtual_page_number).to_s,
|
|
69
|
+
page_sortable: physical_page_number,
|
|
70
|
+
y: y
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def sorted_revisions
|
|
77
|
+
# Collect all unique revisions from entries, all_entries, and cover_entries
|
|
78
|
+
all_revisions = (@entries.keys + @all_entries.keys + @cover_entries.keys).uniq
|
|
79
|
+
|
|
80
|
+
# Sort revisions in descending order (newest first)
|
|
81
|
+
all_revisions.sort do |a, b|
|
|
82
|
+
parse_revision(b) <=> parse_revision(a)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def parse_revision rev_string
|
|
87
|
+
# Parse "1-1", "1-2", "1-10-2" into comparable array [1, 1], [1, 2], [1, 10, 2]
|
|
88
|
+
rev_string.split('-').map(&:to_i)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
require 'asciidoctor/pdf' unless defined? Asciidoctor::PDF
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require_relative 'helpers'
|
|
4
|
+
require_relative 'catalog'
|
|
5
|
+
require_relative 'renderer'
|
|
6
|
+
require_relative 'exporter'
|
|
7
|
+
require_relative 'processors'
|
|
8
|
+
|
|
9
|
+
module Asciidoctor
|
|
10
|
+
module PDF
|
|
11
|
+
module Rhrev
|
|
12
|
+
# Import shared classes from parent namespace
|
|
13
|
+
Catalog = ::Asciidoctor::Rhrev::Catalog
|
|
14
|
+
Helpers = ::Asciidoctor::Rhrev::Helpers
|
|
15
|
+
|
|
16
|
+
class Converter < ::Asciidoctor::Converter.for('pdf')
|
|
17
|
+
register_for 'pdf'
|
|
18
|
+
|
|
19
|
+
include Helpers
|
|
20
|
+
include Renderer
|
|
21
|
+
include Exporter
|
|
22
|
+
|
|
23
|
+
def initialize *args
|
|
24
|
+
super
|
|
25
|
+
@revision_history_extent = nil
|
|
26
|
+
@revision_prefix = nil
|
|
27
|
+
@rhrev_deferred_pages = nil
|
|
28
|
+
@pagerhref_tables = []
|
|
29
|
+
@export_completed = false
|
|
30
|
+
@catalog = nil
|
|
31
|
+
@manual_mode = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def revision_history
|
|
35
|
+
@catalog ||= Catalog.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def convert_document node
|
|
39
|
+
@revision_prefix = node.attr 'revhistoryprefix', 'rhrev'
|
|
40
|
+
|
|
41
|
+
# Check if we're in manual mode (pagerhref support needed)
|
|
42
|
+
@manual_mode = node.attr('rhrev') == 'manual'
|
|
43
|
+
|
|
44
|
+
# Initialize catalog
|
|
45
|
+
@catalog = revision_history
|
|
46
|
+
|
|
47
|
+
# Prescan document to populate catalog before rendering
|
|
48
|
+
# This is necessary for accurate space allocation
|
|
49
|
+
prescan_document node
|
|
50
|
+
|
|
51
|
+
# Render document (this will visit all nodes and update entry metadata)
|
|
52
|
+
result = super
|
|
53
|
+
|
|
54
|
+
# Ink revision history if allocated
|
|
55
|
+
if @revision_history_extent && @catalog
|
|
56
|
+
ink_revision_history node, @revision_history_extent
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Re-render tables with pagerhrefs (only in manual mode)
|
|
60
|
+
ink_pagerhref_tables if @manual_mode && @pagerhref_tables && !@pagerhref_tables.empty?
|
|
61
|
+
|
|
62
|
+
# Export to file AFTER rendering (metadata like sectnum, caption_number are now populated)
|
|
63
|
+
if node.attr?('rhrev-export-to-file') && @catalog
|
|
64
|
+
export_to_adoc_file node
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def convert_section node
|
|
71
|
+
# Skip collect during render if prescan already did it
|
|
72
|
+
collect_revision_entries node unless @prescan_complete
|
|
73
|
+
update_revision_entry_metadata node
|
|
74
|
+
|
|
75
|
+
if node.id
|
|
76
|
+
@anchor_catalog ||= {}
|
|
77
|
+
@anchor_catalog[node.id] ||= {
|
|
78
|
+
title: node.title,
|
|
79
|
+
reftext: node.reftext,
|
|
80
|
+
sectnum: node.sectnum,
|
|
81
|
+
context: node.context,
|
|
82
|
+
level: node.level
|
|
83
|
+
}
|
|
84
|
+
@anchor_catalog[node.id][:dest] = { page: page_number, y: cursor }
|
|
85
|
+
|
|
86
|
+
# Link destination to page for revision history
|
|
87
|
+
revision_history.link_dest_to_page node.id, page_number, 1, y: cursor
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
super
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def convert_table node
|
|
94
|
+
has_id = node.id && !node.id.empty? rescue false
|
|
95
|
+
has_rhrev = false
|
|
96
|
+
if node.respond_to?(:attributes) && node.attributes
|
|
97
|
+
has_rhrev = node.attributes.keys.any? { |k| k.to_s.start_with?(@revision_prefix || 'rhrev') } rescue false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Check for pagerhref macros in table (only in manual mode)
|
|
101
|
+
# This check is expensive so we skip it unless in manual mode
|
|
102
|
+
has_pagerhref = @manual_mode && !@rendering_deferred_table && table_contains_pagerhref?(node)
|
|
103
|
+
|
|
104
|
+
if !has_id && !has_rhrev && !has_pagerhref
|
|
105
|
+
return super
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Skip collect during render if prescan already did it
|
|
109
|
+
collect_revision_entries node unless @prescan_complete
|
|
110
|
+
update_revision_entry_metadata node
|
|
111
|
+
|
|
112
|
+
if has_pagerhref
|
|
113
|
+
allocate_pagerhref_table_extent node
|
|
114
|
+
else
|
|
115
|
+
result = super
|
|
116
|
+
catalog_block_anchor node
|
|
117
|
+
result
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def convert_image node
|
|
122
|
+
# Skip collect during render if prescan already did it
|
|
123
|
+
collect_revision_entries node unless @prescan_complete
|
|
124
|
+
update_revision_entry_metadata node
|
|
125
|
+
result = super
|
|
126
|
+
catalog_block_anchor node
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def convert_example node
|
|
131
|
+
# Skip collect during render if prescan already did it
|
|
132
|
+
collect_revision_entries node unless @prescan_complete
|
|
133
|
+
update_revision_entry_metadata node
|
|
134
|
+
result = super
|
|
135
|
+
catalog_block_anchor node
|
|
136
|
+
result
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def convert_listing node
|
|
140
|
+
# Skip collect during render if prescan already did it
|
|
141
|
+
collect_revision_entries node unless @prescan_complete
|
|
142
|
+
update_revision_entry_metadata node
|
|
143
|
+
result = super
|
|
144
|
+
catalog_block_anchor node
|
|
145
|
+
result
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def convert_floating_title node
|
|
149
|
+
# Skip collect during render if prescan already did it
|
|
150
|
+
collect_revision_entries node unless @prescan_complete
|
|
151
|
+
update_revision_entry_metadata node
|
|
152
|
+
result = super
|
|
153
|
+
catalog_block_anchor node
|
|
154
|
+
result
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def convert_rhrev node
|
|
158
|
+
return unless node.document.attr? 'rhrev'
|
|
159
|
+
return if @revision_history_extent
|
|
160
|
+
|
|
161
|
+
collect_document_level_entries node.document
|
|
162
|
+
@revision_history_extent = allocate_revision_history_extent node.document
|
|
163
|
+
nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def convert_inline_quoted node
|
|
167
|
+
if node.text&.match?(/^\[pagerhref:/)
|
|
168
|
+
if node.text =~ /^\[pagerhref:(.+)\]$/
|
|
169
|
+
anchor = $1
|
|
170
|
+
lookup_anchor = anchor.gsub('----', ':::')
|
|
171
|
+
|
|
172
|
+
if @anchor_catalog && @anchor_catalog[lookup_anchor] && @anchor_catalog[lookup_anchor][:dest]
|
|
173
|
+
page_num = @anchor_catalog[lookup_anchor][:dest][:page]
|
|
174
|
+
%(<a anchor="#{lookup_anchor}"><span class="pagerhref">#{page_num}</span></a>)
|
|
175
|
+
else
|
|
176
|
+
if scratch?
|
|
177
|
+
%(<a anchor="#{lookup_anchor}"><span class="pagerhref">99</span></a>)
|
|
178
|
+
else
|
|
179
|
+
%(<a anchor="#{lookup_anchor}"><span class="pagerhref">??</span></a>)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
super
|
|
184
|
+
end
|
|
185
|
+
else
|
|
186
|
+
super
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def start_new_chapter chapter
|
|
191
|
+
if !@revision_history_extent && chapter.document && (chapter.document.attr? 'rhrev')
|
|
192
|
+
rhrev_value = chapter.document.attr('rhrev')
|
|
193
|
+
export_to_file = chapter.document.attr? 'rhrev-export-to-file'
|
|
194
|
+
# Effectively only when rhrev is 'true', add the revision history section at the beginning of the document (after the title_page)
|
|
195
|
+
if rhrev_value != 'macro' && rhrev_value != 'manual' && !export_to_file
|
|
196
|
+
collect_document_level_entries chapter.document
|
|
197
|
+
@revision_history_extent = allocate_revision_history_extent chapter.document
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
super
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def collect_revision_entries node
|
|
204
|
+
return if @prescan_complete
|
|
205
|
+
return unless node.respond_to?(:attributes)
|
|
206
|
+
|
|
207
|
+
is_document = node.class.to_s.include?('Document')
|
|
208
|
+
return if is_document
|
|
209
|
+
|
|
210
|
+
# Access raw attributes safely
|
|
211
|
+
attr_entries = node.instance_variable_get(:@attributes) rescue {}
|
|
212
|
+
return if attr_entries.nil? || attr_entries.empty?
|
|
213
|
+
|
|
214
|
+
# Check for rhrev attributes before checking ID to debug missing IDs
|
|
215
|
+
has_rhrev = attr_entries.keys.any? { |k| k.to_s.start_with?(@revision_prefix) }
|
|
216
|
+
return unless has_rhrev
|
|
217
|
+
|
|
218
|
+
has_id = node.id && !node.id.empty? rescue false
|
|
219
|
+
unless has_id
|
|
220
|
+
debug_log "Node #{node.context} has rhrev attributes but NO ID. Skipping. Attributes: #{attr_entries.keys.select{|k| k.to_s.start_with?(@revision_prefix)}}", @document
|
|
221
|
+
return
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
debug_log "Found entry candidate on #{node.context} id=#{node.id}", @document
|
|
225
|
+
|
|
226
|
+
attr_entries.each do |key, value|
|
|
227
|
+
key_str = key.to_s
|
|
228
|
+
next unless key_str.start_with?(@revision_prefix)
|
|
229
|
+
next if key_str.include?('-all') || key_str.end_with?('-cover')
|
|
230
|
+
|
|
231
|
+
revision = key_str.sub("#{@revision_prefix}", '')
|
|
232
|
+
|
|
233
|
+
debug_log "Adding entry: Rev=#{revision}, Anchor=#{node.id}, Change=#{value}", @document
|
|
234
|
+
|
|
235
|
+
revision_history.add_entry revision, node.id, value.to_s,
|
|
236
|
+
reftext: nil,
|
|
237
|
+
title: nil,
|
|
238
|
+
sectnum: nil,
|
|
239
|
+
context: node.context,
|
|
240
|
+
is_chapter: false,
|
|
241
|
+
sectname: nil,
|
|
242
|
+
caption_number: nil,
|
|
243
|
+
source_line: node.lineno
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def update_revision_entry_metadata node
|
|
248
|
+
return unless node.id
|
|
249
|
+
return unless node.respond_to?(:attributes)
|
|
250
|
+
|
|
251
|
+
with_attribute_missing_suppressed node.document do
|
|
252
|
+
node.attributes.each do |key, value|
|
|
253
|
+
key_str = key.to_s
|
|
254
|
+
next unless key_str.start_with?(@revision_prefix)
|
|
255
|
+
next if key_str.include?('-all') || key_str.end_with?('-cover')
|
|
256
|
+
|
|
257
|
+
revision = key_str.sub("#{@revision_prefix}", '')
|
|
258
|
+
|
|
259
|
+
entries = revision_history.entries[revision]
|
|
260
|
+
next unless entries
|
|
261
|
+
|
|
262
|
+
entry = entries.find { |e| e[:anchor] == node.id }
|
|
263
|
+
next unless entry
|
|
264
|
+
|
|
265
|
+
if entry[:change].to_s.empty?
|
|
266
|
+
entry[:change] = value
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
entry[:reftext] = node.reftext
|
|
270
|
+
entry[:title] = node.title
|
|
271
|
+
entry[:sectnum] = node.respond_to?(:sectnum) ? node.sectnum : nil
|
|
272
|
+
|
|
273
|
+
if node.context == :section && node.document.doctype == 'book'
|
|
274
|
+
entry[:is_chapter] = (node.level == 0 || node.level == 1)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
if node.respond_to?(:caption) && node.caption
|
|
278
|
+
if node.caption =~ /(\d+)/
|
|
279
|
+
entry[:caption_number] = $1
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
entry[:sectname] = node.sectname if node.respond_to?(:sectname)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def catalog_block_anchor node
|
|
289
|
+
@anchor_catalog ||= {}
|
|
290
|
+
if node.id
|
|
291
|
+
@anchor_catalog[node.id] ||= {
|
|
292
|
+
title: node.title,
|
|
293
|
+
context: node.context
|
|
294
|
+
}
|
|
295
|
+
page_num = page_number
|
|
296
|
+
@anchor_catalog[node.id][:dest] = { page: page_num, y: cursor }
|
|
297
|
+
|
|
298
|
+
revision_history.link_dest_to_page node.id, page_num, 1, y: cursor
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def table_contains_pagerhref? node
|
|
303
|
+
return false unless node.context == :table
|
|
304
|
+
|
|
305
|
+
# Check cell content WITHOUT triggering attribute substitution
|
|
306
|
+
# This prevents counter attributes like {counter:tablecounter} from incrementing
|
|
307
|
+
# We MUST NOT call cell.text or access cell.inner_document as those trigger parsing
|
|
308
|
+
node.rows[:body].each do |row|
|
|
309
|
+
row.each do |cell|
|
|
310
|
+
# Check the raw @text instance variable if already evaluated
|
|
311
|
+
if cell.instance_variable_defined?(:@text)
|
|
312
|
+
raw_text = cell.instance_variable_get(:@text).to_s
|
|
313
|
+
return true if raw_text.include?('pagerhref:')
|
|
314
|
+
end
|
|
315
|
+
# Check the inner document's source lines if available (before parsing)
|
|
316
|
+
if cell.instance_variable_defined?(:@inner_document)
|
|
317
|
+
inner_doc = cell.instance_variable_get(:@inner_document)
|
|
318
|
+
if inner_doc && inner_doc.instance_variable_defined?(:@lines)
|
|
319
|
+
lines = inner_doc.instance_variable_get(:@lines) || []
|
|
320
|
+
return true if lines.any? { |l| l.to_s.include?('pagerhref:') }
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
# Last resort: check cell's style attribute for AsciiDoc cells
|
|
324
|
+
# AsciiDoc cells (a|) will have inner content that might contain pagerhref
|
|
325
|
+
if cell.style == :asciidoc
|
|
326
|
+
# For asciidoc cells, we need to check the source
|
|
327
|
+
# Access the cell's source blocks if available
|
|
328
|
+
if cell.instance_variable_defined?(:@inner_document)
|
|
329
|
+
inner = cell.instance_variable_get(:@inner_document)
|
|
330
|
+
if inner && inner.respond_to?(:blocks)
|
|
331
|
+
inner.blocks.each do |block|
|
|
332
|
+
if block.instance_variable_defined?(:@lines)
|
|
333
|
+
lines = block.instance_variable_get(:@lines) || []
|
|
334
|
+
return true if lines.any? { |l| l.to_s.include?('pagerhref:') }
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
false
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def allocate_pagerhref_table_extent node
|
|
346
|
+
@rendering_deferred_table = true
|
|
347
|
+
|
|
348
|
+
extent = dry_run onto: self do
|
|
349
|
+
super_convert_table node
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
@rendering_deferred_table = false
|
|
353
|
+
|
|
354
|
+
# Move cursor to after the allocated space
|
|
355
|
+
extent.each_page { |first_page| start_new_page unless first_page }
|
|
356
|
+
move_cursor_to extent.to.cursor
|
|
357
|
+
|
|
358
|
+
# Store for later re-rendering with actual page numbers
|
|
359
|
+
@pagerhref_tables << {
|
|
360
|
+
node: node,
|
|
361
|
+
extent: extent
|
|
362
|
+
}
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def super_convert_table node
|
|
366
|
+
# Call the parent class method directly to avoid recursion
|
|
367
|
+
self.class.superclass.instance_method(:convert_table).bind(self).call(node)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def ink_pagerhref_tables
|
|
371
|
+
return if scratch?
|
|
372
|
+
|
|
373
|
+
@anchor_catalog ||= {}
|
|
374
|
+
|
|
375
|
+
@pagerhref_tables.each do |table_info|
|
|
376
|
+
extent = table_info[:extent]
|
|
377
|
+
node = table_info[:node]
|
|
378
|
+
|
|
379
|
+
# Go back to the allocated space
|
|
380
|
+
go_to_page extent.from.page
|
|
381
|
+
move_cursor_to extent.from.cursor
|
|
382
|
+
|
|
383
|
+
# Re-render the table with flag set to prevent recursion
|
|
384
|
+
@rendering_deferred_table = true
|
|
385
|
+
super_convert_table node
|
|
386
|
+
@rendering_deferred_table = false
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def collect_document_level_entries doc
|
|
391
|
+
return if @document_entries_collected
|
|
392
|
+
@document_entries_collected = true
|
|
393
|
+
|
|
394
|
+
(0..9).each do |major|
|
|
395
|
+
(0..9).each do |minor|
|
|
396
|
+
revision = "#{major}-#{minor}"
|
|
397
|
+
prevrev_attr = "#{revision}-prevrev"
|
|
398
|
+
|
|
399
|
+
if doc.attr(prevrev_attr)
|
|
400
|
+
all_attr_name = "#{@revision_prefix}#{revision}-all"
|
|
401
|
+
if (all_value = doc.attr(all_attr_name))
|
|
402
|
+
revision_history.add_all_entry revision, all_value
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
cover_attr_name = "#{@revision_prefix}#{revision}-cover"
|
|
406
|
+
if (cover_value = doc.attr(cover_attr_name))
|
|
407
|
+
revision_history.add_cover_entry revision, cover_value
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def prescan_document doc
|
|
415
|
+
debug_log "Starting prescan_document", doc
|
|
416
|
+
|
|
417
|
+
# Suppress attribute missing warnings during prescan
|
|
418
|
+
# This prevents counter attributes like {counter:tablecounter} from incrementing
|
|
419
|
+
with_attribute_missing_suppressed doc do
|
|
420
|
+
collect_document_level_entries doc
|
|
421
|
+
|
|
422
|
+
# Use find_by with context filter - more efficient than single traversal
|
|
423
|
+
# because find_by(context:) can skip non-matching subtrees
|
|
424
|
+
[:section, :floating_title, :example, :listing, :table, :image].each do |ctx|
|
|
425
|
+
doc.find_by(context: ctx).each do |node|
|
|
426
|
+
collect_revision_entries node
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
@prescan_complete = true
|
|
431
|
+
debug_log "Prescan complete. Catalog has #{revision_history.entries.values.flatten.size} entries.", doc
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|