Class: XlsxKit::ZipReader

Inherits:
Object
  • Object
show all
Defined in:
lib/xlsxkit/zip_reader.rb

Overview

最小化 ZIP32 读取器,专为 XLSX 解包设计。

不依赖 rubyzip 等第三方 gem,仅支持 XLSX 中出现的两种压缩方式:

  • DEFLATE (method 8): 通用压缩
  • STORED (method 0): 无压缩

实现思路:

  1. 从文件末尾反向搜索 EOCD (End of Central Directory) 签名
  2. 解析中央目录,获取所有条目的元数据与 Local Header 偏移
  3. 按需读取单个条目:定位 Local Header → 跳过头部 → Zlib::Inflate 解压

与全量解包不同,本 reader 仅按 name 读取单个条目,避免将整个 ZIP 展开到内存或磁盘。

Constant Summary collapse

EOCD_SIG =

ZIP 格式签名(freeze 防止外部 << 修改)

"PK\x05\x06".b.freeze
CDENTRY_SIG =

End of Central Directory

"PK\x01\x02".b.freeze
LOCAL_SIG =

Central Directory File Header

"PK\x03\x04".b.freeze
STORED =

压缩方法

0
DEFLATE =
8

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io) ⇒ ZipReader

初始化 ZIP 读取器。

Parameters:

  • io (IO, String) —

    文件路径或已打开的 IO(必须可 seek)



52
53
54
55
# File 'lib/xlsxkit/zip_reader.rb', line 52

def initialize(io)
  @io = io.is_a?(String) ? File.open(io, 'rb') : io
  @entries = nil
end

Instance Attribute Details

#entries ⇒ Object (readonly)

解析中央目录,返回 => entry 的 Hash。 entry 为包含 :name, :method, :compressed_size, :uncompressed_size, :local_offset 的 Hash。



75
76
77
# File 'lib/xlsxkit/zip_reader.rb', line 75

def entries
  @entries
end

Class Method Details

.open(path) ⇒ Object

便捷类方法:打开文件并读取后关闭。



59
60
61
62
63
64
# File 'lib/xlsxkit/zip_reader.rb', line 59

def self.open(path)
  reader = new(path)
  yield reader
ensure
  reader&.close
end

Instance Method Details

#close ⇒ Object

关闭底层 IO(若由本类打开)。



68
69
70
# File 'lib/xlsxkit/zip_reader.rb', line 68

def close
  @io.close unless @io.closed?
end

#read_entry(name) ⇒ String?

读取指定条目的原始字节(已解压)。

Parameters:

  • name (String) —

    条目名(如 "xl/sharedStrings.xml")

Returns:

  • (String, nil) —

    二进制内容;条目不存在时返回 nil



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/xlsxkit/zip_reader.rb', line 119

def read_entry(name)
  entry = entries[name]
  return nil unless entry

  data_offset = resolve_data_offset(entry[:local_offset])
  @io.seek(data_offset)
  raw = @io.read(entry[:compressed_size])

  case entry[:method]
  when STORED
    raw
  when DEFLATE
    Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(raw)
  else
    raise "Unsupported compression method: #{entry[:method]}"
  end
end

#read_entry_utf8(name) ⇒ Object

读取指定条目并强制 UTF-8 编码(用于 XML 文本)。



139
140
141
142
143
# File 'lib/xlsxkit/zip_reader.rb', line 139

def read_entry_utf8(name)
  data = read_entry(name)
  return nil unless data
  data.force_encoding('UTF-8')
end