Class: Uniword::Docx::IdAllocator

Inherits:
Object
  • Object
show all
Defined in:
lib/uniword/docx/id_allocator.rb

Overview

Single owner of all ID assignment in a DOCX package.

Every builder, the adapter, and the reconciler call into this class for rId, footnote, endnote, bookmark, comment, and paragraph ID allocation. No other code generates IDs.

Two usage modes:

Creating from scratch — allocator starts empty, all IDs are fresh.
Editing a template   — call seed_from_* methods BEFORE any builder
                      runs, so new IDs don't collide with existing ones.

This is the "populate-first" principle: when loading a template DOCX, parse and seed ALL existing IDs from the template before modification.

Constant Summary collapse

REL_TYPE_BASE =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships"
IMAGE_REL_TYPE =
"#{REL_TYPE_BASE}/image"
HEADER_REL_TYPE =
"#{REL_TYPE_BASE}/header"
"#{REL_TYPE_BASE}/footer"
"#{REL_TYPE_BASE}/hyperlink"
CHART_REL_TYPE =
"#{REL_TYPE_BASE}/chart"
FOOTNOTES_REL_TYPE =
"#{REL_TYPE_BASE}/footnotes"
ENDNOTES_REL_TYPE =
"#{REL_TYPE_BASE}/endnotes"
THEME_REL_TYPE =
"#{REL_TYPE_BASE}/theme"
NUMBERING_REL_TYPE =
"#{REL_TYPE_BASE}/numbering"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeIdAllocator

Returns a new instance of IdAllocator.



33
34
35
36
37
38
39
40
41
42
# File 'lib/uniword/docx/id_allocator.rb', line 33

def initialize
  @rid_counter = 0
  @rid_entries = {}  # [target, type] -> { id, type, target, target_mode }
  @footnote_counter = 1
  @endnote_counter = 1
  @bookmark_counter = 0
  @comment_counter = 0
  @para_counter = 0
  @rsid_counter = 0
end

Class Method Details

.populate_from_package(package) ⇒ Object

Build an allocator seeded from every source on a package in one call. Used by both Package#populate_allocator and DocumentBuilder.from_template so they cannot drift apart.



131
132
133
134
135
136
137
138
139
140
# File 'lib/uniword/docx/id_allocator.rb', line 131

def self.populate_from_package(package)
  alloc = new
  alloc.seed_from_rels(package.document_rels&.relationships)
  alloc.seed_from_rels(package.package_rels&.relationships)
  alloc.seed_from_notes(
    package.footnotes&.footnote_entries,
    package.endnotes&.endnote_entries,
  )
  alloc
end

Instance Method Details

#all_relsObject

Produce the final ordered list of all allocated relationships.



118
119
120
# File 'lib/uniword/docx/id_allocator.rb', line 118

def all_rels
  @rid_entries.values.sort_by { |r| r[:id][/\d+/]&.to_i || 0 }
end

#alloc_bookmark_idObject



68
69
70
71
# File 'lib/uniword/docx/id_allocator.rb', line 68

def alloc_bookmark_id
  @bookmark_counter += 1
  @bookmark_counter.to_s
end

#alloc_comment_idObject



73
74
75
76
# File 'lib/uniword/docx/id_allocator.rb', line 73

def alloc_comment_id
  @comment_counter += 1
  @comment_counter.to_s
end

#alloc_endnote_idObject



62
63
64
65
66
# File 'lib/uniword/docx/id_allocator.rb', line 62

def alloc_endnote_id
  id = @endnote_counter
  @endnote_counter += 1
  id
end

#alloc_footnote_idObject



56
57
58
59
60
# File 'lib/uniword/docx/id_allocator.rb', line 56

def alloc_footnote_id
  id = @footnote_counter
  @footnote_counter += 1
  id
end

#alloc_para_idObject



78
79
80
81
# File 'lib/uniword/docx/id_allocator.rb', line 78

def alloc_para_id
  @para_counter += 1
  Digest::SHA256.hexdigest("para:#{@para_counter}").upcase[0, 12]
end

#alloc_rid(target:, type:, target_mode: nil) ⇒ Object

Allocate a relationship ID for a target+type pair. Returns existing rId if this target+type was already registered.



46
47
48
49
50
51
52
53
54
# File 'lib/uniword/docx/id_allocator.rb', line 46

def alloc_rid(target:, type:, target_mode: nil)
  key = [target, type.to_s]
  @rid_entries[key] ||= begin
    @rid_counter += 1
    { id: "rId#{@rid_counter}", type: type.to_s,
      target: target, target_mode: target_mode }
  end
  @rid_entries[key][:id]
end

#alloc_rsidObject



83
84
85
86
# File 'lib/uniword/docx/id_allocator.rb', line 83

def alloc_rsid
  @rsid_counter += 1
  Digest::SHA256.hexdigest("rsid:#{@rsid_counter}").upcase[0, 12]
end

#rid_for(target:, type:) ⇒ Object

Check if a relationship has been registered for a target+type.



123
124
125
126
# File 'lib/uniword/docx/id_allocator.rb', line 123

def rid_for(target:, type:)
  key = [target, type.to_s]
  @rid_entries[key]&.fetch(:id, nil)
end

#seed_from_notes(footnote_entries, endnote_entries) ⇒ Object

Seed footnote/endnote counters from existing note entries.



106
107
108
109
110
111
112
113
114
115
# File 'lib/uniword/docx/id_allocator.rb', line 106

def seed_from_notes(footnote_entries, endnote_entries)
  footnote_entries&.each do |e|
    id = e.id.to_i
    @footnote_counter = [@footnote_counter, id + 1].max if id > 0
  end
  endnote_entries&.each do |e|
    id = e.id.to_i
    @endnote_counter = [@endnote_counter, id + 1].max if id > 0
  end
end

#seed_from_rels(relationships) ⇒ Object

Seed from a relationships collection — preserves existing rIds.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/uniword/docx/id_allocator.rb', line 89

def seed_from_rels(relationships)
  return unless relationships

  relationships.each do |r|
    key = [r.target, r.type.to_s]
    @rid_entries[key] = {
      id: r.id,
      type: r.type.to_s,
      target: r.target,
      target_mode: r.target_mode,
    }
    num = r.id[/\ArId(\d+)\z/, 1]&.to_i || 0
    @rid_counter = [@rid_counter, num].max
  end
end