Class: Xlsxrb::Ooxml::ZipWriter
- Inherits:
-
Object
- Object
- Xlsxrb::Ooxml::ZipWriter
- Defined in:
- lib/xlsxrb/ooxml/zip_writer.rb,
sig/generated/xlsxrb/ooxml/zip_writer.rbs
Overview
Writes ZIP archives using only stdlib (zlib). Supports streaming: entries are written sequentially, central directory at close.
Class Method Summary collapse
Instance Method Summary collapse
-
#add_binary_entry(path, content) ⇒ Object
Add a file entry with binary content (no encoding conversion).
-
#add_entry(path, content) ⇒ Object
Add a file entry with string content.
- #close ⇒ Object
- #deflate(content) ⇒ Object
- #determine_seekable ⇒ Object
- #finish_entry ⇒ Object
-
#initialize(io) ⇒ ZipWriter
constructor
A new instance of ZipWriter.
-
#start_entry(path) ⇒ Object
Write a string directly into the current ZIP entry stream.
- #write_bytes(data) ⇒ Object
- #write_central_directory ⇒ Object
- #write_data(str) ⇒ Object
- #write_end_of_central_directory(cd_offset, cd_size) ⇒ Object
- #write_local_header(path, crc, compressed_size, uncompressed_size, gp_flag: 0, count_bytes: true) ⇒ Object
- #write_raw_entry(path, content_bytes) ⇒ Object
Constructor Details
#initialize(io) ⇒ ZipWriter
Returns a new instance of ZipWriter.
27 28 29 30 31 32 33 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 27 def initialize(io) @io = io @entries = [] @closed = false @bytes_written = 0 @seekable = determine_seekable end |
Class Method Details
.open(target, &block) ⇒ void
This method returns an undefined value.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 12 def self.open(target, &block) io = target.is_a?(String) ? File.open(target, "wb") : target writer = new(io) if block begin yield writer ensure writer.close io.close if target.is_a?(String) end else writer end end |
Instance Method Details
#add_binary_entry(path, content) ⇒ Object
Add a file entry with binary content (no encoding conversion).
44 45 46 47 48 49 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 44 def add_binary_entry(path, content) raise "ZipWriter is closed" if @closed content_bytes = content.b write_raw_entry(path, content_bytes) end |
#add_entry(path, content) ⇒ Object
Add a file entry with string content.
36 37 38 39 40 41 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 36 def add_entry(path, content) raise "ZipWriter is closed" if @closed content_bytes = content.encode("UTF-8").b write_raw_entry(path, content_bytes) end |
#close ⇒ Object
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 116 def close return if @closed finish_entry if @current_entry @closed = true cd_offset = @bytes_written cd_size = write_central_directory write_end_of_central_directory(cd_offset, cd_size) end |
#deflate(content) ⇒ Object
166 167 168 169 170 171 172 173 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 166 def deflate(content) deflater = Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -Zlib::MAX_WBITS) begin deflater.deflate(content, Zlib::FINISH) ensure deflater.close end end |
#determine_seekable ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 129 def determine_seekable return true if @io.is_a?(StringIO) return false unless @io.respond_to?(:seek) && @io.respond_to?(:tell) begin @io.tell true rescue SystemCallError, IOError false end end |
#finish_entry ⇒ Object
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 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 81 def finish_entry raise "No entry started" unless @has_current_entry @has_current_entry = false # Flush remaining deflate data remaining = @entry_deflater.finish @entry_compressed_size += remaining.bytesize write_bytes(remaining) @entry_deflater.close final_crc = @entry_crc & 0xFFFFFFFF if @entry_seekable # Patch the local header if seekable current_pos = @io.is_a?(StringIO) ? @io.pos : @io.tell @io.seek(@entry_offset) write_local_header(@entry_path, final_crc, @entry_compressed_size, @entry_uncompressed_size, gp_flag: 0, count_bytes: false) @io.seek(current_pos) else # Write Data Descriptor (signature 0x08074b50 + crc32 + comp_size + uncomp_size) descriptor = [0x08074B50, final_crc, @entry_compressed_size, @entry_uncompressed_size].pack("VVVV") write_bytes(descriptor) end @entries << { path: @entry_path, crc32: final_crc, compressed_size: @entry_compressed_size, uncompressed_size: @entry_uncompressed_size, offset: @entry_offset, gp_flag: @entry_gp_flag } end |
#start_entry(path) ⇒ Object
Write a string directly into the current ZIP entry stream. Use start_entry / write_data / finish_entry for true streaming.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 53 def start_entry(path) raise "ZipWriter is closed" if @closed @entry_path = path @entry_offset = @bytes_written @entry_gp_flag = @seekable ? 0 : 0x0008 @entry_deflater = Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -Zlib::MAX_WBITS) @entry_crc = Zlib.crc32 @entry_uncompressed_size = 0 @entry_compressed_size = 0 @entry_seekable = @seekable @has_current_entry = true # Write local header (if unseekable, sizes/crc are 0 and bit 3 is set) write_local_header(path, 0, 0, 0, gp_flag: @entry_gp_flag) end |
#write_bytes(data) ⇒ Object
141 142 143 144 145 146 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 141 def write_bytes(data) return if data.nil? || data.empty? @io.write(data) @bytes_written += data.bytesize end |
#write_central_directory ⇒ Object
200 201 202 203 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 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 200 def write_central_directory size = 0 @entries.each do |entry| name_bytes = entry[:path].encode("UTF-8").b header = [ 0x02014B50, # central directory file header signature 20, # version made by 20, # version needed entry[:gp_flag] || 0, # general purpose bit flag 8, # compression method 0, # last mod file time 33, # last mod file date entry[:crc32], entry[:compressed_size], entry[:uncompressed_size], name_bytes.bytesize, 0, # extra field length 0, # file comment length 0, # disk number start 0, # internal file attributes 0, # external file attributes entry[:offset] # relative offset of local header ].pack("VvvvvvvVVVvvvvvVV") write_bytes(header) write_bytes(name_bytes) size += header.bytesize + name_bytes.bytesize end size end |
#write_data(str) ⇒ Object
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 70 def write_data(str) raise "No entry started" unless @has_current_entry return if str.empty? @entry_crc = Zlib.crc32(str, @entry_crc) @entry_uncompressed_size += str.bytesize compressed = @entry_deflater.deflate(str) @entry_compressed_size += compressed.bytesize write_bytes(compressed) end |
#write_end_of_central_directory(cd_offset, cd_size) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 231 def write_end_of_central_directory(cd_offset, cd_size) eocd = [ 0x06054B50, # end of central dir signature 0, # disk number 0, # disk with central directory @entries.size, # entries on this disk @entries.size, # total entries cd_size, # size of central directory cd_offset, # offset of central directory 0 # comment length ].pack("VvvvvVVv") write_bytes(eocd) end |
#write_local_header(path, crc, compressed_size, uncompressed_size, gp_flag: 0, count_bytes: true) ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 175 def write_local_header(path, crc, compressed_size, uncompressed_size, gp_flag: 0, count_bytes: true) name_bytes = path.encode("UTF-8").b header = [ 0x04034B50, # local file header signature 20, # version needed (2.0) gp_flag, # general purpose bit flag (0x0008 if data descriptor follows) 8, # compression method (deflate) 0, # last mod file time 33, # last mod file date crc, compressed_size, uncompressed_size, name_bytes.bytesize, 0 # extra field length ].pack("VvvvvvVVVvv") if count_bytes write_bytes(header) write_bytes(name_bytes) else @io.write(header) @io.write(name_bytes) end end |
#write_raw_entry(path, content_bytes) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 148 def write_raw_entry(path, content_bytes) crc = Zlib.crc32(content_bytes) & 0xFFFFFFFF compressed = deflate(content_bytes) offset = @bytes_written write_local_header(path, crc, compressed.bytesize, content_bytes.bytesize, gp_flag: 0) write_bytes(compressed) @entries << { path: path, crc32: crc, compressed_size: compressed.bytesize, uncompressed_size: content_bytes.bytesize, offset: offset, gp_flag: 0 } end |