Class: Uniword::Docx::Package

Inherits:
Lutaml::Model::Serializable
  • Object
show all
Includes:
PackageDefaults, PackageSerialization
Defined in:
lib/uniword/docx/package.rb

Overview

DOCX Package - Complete DOCX file format model

Represents the entire .docx file structure as a lutaml-model object. Each XML file within the ZIP is a separate lutaml-model class.

A DOCX package CONTAINS OOXML markup wrapped in an OPC ZIP container. This class lives in Uniword::Docx, not Uniword::Ooxml, because DOCX is a file format that uses OOXML, not the other way around.

Examples:

Load DOCX

package = Package.from_file('document.docx')
package.core_properties.title = 'New Title'
package.to_file('output.docx')

Access document content

package = Package.from_file('document.docx')
package.document.body.paragraphs.each { |p| puts p.text }

Constant Summary

Constants included from PackageSerialization

Uniword::Docx::PackageSerialization::DOCX_INFRA_OPTIONS, Uniword::Docx::PackageSerialization::DOCX_PART_OPTIONS, Uniword::Docx::PackageSerialization::DOCX_PROPS_OPTIONS, Uniword::Docx::PackageSerialization::DOCX_XML_OPTIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PackageSerialization

#add_standalone, #inject_part_relationships, #serialize_infrastructure, #serialize_package_parts, #serialize_part

Methods included from PackageDefaults

included

Instance Attribute Details

#allocatorObject

Central ID allocator — owns all rId, footnote, bookmark, etc. assignment. Seeded from template rels on load; used by builders during construction.



130
131
132
# File 'lib/uniword/docx/package.rb', line 130

def allocator
  @allocator
end

#custom_xml_itemsArray<CustomXmlItem>?

Custom XML data items (customXml/item*.xml)

Returns:



49
50
51
# File 'lib/uniword/docx/package.rb', line 49

def custom_xml_items
  @custom_xml_items
end

#endnotes_relsObject

Returns the value of attribute endnotes_rels.



98
99
100
# File 'lib/uniword/docx/package.rb', line 98

def endnotes_rels
  @endnotes_rels
end

#footnotes_relsObject

Returns the value of attribute footnotes_rels.



98
99
100
# File 'lib/uniword/docx/package.rb', line 98

def footnotes_rels
  @footnotes_rels
end

#modified_part_pathsObject

Paths that have been modified and should not use raw XML passthrough.



138
139
140
# File 'lib/uniword/docx/package.rb', line 138

def modified_part_paths
  @modified_part_paths
end

#profileObject

Non-serialized attributes (DOCX packaging helpers)



97
98
99
# File 'lib/uniword/docx/package.rb', line 97

def profile
  @profile
end

#raw_xml_partsObject

Raw XML from template ZIP for unmodified parts. When present, these are used verbatim instead of re-serializing through lutaml-model (which drops unmapped elements).



135
136
137
# File 'lib/uniword/docx/package.rb', line 135

def raw_xml_parts
  @raw_xml_parts
end

#settings_relsObject

Returns the value of attribute settings_rels.



98
99
100
# File 'lib/uniword/docx/package.rb', line 98

def settings_rels
  @settings_rels
end

Class Method Details

.from_file(path) ⇒ Package

Load DOCX package from file

Parameters:

  • path (String)

    Path to .docx file

Returns:

  • (Package)

    Package with all parts loaded



153
154
155
156
157
158
159
# File 'lib/uniword/docx/package.rb', line 153

def self.from_file(path)
  extractor = Infrastructure::ZipExtractor.new
  zip_content = extractor.extract(path)
  package = from_zip_content(zip_content, path)
  package.populate_allocator
  package
end

.from_zip_content(zip_content, zip_path = nil) ⇒ Package

Create package from extracted ZIP content

Parts load by iterating Ooxml::PartRegistry.loadable and dispatching each definition to its Docx::PartLoader strategy — see PartLoader for the load order and strategy registry.

Parameters:

  • zip_content (Hash)

    Extracted ZIP files

  • zip_path (String, nil) (defaults to: nil)

    Original ZIP path for binary re-extraction

Returns:



170
171
172
173
174
# File 'lib/uniword/docx/package.rb', line 170

def self.from_zip_content(zip_content, zip_path = nil)
  package = new
  PartLoader.load(zip_content, package, zip_path: zip_path)
  package
end

.to_file(document, path, profile: nil, validate: nil) ⇒ void

This method returns an undefined value.

Save document to file (class method for DocumentWriter compatibility)

Parameters:

  • document (Wordprocessingml::DocumentRoot)

    Document to save

  • path (String)

    Output file path

  • profile (Profile, nil) (defaults to: nil)

    Reconciliation profile

  • validate (Boolean, nil) (defaults to: nil)

    Run the package integrity gate before writing; nil falls back to Uniword.configuration.validate_on_save



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/uniword/docx/package.rb', line 184

def self.to_file(document, path, profile: nil, validate: nil)
  package = new
  package.document = document
  package.profile = profile || Profile.defaults
  copy_document_parts_to_package(document, package)
  package.content_types ||= minimal_content_types
  package.package_rels ||= minimal_package_rels
  package.document_rels ||= minimal_document_rels
  package.settings ||= Uniword::Wordprocessingml::Settings.new
  package.font_table ||= Uniword::Wordprocessingml::FontTable.new
  package.web_settings ||= Uniword::Wordprocessingml::WebSettings.new
  package.to_file(path, validate: validate)
end

Instance Method Details

#applied_fixesArray<Reconciler::Fix>

Audit trail of repairs applied by the Reconciler during the most recent save (to_zip_content). Empty before the first save and when the package needed no repairs.

Returns:



145
146
147
# File 'lib/uniword/docx/package.rb', line 145

def applied_fixes
  @applied_fixes ||= []
end

#bodyObject



297
298
299
# File 'lib/uniword/docx/package.rb', line 297

def body
  document&.body
end

#chartsObject



311
312
313
# File 'lib/uniword/docx/package.rb', line 311

def charts
  document&.charts || []
end

#each_paragraphObject



305
306
307
# File 'lib/uniword/docx/package.rb', line 305

def each_paragraph(&)
  paragraphs.each(&)
end

#embeddingsPartCollection

OLE/embedded object binaries (word/embeddings/*), keyed by target.

Returns:



103
104
105
# File 'lib/uniword/docx/package.rb', line 103

def embeddings
  @embeddings ||= PartCollection.new(:target, Part)
end

#embeddings=(value) ⇒ Object

Bulk-assign embeddings (Hash of target => Part/binary; nil clears).



108
109
110
# File 'lib/uniword/docx/package.rb', line 108

def embeddings=(value)
  embeddings.replace_all(value)
end

#paragraphsObject

Delegate common DocumentRoot methods for API compatibility



289
290
291
# File 'lib/uniword/docx/package.rb', line 289

def paragraphs
  document&.paragraphs || []
end

#populate_allocatorObject

Populate the allocator from all existing template data. Must be called BEFORE any builder runs (populate-first principle).



200
201
202
# File 'lib/uniword/docx/package.rb', line 200

def populate_allocator
  @allocator = IdAllocator.populate_from_package(self)
end

#prepare_allocatorObject

Guarantee the single rId authority before reconciliation: reuse the package's allocator (loaded/builder-seeded) or start one, then seed it from the package's current relationships. Seeding preserves loaded rIds verbatim; earlier builder allocations keep their ids — a seeded rel whose id collides is reallocated deterministically (IdAllocator#seed_from_rels).



210
211
212
213
214
215
216
# File 'lib/uniword/docx/package.rb', line 210

def prepare_allocator
  self.allocator ||= IdAllocator.new
  allocator.seed_from_rels(package_rels&.relationships,
                           scope: :package)
  allocator.seed_from_rels(document_rels&.relationships)
  allocator
end

#raw_partsPartCollection

Raw passthrough parts no registry definition models, keyed by package path ("docProps/meta.xml", "word/glossary/document.xml", ...). Carried byte-for-byte from load to save — see PartLoader::RawPartLoader for claiming and PackageSerialization#serialize_raw_parts for emission.

Returns:



119
120
121
# File 'lib/uniword/docx/package.rb', line 119

def raw_parts
  @raw_parts ||= PartCollection.new(:path, RawPart)
end

#raw_parts=(value) ⇒ Object

Bulk-assign raw parts (Hash of path => RawPart/hash; nil clears).



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

def raw_parts=(value)
  raw_parts.replace_all(value)
end

#reorder_content_hash(content) ⇒ Object

Ensure [Content_Types].xml is first, _rels/.rels is second.



280
281
282
283
284
285
# File 'lib/uniword/docx/package.rb', line 280

def reorder_content_hash(content)
  priority = {}
  priority["[Content_Types].xml"] = content.delete("[Content_Types].xml") if content.key?("[Content_Types].xml")
  priority["_rels/.rels"] = content.delete("_rels/.rels") if content.key?("_rels/.rels")
  content.replace(priority.merge(content))
end

#styles_configurationObject



315
316
317
# File 'lib/uniword/docx/package.rb', line 315

def styles_configuration
  document&.styles_configuration
end

#tablesObject



293
294
295
# File 'lib/uniword/docx/package.rb', line 293

def tables
  document&.tables || []
end

#textObject



301
302
303
# File 'lib/uniword/docx/package.rb', line 301

def text
  document&.text || ""
end

#to_file(path, validate: nil) ⇒ void Also known as: save

This method returns an undefined value.

Save package to file

Parameters:

  • path (String)

    Output file path

  • validate (Boolean, nil) (defaults to: nil)

    Run the package integrity gate before writing; nil falls back to Uniword.configuration.validate_on_save

Raises:



226
227
228
229
230
# File 'lib/uniword/docx/package.rb', line 226

def to_file(path, validate: nil)
  zip_content = to_zip_content(validate: validate)
  packager = Infrastructure::ZipPackager.new
  packager.package(zip_content, path)
end

#to_zip_content(validate: nil) ⇒ Hash

Generate ZIP content hash

Runs the Reconciler (the only mutating pass), records its repair report on #applied_fixes, and — unless validation is disabled — refuses invalid output via the PackageIntegrityChecker gate.

Parameters:

  • validate (Boolean, nil) (defaults to: nil)

    Run the package integrity gate; nil falls back to Uniword.configuration.validate_on_save

Returns:

  • (Hash)

    File paths => content

Raises:



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/uniword/docx/package.rb', line 243

def to_zip_content(validate: nil)
  content = {}

  self.content_types ||= self.class.minimal_content_types
  self.package_rels ||= self.class.minimal_package_rels
  self.document_rels ||= self.class.minimal_document_rels

  self.settings ||= Uniword::Wordprocessingml::Settings.new
  self.font_table ||= Uniword::Wordprocessingml::FontTable.new
  self.web_settings ||= Uniword::Wordprocessingml::WebSettings.new

  # An allocator carried into the save (builder-managed document
  # or loaded package) selects light-touch repairs; documents
  # without one get the full normalization repertoire. rIds flow
  # through the allocator either way.
  builder_managed = !allocator.nil?
  prepare_allocator

  reconciler = Reconciler.new(self,
                              profile: profile || Profile.defaults,
                              allocator: allocator,
                              builder_managed: builder_managed)
  reconciler.reconcile
  @applied_fixes = reconciler.applied_fixes
  log_applied_fixes

  inject_part_relationships(content, content_types, package_rels, document_rels)
  serialize_package_parts(content, content_types, package_rels, document_rels)

  # OOXML requires [Content_Types].xml as the first ZIP entry.
  reorder_content_hash(content)

  enforce_package_integrity(content, validate)
  content
end