Class: Uniword::Ooxml::DotxPackage

Inherits:
Lutaml::Model::Serializable
  • Object
show all
Defined in:
lib/uniword/ooxml/dotx_package.rb

Overview

DOTX/DOTM Package - Word Template formats

Represents .dotx (template) and .dotm (macro-enabled template) files. Structure is identical to DOCX but used as document templates. Templates often contain StyleSets and building blocks.

This is the CORRECT OOP approach:

  • ONE model class for the container
  • Each XML part is a proper model attribute
  • No serializer/deserializer anti-pattern

Examples:

Load template

package = DotxPackage.from_file('template.dotx')
package.core_properties.title = 'New Template Title'
package.to_file('output.dotx')

Direct Known Subclasses

StyleSetPackage

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#raw_document_xmlObject

Access raw document XML (for compatibility)



185
186
187
# File 'lib/uniword/ooxml/dotx_package.rb', line 185

def raw_document_xml
  @raw_document_xml
end

Class Method Details

.from_file(path) ⇒ Document

Load DOTX/DOTM package from file

Parameters:

  • path (String)

    Path to .dotx or .dotm file

Returns:

  • (Document)

    Loaded document (Generated::Wordprocessingml::DocumentRoot)



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/uniword/ooxml/dotx_package.rb', line 67

def self.from_file(path)
  # Extract ZIP content
  extractor = Infrastructure::ZipExtractor.new
  zip_content = extractor.extract(path)

  # Parse package with properties and theme
  package = from_zip_content(zip_content)

  # Parse main document XML using generated classes
  # Document is aliased in lib/uniword.rb to Generated::Wordprocessingml::DocumentRoot
  document = if package.raw_document_xml
               Uniword::Wordprocessingml::DocumentRoot.from_xml(package.raw_document_xml)
             else
               Uniword::Wordprocessingml::DocumentRoot.new
             end

  # Transfer properties from package to document
  document.core_properties = package.core_properties if package.core_properties
  document.app_properties = package.app_properties if package.app_properties
  document.theme = package.theme if package.theme

  # Transfer model-based configurations
  document.styles_configuration = package.styles_configuration if package.styles_configuration
  document.numbering_configuration = package.numbering_configuration if package.numbering_configuration
  document.settings = package.settings if package.settings
  document.font_table = package.font_table if package.font_table
  document.web_settings = package.web_settings if package.web_settings
  document.document_rels = package.document_rels if package.document_rels
  document.content_types = package.content_types if package.content_types
  document.package_rels = package.package_rels if package.package_rels
  document.footnotes = package.footnotes if package.footnotes
  document.endnotes = package.endnotes if package.endnotes

  document
end

.from_zip_content(zip_content) ⇒ DotxPackage

Create package from extracted ZIP content

Parameters:

  • zip_content (Hash)

    Extracted ZIP files

Returns:



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/uniword/ooxml/dotx_package.rb', line 107

def self.from_zip_content(zip_content)
  package = new

  # Parse lutaml-model files
  package.core_properties = CoreProperties.from_xml(zip_content["docProps/core.xml"]) if zip_content["docProps/core.xml"]

  package.app_properties = AppProperties.from_xml(zip_content["docProps/app.xml"]) if zip_content["docProps/app.xml"]

  package.theme = Uniword::Drawingml::Theme.from_xml(zip_content["word/theme/theme1.xml"]) if zip_content["word/theme/theme1.xml"]

  # Parse styles and numbering as models
  package.styles_configuration = Uniword::Wordprocessingml::StylesConfiguration.from_xml(zip_content["word/styles.xml"]) if zip_content["word/styles.xml"]

  package.numbering_configuration = Uniword::Wordprocessingml::NumberingConfiguration.from_xml(zip_content["word/numbering.xml"]) if zip_content["word/numbering.xml"]

  # Store raw document XML (will be parsed by DotxHandler)
  package.raw_document_xml = zip_content["word/document.xml"] if zip_content["word/document.xml"]

  # Parse settings
  if zip_content["word/settings.xml"]
    package.settings = Uniword::Wordprocessingml::Settings.from_xml(
      zip_content["word/settings.xml"],
    )
  end

  # Parse font table
  if zip_content["word/fontTable.xml"]
    package.font_table = Uniword::Wordprocessingml::FontTable.from_xml(
      zip_content["word/fontTable.xml"],
    )
  end

  # Parse web settings
  if zip_content["word/webSettings.xml"]
    package.web_settings = Uniword::Wordprocessingml::WebSettings.from_xml(
      zip_content["word/webSettings.xml"],
    )
  end

  # Parse content types
  if zip_content["[Content_Types].xml"]
    package.content_types = Uniword::ContentTypes::Types.from_xml(
      zip_content["[Content_Types].xml"],
    )
  end

  # Parse package relationships
  if zip_content["_rels/.rels"]
    package.package_rels = Uniword::Ooxml::Relationships::PackageRelationships.from_xml(
      zip_content["_rels/.rels"],
    )
  end

  # Parse document relationships
  if zip_content["word/_rels/document.xml.rels"]
    package.document_rels = Uniword::Ooxml::Relationships::PackageRelationships.from_xml(
      zip_content["word/_rels/document.xml.rels"],
    )
  end

  # Parse footnotes
  if zip_content["word/footnotes.xml"]
    package.footnotes = Uniword::Wordprocessingml::Footnotes.from_xml(
      zip_content["word/footnotes.xml"],
    )
  end

  # Parse endnotes
  if zip_content["word/endnotes.xml"]
    package.endnotes = Uniword::Wordprocessingml::Endnotes.from_xml(
      zip_content["word/endnotes.xml"],
    )
  end

  package
end

.supported_extensionsArray<String>

Get supported file extensions

Returns:

  • (Array<String>)

    Array of supported extensions



190
191
192
# File 'lib/uniword/ooxml/dotx_package.rb', line 190

def self.supported_extensions
  [".dotx", ".dotm"]
end

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

This method returns an undefined value.

Save document to file

Parameters:

  • document (Document)

    The document to save (Generated::Wordprocessingml::DocumentRoot)

  • path (String)

    Output path

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

    Unused (DOCX reconciliation profile)

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

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

Raises:



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/uniword/ooxml/dotx_package.rb', line 204

def self.to_file(document, path, profile: nil, validate: nil)
  # Create package
  package = new

  # Transfer properties to package
  package.core_properties = document.core_properties || CoreProperties.new
  package.app_properties = document.app_properties || AppProperties.new
  package.theme = document.theme

  # Transfer model-based configurations
  package.styles_configuration = document.styles_configuration
  package.numbering_configuration = document.numbering_configuration

  # Serialize main document
  package.raw_document_xml = document.to_xml(encoding: "UTF-8")

  # Generate ZIP content
  zip_content = package.to_zip_content

  # Add required OOXML infrastructure files
  add_required_files(zip_content)

  # Refuse invalid output before packaging
  package.enforce_package_integrity(zip_content, validate)

  # Package and save
  packager = Infrastructure::ZipPackager.new
  packager.package(zip_content, path)
end

Instance Method Details

#enforce_package_integrity(zip_content, validate) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Write-time integrity gate: refuse invalid package content.

DOTX is an OPC package like DOCX, so the same Docx::PackageIntegrityChecker invariants apply.

Parameters:

  • zip_content (Hash)

    File paths => content

  • validate (Boolean, nil)

    explicit override; nil reads policy

Raises:



261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/uniword/ooxml/dotx_package.rb', line 261

def enforce_package_integrity(zip_content, validate)
  validate = Uniword.configuration.validate_on_save if validate.nil?
  return unless validate

  issues = Docx::PackageIntegrityChecker.new.check(zip_content)
  return if issues.empty?

  raise Uniword::ValidationError.new(
    self,
    issues.map { |issue| "#{issue.code} (#{issue.part}): #{issue.message}" },
    issues: issues,
  )
end

#to_file(path, validate: nil) ⇒ void

This method returns an undefined value.

Save package to file

Parameters:

  • path (String)

    Output path

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

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

Raises:



242
243
244
245
246
247
248
249
# File 'lib/uniword/ooxml/dotx_package.rb', line 242

def to_file(path, validate: nil)
  zip_content = to_zip_content

  enforce_package_integrity(zip_content, validate)

  packager = Infrastructure::ZipPackager.new
  packager.package(zip_content, path)
end

#to_zip_contentHash

Generate ZIP content hash

Returns:

  • (Hash)

    File paths => content



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/uniword/ooxml/dotx_package.rb', line 278

def to_zip_content
  content = {}

  # Serialize lutaml-model files with prefix: false
  if core_properties
    content["docProps/core.xml"] =
      core_properties.to_xml(encoding: "UTF-8", prefix: false)
  end
  if app_properties
    content["docProps/app.xml"] =
      app_properties.to_xml(encoding: "UTF-8", prefix: false)
  end

  # Theme serialization (no raw XML fallback)
  if theme
    content["word/theme/theme1.xml"] =
      theme.to_xml(encoding: "UTF-8")
  end

  # Serialize model-based configurations
  if styles_configuration
    content["word/styles.xml"] =
      styles_configuration.to_xml(encoding: "UTF-8")
  end

  if numbering_configuration
    content["word/numbering.xml"] =
      numbering_configuration.to_xml(encoding: "UTF-8")
  end

  # Serialize main document (word/document.xml)
  if @document
    content["word/document.xml"] = @document.to_xml(encoding: "UTF-8")
  elsif @raw_document_xml
    # Fallback to raw XML if document wasn't parsed yet
    content["word/document.xml"] = @raw_document_xml
  end

  # Serialize settings
  if settings
    content["word/settings.xml"] =
      settings.to_xml(encoding: "UTF-8")
  end

  # Serialize font table
  if font_table
    content["word/fontTable.xml"] =
      font_table.to_xml(encoding: "UTF-8")
  end

  # Serialize web settings
  if web_settings
    content["word/webSettings.xml"] =
      web_settings.to_xml(encoding: "UTF-8")
  end

  # Serialize content types
  if content_types
    content["[Content_Types].xml"] =
      content_types.to_xml(declaration: true)
  end

  # Serialize package relationships
  if package_rels
    content["_rels/.rels"] =
      package_rels.to_xml(declaration: true)
  end

  # Serialize document relationships
  if document_rels
    content["word/_rels/document.xml.rels"] =
      document_rels.to_xml(declaration: true)
  end

  # Serialize footnotes
  if footnotes
    content["word/footnotes.xml"] =
      footnotes.to_xml(encoding: "UTF-8")
  end

  # Serialize endnotes
  if endnotes
    content["word/endnotes.xml"] =
      endnotes.to_xml(encoding: "UTF-8")
  end

  content
end