Class: Xlsxrb::Ooxml::ZipGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/xlsxrb/ooxml/zip_generator.rb,
sig/generated/xlsxrb/ooxml/zip_generator.rbs

Overview

Generates a simple ZIP file using only the standard library.

Instance Method Summary collapse

Constructor Details

#initialize(filepath) ⇒ ZipGenerator

Returns a new instance of ZipGenerator.

Parameters:

  • filepath (Object)


11
12
13
14
15
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 11

def initialize(filepath)
  @filepath = filepath
  @entries = []
  @file = nil
end

Instance Method Details

#add_entry(filepath, content) ⇒ Object

Adds an entry to the ZIP archive.

Parameters:

  • filepath (Object)
  • content (Object)

Returns:

  • (Object)


18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 18

def add_entry(filepath, content)
  compressed_data = compress(content)
  @entries << {
    path: filepath,
    content: content,
    compressed_data: compressed_data,
    crc32: crc32(content),
    uncompressed_size: content.bytesize,
    compressed_size: compressed_data.bytesize,
    mtime: Time.now
  }
end

#calculate_central_dir_sizeObject

Returns:

  • (Object)


154
155
156
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 154

def calculate_central_dir_size
  @entries.sum { |entry| 46 + entry[:path].bytesize }
end

#compress(content) ⇒ Object

Parameters:

  • content (Object)

Returns:

  • (Object)


127
128
129
130
131
132
133
134
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 127

def compress(content)
  deflater = Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -Zlib::MAX_WBITS)
  begin
    deflater.deflate(content, Zlib::FINISH)
  ensure
    deflater.close
  end
end

#crc32(content) ⇒ Object

Parameters:

  • content (Object)

Returns:

  • (Object)


136
137
138
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 136

def crc32(content)
  Zlib.crc32(content) & 0xFFFFFFFF
end

#dos_datetime(time) ⇒ Object

Parameters:

  • time (Object)

Returns:

  • (Object)


140
141
142
143
144
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 140

def dos_datetime(time)
  dos_date = ((time.year - 1980) << 9) | (time.month << 5) | time.day
  dos_time = (time.hour << 11) | (time.min << 5) | (time.sec / 2)
  le16(dos_time) + le16(dos_date)
end

#generateObject

Generates the ZIP file.

Returns:

  • (Object)


32
33
34
35
36
37
38
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 32

def generate
  File.open(@filepath, "wb") do |f|
    @file = f
    write_entries
    write_central_directory
  end
end

#le16(value) ⇒ Object

Parameters:

  • value (Object)

Returns:

  • (Object)


146
147
148
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 146

def le16(value)
  [value].pack("v").bytes
end

#le32(value) ⇒ Object

Parameters:

  • value (Object)

Returns:

  • (Object)


150
151
152
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 150

def le32(value)
  [value].pack("V").bytes
end

#write_central_directoryObject

Returns:

  • (Object)


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 78

def write_central_directory
  offset = @file.pos
  cd_offset = 0
  @entries.each do |entry|
    filename = entry[:path]
    filename_bytes = filename.bytes

    cd_header = []
    cd_header += [0x50, 0x4b, 0x01, 0x02] # Central directory file header signature
    cd_header += [0x14, 0x03]  # Version made by
    cd_header += [0x14, 0x00]  # Version needed to extract
    cd_header += [0x00, 0x00]  # General purpose bit flag
    cd_header += [0x08, 0x00]  # Compression method

    dos_time = dos_datetime(entry[:mtime])
    cd_header += dos_time

    cd_header += le32(entry[:crc32]) # CRC-32
    cd_header += le32(entry[:compressed_size]) # Compressed size
    cd_header += le32(entry[:uncompressed_size]) # Uncompressed size
    cd_header += le16(filename_bytes.length) # Filename length
    cd_header += le16(0)  # Extra field length
    cd_header += le16(0)  # File comment length
    cd_header += le16(0)  # Disk number start
    cd_header += le16(0)  # Internal file attributes
    cd_header += le32(0)  # External file attributes
    cd_header += le32(cd_offset) # Local header offset

    @file.write(cd_header.pack("C*"))
    @file.write(filename)

    # Advance offset past the local header and compressed data for this entry.
    cd_offset += 30 + filename_bytes.length + entry[:compressed_size]
  end

  # End of central directory record
  end_cd = []
  end_cd += [0x50, 0x4b, 0x05, 0x06] # End of central directory signature
  end_cd += le16(0)  # Disk number
  end_cd += le16(0)  # Disk with central directory
  end_cd += le16(@entries.length)  # Number of central directory records on this disk
  end_cd += le16(@entries.length)  # Total number of central directory records
  end_cd += le32(calculate_central_dir_size) # Size of central directory
  end_cd += le32(offset) # Offset of central directory
  end_cd += le16(0) # Comment length

  @file.write(end_cd.pack("C*"))
end

#write_entriesObject

Returns:

  • (Object)


42
43
44
45
46
47
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 42

def write_entries
  @entries.each do |entry|
    write_local_header(entry)
    write_file_data(entry)
  end
end

#write_file_data(entry) ⇒ Object

Parameters:

  • entry (Object)

Returns:

  • (Object)


74
75
76
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 74

def write_file_data(entry)
  @file.write(entry[:compressed_data])
end

#write_local_header(entry) ⇒ Object

Parameters:

  • entry (Object)

Returns:

  • (Object)


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/xlsxrb/ooxml/zip_generator.rb', line 49

def write_local_header(entry)
  filename = entry[:path]
  filename_bytes = filename.bytes

  # Local file header
  local_header = []
  local_header += [0x50, 0x4b, 0x03, 0x04] # Signature
  local_header += [0x14, 0x00]  # Version needed to extract
  local_header += [0x00, 0x00]  # General purpose bit flag
  local_header += [0x08, 0x00]  # Compression method (8 = deflate)

  # Last mod file time/date
  dos_time = dos_datetime(entry[:mtime])
  local_header += dos_time

  local_header += le32(entry[:crc32]) # CRC-32
  local_header += le32(entry[:compressed_size]) # Compressed size
  local_header += le32(entry[:uncompressed_size]) # Uncompressed size
  local_header += le16(filename_bytes.length) # Filename length
  local_header += le16(0) # Extra field length

  @file.write(local_header.pack("C*"))
  @file.write(filename)
end