diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6dcc0a3..0ba66cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 2.6.0 (2024-07-10)
+
+- Move to [ZipKit gem](http://rubygems.org/gems/zip_kit); equivalent of https://github.com/felixbuenemann/xlsxtream/pull/57/files
+
+## 2.5.0 (2021-06-28)
+
+- New `:add_header_row` method in Worksheet, which outputs as a row as bold text, intended for header rows
+
## 2.4.0 (2020-06-27)
- Allow writing worksheets without a block using add\_worksheet (#42, #45)
@@ -23,11 +31,11 @@
## 2.0.1 (2018-03-11)
- Rescue gracefully from invalid dates with auto-format (#22)
-- Remove unused ZipTricksFibers IO wrapper (#24)
+- Remove unused ZipKitFibers IO wrapper (#24)
## 2.0.0 (2017-10-31)
-- Replace RubyZip with ZipTricks as default compressor (#16)
+- Replace RubyZip with ZipKit as default compressor (#16)
- Drop support for Ruby < 2.1.0 (required for zip\_tricks gem)
- Deprecate :io\_wrapper option, you can now pass wrapper instances (#20)
diff --git a/README.md b/README.md
index 5475554..05ac496 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,12 @@ xlsx.write_worksheet 'AppendixSheet' do |sheet|
sheet.add_row [Time.now, 'Time-machine']
end
+# Output the first row as a header line using bold text
+xls.write_worksheet 'Sheet1' do |sheet|
+ sheet.add_header_row ['headers', 'in', 'bold']
+ sheet << ['first', 'normal', 'row']
+end
+
# If you have highly repetitive data, you can enable Shared String Tables (SST)
# for the workbook or a single worksheet. The SST has to be kept in memory,
# so do not use it if you have a huge amount of rows or a little duplication
diff --git a/lib/xlsxtream/header_row.rb b/lib/xlsxtream/header_row.rb
new file mode 100644
index 0000000..6166420
--- /dev/null
+++ b/lib/xlsxtream/header_row.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Xlsxtream
+ class HeaderRow < Row
+ def initialize(row, rownum, options = {})
+ super
+
+ @normal_style = ' s="3"'
+ @date_style = ' s="4"'
+ @time_style = ' s="5"'
+ end
+ end
+end
diff --git a/lib/xlsxtream/io/zip_kit.rb b/lib/xlsxtream/io/zip_kit.rb
new file mode 100644
index 0000000..a7d6236
--- /dev/null
+++ b/lib/xlsxtream/io/zip_kit.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+require "zip_kit"
+
+module Xlsxtream
+ module IO
+ class ZipKit
+ BUFFER_SIZE = 64 * 1024
+
+ def initialize(body)
+ @streamer = ::ZipKit::Streamer.new(body)
+ @wf = nil
+ @buffer = String.new
+ end
+
+ def <<(data)
+ @buffer << data
+ flush_buffer if @buffer.size >= BUFFER_SIZE
+ self
+ end
+
+ def add_file(path)
+ flush_file
+ @wf = @streamer.write_deflated_file(path)
+ end
+
+ def close
+ flush_file
+ @streamer.close
+ end
+
+ private
+
+ def flush_buffer
+ @wf << @buffer
+ @buffer.clear
+ end
+
+ def flush_file
+ return unless @wf
+ flush_buffer if @buffer.size > 0
+ @wf.close
+ end
+ end
+ end
+end
diff --git a/lib/xlsxtream/io/zip_tricks.rb b/lib/xlsxtream/io/zip_tricks.rb
index 3bf57d8..a7d6236 100644
--- a/lib/xlsxtream/io/zip_tricks.rb
+++ b/lib/xlsxtream/io/zip_tricks.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require "zip_tricks"
+require "zip_kit"
module Xlsxtream
module IO
- class ZipTricks
+ class ZipKit
BUFFER_SIZE = 64 * 1024
def initialize(body)
- @streamer = ::ZipTricks::Streamer.new(body)
+ @streamer = ::ZipKit::Streamer.new(body)
@wf = nil
@buffer = String.new
end
diff --git a/lib/xlsxtream/row.rb b/lib/xlsxtream/row.rb
index afba661..50d44dd 100644
--- a/lib/xlsxtream/row.rb
+++ b/lib/xlsxtream/row.rb
@@ -16,14 +16,15 @@ class Row
TRUE_STRING = 'true'.freeze
FALSE_STRING = 'false'.freeze
- DATE_STYLE = 1
- TIME_STYLE = 2
-
def initialize(row, rownum, options = {})
@row = row
@rownum = rownum
@sst = options[:sst]
@auto_format = options[:auto_format]
+
+ @normal_style = ''
+ @date_style = ' s="1"'
+ @time_style = ' s="2"'
end
def to_xml
@@ -40,15 +41,15 @@ def to_xml
case value
when Numeric
- xml << %Q{#{value}}
+ xml << %Q{#{value}}
when TrueClass, FalseClass
- xml << %Q{#{value ? 1 : 0}}
+ xml << %Q{#{value ? 1 : 0}}
when Time
- xml << %Q{#{time_to_oa_date(value)}}
+ xml << %Q{#{time_to_oa_date(value)}}
when DateTime
- xml << %Q{#{datetime_to_oa_date(value)}}
+ xml << %Q{#{datetime_to_oa_date(value)}}
when Date
- xml << %Q{#{date_to_oa_date(value)}}
+ xml << %Q{#{date_to_oa_date(value)}}
else
value = value.to_s
@@ -56,9 +57,9 @@ def to_xml
value = value.encode(ENCODING) if value.encoding != ENCODING
if @sst
- xml << %Q{#{@sst[value]}}
+ xml << %Q{#{@sst[value]}}
else
- xml << %Q{#{XML.escape_value(value)}}
+ xml << %Q{#{XML.escape_value(value)}}
end
end
end
diff --git a/lib/xlsxtream/workbook.rb b/lib/xlsxtream/workbook.rb
index 1fdb04b..aabcb0e 100644
--- a/lib/xlsxtream/workbook.rb
+++ b/lib/xlsxtream/workbook.rb
@@ -3,7 +3,7 @@
require "xlsxtream/xml"
require "xlsxtream/shared_string_table"
require "xlsxtream/worksheet"
-require "xlsxtream/io/zip_tricks"
+require "xlsxtream/io/zip_kit"
module Xlsxtream
class Workbook
@@ -46,13 +46,13 @@ def initialize(output, options = {})
end
if output.is_a?(String) || !output.respond_to?(:<<)
@file = File.open(output, 'wb')
- @io = IO::ZipTricks.new(@file)
+ @io = IO::ZipKit.new(@file)
elsif output.respond_to? :add_file
@file = nil
@io = output
else
@file = nil
- @io = IO::ZipTricks.new(output)
+ @io = IO::ZipKit.new(output)
end
@sst = SharedStringTable.new
@worksheets = []
@@ -162,12 +162,18 @@ def write_styles
-
+
+
+
+
+
+
+
@@ -183,10 +189,19 @@ def write_styles
-
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/xlsxtream/worksheet.rb b/lib/xlsxtream/worksheet.rb
index c4e8b41..9f10881 100644
--- a/lib/xlsxtream/worksheet.rb
+++ b/lib/xlsxtream/worksheet.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "xlsxtream/xml"
require "xlsxtream/row"
+require "xlsxtream/header_row"
module Xlsxtream
class Worksheet
@@ -19,6 +20,11 @@ def <<(row)
end
alias_method :add_row, :<<
+ def add_header_row(row)
+ @io << HeaderRow.new(row, @rownum, @options).to_xml
+ @rownum += 1
+ end
+
def close
write_footer
@closed = true
diff --git a/test/xlsxtream/header_row_test.rb b/test/xlsxtream/header_row_test.rb
new file mode 100644
index 0000000..6c7fb86
--- /dev/null
+++ b/test/xlsxtream/header_row_test.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+require 'test_helper'
+require 'xlsxtream/header_row'
+
+module Xlsxtream
+ class HeaderRowTest < Minitest::Test
+ def test_header_string_column
+ row = HeaderRow.new(['hello'], 1)
+ expected = 'hello
'
+ actual = row.to_xml
+ assert_equal expected, actual
+ end
+
+ def test_header_symbol_column
+ row = HeaderRow.new([:hello], 1)
+ expected = 'hello
'
+ actual = row.to_xml
+ assert_equal expected, actual
+ end
+
+ def test_header_boolean_column
+ row = HeaderRow.new([true], 1)
+ actual = row.to_xml
+ expected = '1
'
+ assert_equal expected, actual
+ row = HeaderRow.new([false], 1)
+ actual = row.to_xml
+ expected = '0
'
+ assert_equal expected, actual
+ end
+
+ def test_header_text_boolean_column
+ row = HeaderRow.new(['true'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '1
'
+ assert_equal expected, actual
+ row = HeaderRow.new(['false'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '0
'
+ assert_equal expected, actual
+ end
+
+ def test_header_integer_column
+ row = HeaderRow.new([1], 1)
+ actual = row.to_xml
+ expected = '1
'
+ assert_equal expected, actual
+ end
+
+ def test_header_text_integer_column
+ row = HeaderRow.new(['1'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '1
'
+ assert_equal expected, actual
+ end
+
+ def test_header_float_column
+ row = HeaderRow.new([1.5], 1)
+ actual = row.to_xml
+ expected = '1.5
'
+ assert_equal expected, actual
+ end
+
+ def test_header_text_float_column
+ row = HeaderRow.new(['1.5'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '1.5
'
+ assert_equal expected, actual
+ end
+
+ def test_header_date_column
+ row = HeaderRow.new([Date.new(1900, 1, 1)], 1)
+ actual = row.to_xml
+ expected = '2.0
'
+ assert_equal expected, actual
+ end
+
+ def test_header_text_date_column
+ row = HeaderRow.new(['1900-01-01'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '2.0
'
+ assert_equal expected, actual
+ end
+
+ def test_header_invalid_text_date_column
+ row = HeaderRow.new(['1900-02-29'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '1900-02-29
'
+ assert_equal expected, actual
+ end
+
+ def test_header_date_time_column
+ row = HeaderRow.new([DateTime.new(1900, 1, 1, 12, 0, 0, '+00:00')], 1)
+ actual = row.to_xml
+ expected = '2.5
'
+ assert_equal expected, actual
+ end
+
+ def test_header_text_date_time_column
+ candidates = [
+ '1900-01-01T12:00',
+ '1900-01-01T12:00Z',
+ '1900-01-01T12:00+00:00',
+ '1900-01-01T12:00:00+00:00',
+ '1900-01-01T12:00:00.000+00:00',
+ '1900-01-01T12:00:00.000000000Z'
+ ]
+ candidates.each do |timestamp|
+ row = HeaderRow.new([timestamp], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '2.5
'
+ assert_equal expected, actual
+ end
+ row = HeaderRow.new(['1900-01-01T12'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '2.5
'
+ refute_equal expected, actual
+ end
+
+ def test_header_invalid_text_date_time_column
+ row = HeaderRow.new(['1900-02-29T12:00'], 1, :auto_format => true)
+ actual = row.to_xml
+ expected = '1900-02-29T12:00
'
+ assert_equal expected, actual
+ end
+
+ def test_header_time_column
+ row = HeaderRow.new([Time.new(1900, 1, 1, 12, 0, 0, '+00:00')], 1)
+ actual = row.to_xml
+ expected = '2.5
'
+ assert_equal expected, actual
+ end
+
+ def test_header_string_column_with_shared_string_table
+ mock_sst = { 'hello' => 0 }
+ row = HeaderRow.new(['hello'], 1, :sst => mock_sst)
+ expected = '0
'
+ actual = row.to_xml
+ assert_equal expected, actual
+ end
+
+ def test_header_multiple_columns
+ row = HeaderRow.new(['foo', nil, 23], 1)
+ expected = 'foo23
'
+ actual = row.to_xml
+ assert_equal expected, actual
+ end
+ end
+end
diff --git a/test/xlsxtream/io/zip_tricks_test.rb b/test/xlsxtream/io/zip_tricks_test.rb
index 1aa63d3..3aab892 100644
--- a/test/xlsxtream/io/zip_tricks_test.rb
+++ b/test/xlsxtream/io/zip_tricks_test.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
require 'test_helper'
-require 'xlsxtream/io/zip_tricks'
+require 'xlsxtream/io/zip_kit'
require 'zip'
module Xlsxtream
- class ZipTricksTest < Minitest::Test
+ class ZipKitTest < Minitest::Test
def test_writes_of_multiple_files
zip_buf = Tempfile.new('ztio-test')
- io = Xlsxtream::IO::ZipTricks.new(zip_buf)
+ io = Xlsxtream::IO::ZipKit.new(zip_buf)
io.add_file("book1.xml")
io << ''
io.add_file("book2.xml")
diff --git a/test/xlsxtream/workbook_test.rb b/test/xlsxtream/workbook_test.rb
index a589cae..3516608 100644
--- a/test/xlsxtream/workbook_test.rb
+++ b/test/xlsxtream/workbook_test.rb
@@ -367,6 +367,29 @@ def test_add_columns_via_workbook_options_and_add_rows
assert_equal expected, actual
end
+ def test_add_header_row
+ iow_spy = io_wrapper_spy
+ Workbook.open(iow_spy) do |wb|
+ wb.write_worksheet(headers: ['foo']) do |ws|
+ ws << ['foo']
+ ws.add_header_row ['bar'] # It's usually used as first row, but doesn't have to be
+ ws.add_row ['baz']
+ end
+ end
+
+ expected = \
+ ''"\r\n" \
+ '' \
+ '' \
+ 'foo
' \
+ 'bar
' \
+ 'baz
' \
+ ''
+
+ actual = iow_spy['xl/worksheets/sheet1.xml']
+ assert_equal expected, actual
+ end
+
def test_styles_content
iow_spy = io_wrapper_spy
Workbook.open(iow_spy) {}
@@ -377,8 +400,14 @@ def test_styles_content
'' \
'' \
'' \
- '' \
+ '' \
+ '' \
+ '' \
+ '' \
+ '' \
+ '' \
'' \
+ '' \
'' \
'' \
'' \
@@ -398,10 +427,19 @@ def test_styles_content
'' \
'' \
'' \
- '' \
+ '' \
'' \
'' \
'' \
+ '' \
+ '' \
+ '' \
+ '' \
+ '' \
+ '' \
+ '' \
+ '' \
+ '' \
'' \
'' \
'' \
diff --git a/xlsxtream.gemspec b/xlsxtream.gemspec
index 9f1149b..66e7301 100644
--- a/xlsxtream.gemspec
+++ b/xlsxtream.gemspec
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 2.1.0"
- spec.add_dependency "zip_tricks", ">= 4.5", "< 6"
+ spec.add_dependency "zip_kit", ">= 6.0", "< 7"
spec.add_development_dependency "bundler", ">= 1.7", "< 3"
spec.add_development_dependency "rake"