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
123 124 125 126 127 128 129 130 131 132 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 123 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
173 174 175 176 177 178 179 180 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 173 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
136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 136 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
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 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 87 def finish_entry raise "No entry started" unless @current_entry entry = @current_entry @current_entry = nil # 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 69 70 71 72 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 53 def start_entry(path) raise "ZipWriter is closed" if @closed entry_offset = @bytes_written gp_flag = @seekable ? 0 : 0x0008 @current_entry = { path: path, offset: entry_offset, deflater: Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -Zlib::MAX_WBITS), crc: Zlib.crc32, uncompressed_size: 0, compressed_size: 0, gp_flag: gp_flag, seekable: @seekable } # Write local header (if unseekable, sizes/crc are 0 and bit 3 is set) write_local_header(path, 0, 0, 0, gp_flag: gp_flag) end |
#write_bytes(data) ⇒ Object
148 149 150 151 152 153 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 148 def write_bytes(data) return if data.nil? || data.empty? @io.write(data) @bytes_written += data.bytesize end |
#write_central_directory ⇒ Object
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 233 234 235 236 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 207 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
74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 74 def write_data(str) raise "No entry started" unless @current_entry bytes = str.b return if bytes.empty? @current_entry[:crc] = Zlib.crc32(bytes, @current_entry[:crc]) @current_entry[:uncompressed_size] += bytes.bytesize compressed = @current_entry[:deflater].deflate(bytes, Zlib::SYNC_FLUSH) @current_entry[:compressed_size] += compressed.bytesize write_bytes(compressed) end |
#write_end_of_central_directory(cd_offset, cd_size) ⇒ Object
238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 238 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
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 182 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
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/xlsxrb/ooxml/zip_writer.rb', line 155 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 |