Class: Uniword::Ooxml::PackageFile

Inherits:
Object
  • Object
show all
Defined in:
lib/uniword/ooxml/package_file.rb

Overview

Abstract base class for Office Open XML package files

Provides common functionality for ZIP-based Office formats:

  • Extraction to temporary directory

  • Packaging from temporary directory to ZIP

  • File reading/writing within extracted package

  • Cleanup of temporary files

Subclasses must implement:

  • load_content: Parse package contents into domain model

  • save_content: Serialize domain model into package files

Examples:

Create a specialized package

class MyPackage < PackageFile
  def load_content
    extract
    data_xml = read_file('data/main.xml')
    @model = MyModel.from_xml(data_xml)
  end

  def save_content(model)
    @model = model
    write_file('data/main.xml', model.to_xml)
  end
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:) ⇒ PackageFile

Initialize package

Parameters:

  • path (String)

    Path to package file (.dotx, .thmx, etc.)



44
45
46
47
# File 'lib/uniword/ooxml/package_file.rb', line 44

def initialize(path:)
  @path = path
  @extracted_dir = nil
end

Instance Attribute Details

#extracted_dirObject (readonly)

Temporary extraction directory



39
40
41
# File 'lib/uniword/ooxml/package_file.rb', line 39

def extracted_dir
  @extracted_dir
end

#pathObject (readonly)

Package file path



36
37
38
# File 'lib/uniword/ooxml/package_file.rb', line 36

def path
  @path
end

Class Method Details

.create_empty_package_pathString

Create an empty package template

Subclasses may override to provide specific empty templates.

Returns:

  • (String)

    Path to empty package template

Raises:

  • (NotImplementedError)


224
225
226
227
# File 'lib/uniword/ooxml/package_file.rb', line 224

def self.create_empty_package_path
  raise NotImplementedError,
        "#{name} must implement create_empty_package_path or provide template_path"
end

.load_from_file(path) ⇒ Object

Convenience method: Load from file

Extracts package, loads content, and returns the loaded model. Does NOT clean up automatically - caller must call cleanup.

Parameters:

  • path (String)

    Path to package file

Returns:

  • (Object)

    Loaded domain model



193
194
195
196
# File 'lib/uniword/ooxml/package_file.rb', line 193

def self.load_from_file(path)
  package = new(path: path)
  package.load_content
end

.save_to_file(model, output_path, template_path: nil) ⇒ String

Convenience method: Save to file

Creates package from model, writes to file, and cleans up.

Parameters:

  • model (Object)

    Domain model to save

  • output_path (String)

    Path for output package file

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

    Optional template to base package on

Returns:

  • (String)

    Path to created package file



206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/uniword/ooxml/package_file.rb', line 206

def self.save_to_file(model, output_path, template_path: nil)
  # Determine source package
  source = template_path || create_empty_package_path

  package = new(path: source)
  package.extract
  package.save_content(model)
  package.package(output_path)
  package.cleanup

  output_path
end

Instance Method Details

#cleanupvoid

This method returns an undefined value.

Clean up temporary extraction directory

Removes the temporary directory and all its contents. Safe to call multiple times.



112
113
114
115
116
117
# File 'lib/uniword/ooxml/package_file.rb', line 112

def cleanup
  return unless @extracted_dir

  FileUtils.rm_rf(@extracted_dir)
  @extracted_dir = nil
end

#extractString

Extract package contents to temporary directory

Creates a temporary directory and extracts all ZIP contents. The extracted_dir attribute is set to the temp directory path.

Returns:

  • (String)

    Path to temporary extraction directory

Raises:

  • (ArgumentError)

    if file doesn’t exist or is invalid



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/uniword/ooxml/package_file.rb', line 56

def extract
  unless File.exist?(@path)
    raise ArgumentError,
          "File not found: #{@path}"
  end

  @extracted_dir = Dir.mktmpdir("uniword-package-")

  Zip::File.open(@path) do |zip_file|
    zip_file.each do |entry|
      target_path = File.join(@extracted_dir, entry.name)
      FileUtils.mkdir_p(File.dirname(target_path))
      entry.extract(target_path)
    end
  end

  @extracted_dir
end

#file_exists?(relative_path) ⇒ Boolean

Check if file exists in extracted package

Parameters:

  • relative_path (String)

    Path relative to package root

Returns:

  • (Boolean)

    true if file exists



152
153
154
155
156
157
# File 'lib/uniword/ooxml/package_file.rb', line 152

def file_exists?(relative_path)
  return false unless @extracted_dir

  full_path = File.join(@extracted_dir, relative_path)
  File.exist?(full_path)
end

#load_contentObject

Load package content into domain model

This is an abstract method that must be implemented by subclasses. Should extract the package and parse its contents into the appropriate domain model object.

Returns:

  • (Object)

    Domain model object (Theme, StyleSet, etc.)

Raises:

  • (NotImplementedError)

    if not overridden



167
168
169
170
# File 'lib/uniword/ooxml/package_file.rb', line 167

def load_content
  raise NotImplementedError,
        "#{self.class} must implement load_content"
end

#package(output_path) ⇒ String

Package temporary directory contents into ZIP file

Creates a new ZIP file at output_path containing all files from the extracted temporary directory.

Parameters:

  • output_path (String)

    Path for output package file

Returns:

  • (String)

    Path to created package file

Raises:

  • (RuntimeError)

    if extracted_dir is nil (must extract first)



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/uniword/ooxml/package_file.rb', line 83

def package(output_path)
  raise "Must extract before packaging" unless @extracted_dir

  # Ensure output directory exists
  FileUtils.mkdir_p(File.dirname(output_path))

  Zip::File.open(output_path, Zip::File::CREATE) do |zipfile|
    Dir.glob(File.join(@extracted_dir, "**", "*")).each do |file_path|
      next if File.directory?(file_path)

      # Calculate relative path within package
      relative_path = file_path.sub("#{@extracted_dir}/", "")

      # Skip if entry already exists (prevents duplicates)
      next if zipfile.find_entry(relative_path)

      zipfile.add(relative_path, file_path)
    end
  end

  output_path
end

#read_file(relative_path) ⇒ String

Read file from extracted package

Parameters:

  • relative_path (String)

    Path relative to package root

Returns:

  • (String)

    File contents

Raises:

  • (RuntimeError)

    if extracted_dir is nil (must extract first)

  • (Errno::ENOENT)

    if file doesn’t exist



125
126
127
128
129
130
# File 'lib/uniword/ooxml/package_file.rb', line 125

def read_file(relative_path)
  raise "Must extract before reading files" unless @extracted_dir

  full_path = File.join(@extracted_dir, relative_path)
  File.read(full_path)
end

#save_content(content) ⇒ void

This method returns an undefined value.

Save domain model content into package

This is an abstract method that must be implemented by subclasses. Should serialize the domain model and write necessary files to the extracted directory.

Parameters:

  • content (Object)

    Domain model object to save

Raises:

  • (NotImplementedError)

    if not overridden



181
182
183
184
# File 'lib/uniword/ooxml/package_file.rb', line 181

def save_content(content)
  raise NotImplementedError,
        "#{self.class} must implement save_content(content)"
end

#write_file(relative_path, content) ⇒ void

This method returns an undefined value.

Write file to extracted package

Creates parent directories if needed.

Parameters:

  • relative_path (String)

    Path relative to package root

  • content (String)

    File contents to write

Raises:

  • (RuntimeError)

    if extracted_dir is nil (must extract first)



140
141
142
143
144
145
146
# File 'lib/uniword/ooxml/package_file.rb', line 140

def write_file(relative_path, content)
  raise "Must extract before writing files" unless @extracted_dir

  full_path = File.join(@extracted_dir, relative_path)
  FileUtils.mkdir_p(File.dirname(full_path))
  File.write(full_path, content)
end