xlsxtream 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af09aea4670bc6bea0e57545d023cda25571ed8e
4
- data.tar.gz: fd8bbcf0d5d53f0ccb951a4f42c3507bf341d2f3
3
+ metadata.gz: 9f6d507fcaf88af59b20094f992ba4e921ca610b
4
+ data.tar.gz: 81f6f7d9a0312be08a7e394ef2e27be1f5f51a09
5
5
  SHA512:
6
- metadata.gz: 2f4da6bd0909431125db97e6c7a5f0126d7af9e836e52439a8e81505c135c3aeba5e94e3cd779278cfcefc1d59a6098783b05c7b85df64f9ed41651a28afe7a5
7
- data.tar.gz: 70d5e4294027c6567c8d22a5a9d72ca8d58019a2b40aeb0ebba6b145f69a1d371f0d5a07aae26d44192207d3970105c211e5aed725ca51c22b7a63979c7a8630
6
+ metadata.gz: 2890495ed08670099470e514836ee399481bdd261e12207a3df454be1e4e7ef6a8489e2b2b662f7cb3286dc5cb82af7c5fa5354b4e801847a95b0307812e008e
7
+ data.tar.gz: 19f3812cdb16465b87e86c1aa2ff78605092729cd1fdbd767ae64abe3ba4cbea57d1ad63e5b1760c4a2bf5015a1629a5cb3834c9cafee6c33836a058f004a803
data/.travis.yml CHANGED
@@ -1,4 +1,15 @@
1
1
  language: ruby
2
+
3
+ sudo: false
4
+ cache: bundler
5
+
6
+ before_install: gem install bundler -v '~> 1.15' --conservative --minimal-deps
7
+
2
8
  rvm:
3
- - 2.2.3
4
- before_install: gem install bundler -v 1.10.6
9
+ - 1.9.2
10
+ - 1.9.3
11
+ - 2.0.0
12
+ - 2.1.10
13
+ - 2.2.7
14
+ - 2.3.4
15
+ - 2.4.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 (2017-07-12)
4
+
5
+ - Add support for auto-formatting
6
+
7
+ ## 0.2.0 (2017-02-20)
8
+
9
+ - Ruby 2.4 compatibility
10
+ - Misc bug fixes
11
+
12
+ ## 0.1.0 (2015-10-17)
13
+
14
+ - Initial release
data/README.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # Xlsxtream
2
2
 
3
- Xlsxtream is a streaming writer for XLSX spreadsheets. It supports multiple worksheets and optional string deduplication via a shared string table (SST). Its purpose is to replace CSV for large exports, because using CSV in Excel is very buggy and error prone. It's very efficient and can quickly write millions of rows with low memory usage.
3
+ Xlsxtream is a streaming writer for XLSX spreadsheets. It supports multiple worksheets and optional string
4
+ deduplication via a shared string table (SST). Its purpose is to replace CSV for large exports, because using
5
+ CSV in Excel is very buggy and error prone. It's very efficient and can quickly write millions of rows with
6
+ low memory usage.
4
7
 
5
- Xlsxtream does not support formatting, charts, comments and a myriad of other [OOXML](https://en.wikipedia.org/wiki/Office_Open_XML) features. If you are looking for a fully featured solution take a look at [axslx](https://github.com/randym/axlsx).
8
+ Xlsxtream does not support formatting, charts, comments and a myriad of
9
+ other [OOXML](https://en.wikipedia.org/wiki/Office_Open_XML) features. If you are looking for a
10
+ fully featured solution take a look at [axslx](https://github.com/randym/axlsx).
6
11
 
7
12
  Xlsxtream supports writing to files or IO-like objects, data is flushed as the ZIP compressor sees fit.
8
13
 
@@ -25,38 +30,51 @@ Or install it yourself as:
25
30
  ## Usage
26
31
 
27
32
  ```ruby
28
- # Creates a new workbook and closes it at the end of the block.
29
- Xlsxtream::Workbook.open("foo.xlsx") do |xlsx|
30
- xlsx.write_worksheet "Sheet1" do |sheet|
33
+ # Creates a new workbook and closes it at the end of the block
34
+ Xlsxtream::Workbook.open('my_data.xlsx') do |xlsx|
35
+ xlsx.write_worksheet 'Sheet1' do |sheet|
31
36
  # Date, Time, DateTime and Numeric are properly mapped
32
- sheet << [Date.today, "hello", "world", 42, 3.14159265359, 42**13]
37
+ sheet << [Date.today, 'hello', 'world', 42, 3.14159265359, 42**13]
33
38
  end
34
39
  end
35
40
 
36
- io = StringIO.new('')
41
+ io = StringIO.new
37
42
  xlsx = Xlsxtream::Workbook.new(io)
38
- xlsx.write_worksheet "Sheet1" do |sheet|
39
- # Number of columns doesn't have to match
40
- sheet << %w[first row]
41
- sheet << %w[second row with more colums]
43
+
44
+ # Number of columns doesn't have to match
45
+ xlsx.write_worksheet 'Sheet1' do |sheet|
46
+ sheet << ['first', 'row']
47
+ sheet << ['second', 'row', 'with', 'more colums']
42
48
  end
43
- # Write multiple worksheets with custom names:
44
- xlsx.write_worksheet "Foo & Bar" do |sheet|
45
- sheet.add_row ["Timestamp", "Comment"]
46
- sheet.add_row [Time.now, "Foo"]
47
- sheet.add_row [Time.now, "Bar"]
49
+
50
+ # Write multiple worksheets with custom names
51
+ xlsx.write_worksheet 'AppendixSheet' do |sheet|
52
+ sheet.add_row ['Timestamp', 'Comment']
53
+ sheet.add_row [Time.now, 'Good times']
54
+ sheet.add_row [Time.now, 'Time-machine']
48
55
  end
49
- # If you have highly repetitive data, you can enable Shared
50
- # String Tables (SST) for the workbook or a single worksheet.
51
- # The SST has to be kept in memory, so don't use it if you
52
- # have a huge amount of rows or a little duplication of content
53
- # accros cells. A single SST is used across the whole workbook.
54
- xlsx.write_worksheet("SST", use_shared_strings: true) do |sheet|
55
- sheet << %w[the same old story]
56
- sheet << %w[the old same story]
57
- sheet << %w[old, the same story]
56
+
57
+ # If you have highly repetitive data, you can enable Shared String Tables (SST)
58
+ # for the workbook or a single worksheet. The SST has to be kept in memory,
59
+ # so do not use it if you have a huge amount of rows or a little duplication
60
+ # of content across cells. A single SST is used for the whole workbook.
61
+ xlsx.write_worksheet('SheetWithSST', :use_shared_strings => true) do |sheet|
62
+ sheet << ['the', 'same', 'old', 'story']
63
+ sheet << ['the', 'old', 'same', 'story']
64
+ sheet << ['old', 'the', 'same', 'story']
58
65
  end
59
- # Writes metadata and ZIP archive central directory.
66
+
67
+ # Strings in numeric or date/time format can be auto-detected and formatted
68
+ # appropriately. This is a convenient way to avoid an Excel-warning about
69
+ # "Number stored as text". Dates and times must be in the ISO-8601 format and
70
+ # numeric values must contain only numbers and an optional decimal separator.
71
+ xlsx.write_worksheet('SheetWithAutoFormat', :auto_format => true) do |sheet|
72
+ # these two rows will be identical in the xlsx-output
73
+ sheet << [11.85, DateTime.parse('2050-01-01T12:00'), Date.parse('1984-01-01')]
74
+ sheet << ['11.85', '2050-01-01T12:00', '1984-01-01']
75
+ end
76
+
77
+ # Writes metadata and ZIP archive central directory
60
78
  xlsx.close
61
79
  ```
62
80
 
@@ -74,4 +92,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/felixb
74
92
  ## License
75
93
 
76
94
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
77
-
@@ -25,7 +25,8 @@ module Xlsxtream
25
25
 
26
26
  # Extend get_compressor to hook our custom deflater.
27
27
  class UnbufferedZipOutputStream < ::Zip::OutputStream
28
- private def get_compressor(entry, level)
28
+ private
29
+ def get_compressor(entry, level)
29
30
  case entry.compression_method
30
31
  when ::Zip::Entry::DEFLATED then
31
32
  StreamingDeflater.new(@output_stream, level, @encrypter)
data/lib/xlsxtream/row.rb CHANGED
@@ -4,45 +4,82 @@ require "xlsxtream/xml"
4
4
 
5
5
  module Xlsxtream
6
6
  class Row
7
- def initialize(row, rownum, sst = nil)
7
+
8
+ ENCODING = Encoding.find('UTF-8')
9
+
10
+ NUMBER_PATTERN = /\A-?[0-9]+(\.[0-9]+)?\z/.freeze
11
+ # ISO 8601 yyyy-mm-dd
12
+ DATE_PATTERN = /\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z/.freeze
13
+ # ISO 8601 yyyy-mm-ddThh:mm:ss(.s)(Z|+hh:mm|-hh:mm)
14
+ TIME_PATTERN = /\A[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(?::[0-9]{2}(?:\.[0-9]{1,9})?)?(?:Z|[+-][0-9]{2}:[0-9]{2})?\z/.freeze
15
+
16
+ DATE_STYLE = 1
17
+ TIME_STYLE = 2
18
+
19
+ def initialize(row, rownum, options = {})
8
20
  @row = row
9
21
  @rownum = rownum
10
- @sst = sst
11
- @encoding = Encoding.find("UTF-8")
22
+ @sst = options[:sst]
23
+ @auto_format = options[:auto_format]
12
24
  end
13
25
 
14
26
  def to_xml
15
27
  column = 'A'
16
- @row.reduce(%'<row r="#@rownum">') do |xml, value|
17
- cid = "#{column}#@rownum"
28
+ xml = %Q{<row r="#{@rownum}">}
29
+
30
+ @row.each do |value|
31
+ cid = "#{column}#{@rownum}"
18
32
  column.next!
19
- xml << case value
33
+
34
+ if @auto_format && value.is_a?(String)
35
+ value = auto_format(value)
36
+ end
37
+
38
+ case value
20
39
  when Numeric
21
- %'<c r="#{cid}" t="n"><v>#{value}</v></c>'
22
- when Date, Time, DateTime
23
- style = value.is_a?(Date) ? 1 : 2
24
- %'<c r="#{cid}" s="#{style}"><v>#{time_to_oa_date value}</v></c>'
40
+ xml << %Q{<c r="#{cid}" t="n"><v>#{value}</v></c>}
41
+ when Time, DateTime
42
+ xml << %Q{<c r="#{cid}" s="#{TIME_STYLE}"><v>#{time_to_oa_date(value)}</v></c>}
43
+ when Date
44
+ xml << %Q{<c r="#{cid}" s="#{DATE_STYLE}"><v>#{time_to_oa_date(value)}</v></c>}
25
45
  else
26
- value = value.to_s unless value.is_a? String
27
- if value.empty?
28
- ''
29
- else
30
- value = value.encode(@encoding) if value.encoding != @encoding
46
+ value = value.to_s
47
+
48
+ unless value.empty? # no xml output for for empty strings
49
+ value = value.encode(ENCODING) if value.encoding != ENCODING
50
+
31
51
  if @sst
32
- %'<c r="#{cid}" t="s"><v>#{@sst[value]}</v></c>'
52
+ xml << %Q{<c r="#{cid}" t="s"><v>#{@sst[value]}</v></c>}
33
53
  else
34
- %'<c r="#{cid}" t="inlineStr"><is><t>#{XML.escape_value value}</t></is></c>'
54
+ xml << %Q{<c r="#{cid}" t="inlineStr"><is><t>#{XML.escape_value(value)}</t></is></c>}
35
55
  end
36
56
  end
37
57
  end
38
- end << '</row>'
58
+ end
59
+
60
+ xml << '</row>'
39
61
  end
40
62
 
41
63
  private
42
64
 
65
+ # Detects and casts numbers, date, time in text
66
+ def auto_format(value)
67
+ case value
68
+ when NUMBER_PATTERN
69
+ value.include?('.') ? value.to_f : value.to_i
70
+ when DATE_PATTERN
71
+ Date.parse(value)
72
+ when TIME_PATTERN
73
+ DateTime.parse(value)
74
+ else
75
+ value
76
+ end
77
+ end
78
+
43
79
  # Converts Time objects to OLE Automation Date
44
80
  def time_to_oa_date(time)
45
- time = time.respond_to?(:to_time) ? time.to_time : time
81
+ time = time.to_time if time.respond_to?(:to_time)
82
+
46
83
  # Local dates are stored as UTC by truncating the offset:
47
84
  # 1970-01-01 00:00:00 +0200 => 1970-01-01 00:00:00 UTC
48
85
  # This is done because SpreadsheetML is not timezone aware.
@@ -1,3 +1,3 @@
1
1
  module Xlsxtream
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -37,12 +37,17 @@ module Xlsxtream
37
37
 
38
38
  def write_worksheet(name = nil, options = {})
39
39
  use_sst = options.fetch(:use_shared_strings, @options[:use_shared_strings])
40
+ auto_format = options.fetch(:auto_format, @options[:auto_format])
41
+ sst = use_sst ? @sst : nil
42
+
40
43
  name ||= "Sheet#{@worksheets.size + 1}"
41
44
  sheet_id = @worksheets[name]
42
45
  @io.add_file "xl/worksheets/sheet#{sheet_id}.xml"
43
- worksheet = Worksheet.new(@io, use_sst ? @sst : nil)
46
+
47
+ worksheet = Worksheet.new(@io, :sst => sst, :auto_format => auto_format)
44
48
  yield worksheet if block_given?
45
49
  worksheet.close
50
+
46
51
  nil
47
52
  end
48
53
  alias_method :add_worksheet, :write_worksheet
@@ -4,15 +4,16 @@ require "xlsxtream/row"
4
4
 
5
5
  module Xlsxtream
6
6
  class Worksheet
7
- def initialize(io, sst = nil)
7
+ def initialize(io, options = {})
8
8
  @io = io
9
9
  @rownum = 1
10
- @sst = sst
10
+ @options = options
11
+
11
12
  write_header
12
13
  end
13
14
 
14
15
  def <<(row)
15
- @io << Row.new(row, @rownum, @sst).to_xml
16
+ @io << Row.new(row, @rownum, @options).to_xml
16
17
  @rownum += 1
17
18
  end
18
19
  alias_method :add_row, :<<
data/xlsxtream.gemspec CHANGED
@@ -18,10 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.bindir = "exe"
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
+ spec.required_ruby_version = '>= 1.9.1'
21
22
 
22
23
  spec.add_dependency "rubyzip", ">= 1.0.0"
23
24
 
24
- spec.add_development_dependency "bundler", "~> 1.10"
25
+ spec.add_development_dependency "bundler", "~> 1.7"
25
26
  spec.add_development_dependency "rake", "~> 10.0"
26
27
  spec.add_development_dependency "minitest"
27
28
  spec.add_development_dependency "pry"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xlsxtream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felix Bünemann
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-20 00:00:00.000000000 Z
11
+ date: 2017-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.10'
33
+ version: '1.7'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.10'
40
+ version: '1.7'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -90,6 +90,7 @@ extra_rdoc_files: []
90
90
  files:
91
91
  - ".gitignore"
92
92
  - ".travis.yml"
93
+ - CHANGELOG.md
93
94
  - Gemfile
94
95
  - LICENSE.txt
95
96
  - README.md
@@ -119,7 +120,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
120
  requirements:
120
121
  - - ">="
121
122
  - !ruby/object:Gem::Version
122
- version: '0'
123
+ version: 1.9.1
123
124
  required_rubygems_version: !ruby/object:Gem::Requirement
124
125
  requirements:
125
126
  - - ">="
@@ -127,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
128
  version: '0'
128
129
  requirements: []
129
130
  rubyforge_project:
130
- rubygems_version: 2.5.2
131
+ rubygems_version: 2.6.13
131
132
  signing_key:
132
133
  specification_version: 4
133
134
  summary: Xlsxtream is a streaming XLSX spreadsheet writer