simple_xlsx_writer 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README +44 -0
- data/Rakefile +40 -0
- data/lib/simple_xlsx.rb +11 -0
- data/lib/simple_xlsx/document.rb +21 -0
- data/lib/simple_xlsx/monkey_patches_for_true_zip_stream.rb +61 -0
- data/lib/simple_xlsx/serializer.rb +196 -0
- data/lib/simple_xlsx/sheet.rb +83 -0
- data/lib/simple_xlsx/xml_escape.rb +6 -0
- data/test/simple_xlsx/document_test.rb +24 -0
- data/test/simple_xlsx/sheet_test.rb +81 -0
- data/test/simple_xlsx_test.rb +29 -0
- data/test/test_helper.rb +8 -0
- metadata +113 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Dee Zsombor (zsombor@primalgrasp.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
** Description **
|
2
|
+
|
3
|
+
|
4
|
+
This is a simple no fuss generator for OpenXML aka XLSX files. No
|
5
|
+
formatting, styles just raw content with a few basic datatypes
|
6
|
+
supported. Produced output is tested to be compatible with
|
7
|
+
|
8
|
+
- Open Office 3.2 series (Linux, Mac, Windows)
|
9
|
+
- Neo Office 3.2 series (Mac)
|
10
|
+
- Microsoft Office 2007 (Windows)
|
11
|
+
- Microsoft Office 2010 (Windows)
|
12
|
+
- Microsoft Office 2008 for Mac (versions 12.2.5 or above)
|
13
|
+
- Microsoft Excel Viewer (Windows)
|
14
|
+
|
15
|
+
Numbers of iWork '09 does not appear to support the inline string
|
16
|
+
storage model prefered by this gem. Apple may release a fix for this
|
17
|
+
eventually, I have avoided the more common shared string table
|
18
|
+
method as it cannot be implemented in linear time.
|
19
|
+
|
20
|
+
|
21
|
+
** Sample **
|
22
|
+
|
23
|
+
|
24
|
+
serializer = SimpleXlsx::Serializer.new("test.xlsx") do |doc|
|
25
|
+
doc.add_sheet("People") do |sheet|
|
26
|
+
sheet.add_row(%w{DoB Name Occupation})
|
27
|
+
sheet.add_row([Date.parse("July 31, 1912"),
|
28
|
+
"Milton Friedman",
|
29
|
+
"Economist / Statistician"])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
** License **
|
35
|
+
|
36
|
+
|
37
|
+
See attached LICENSE for details.
|
38
|
+
|
39
|
+
|
40
|
+
** Credits **
|
41
|
+
|
42
|
+
|
43
|
+
Written by Dee Zsombor: http://primalgrasp.com
|
44
|
+
Funded by Harvest: http://www.getharvest.com
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
|
6
|
+
task :default => [:test]
|
7
|
+
|
8
|
+
Rake::TestTask.new do |test|
|
9
|
+
test.libs << "test"
|
10
|
+
test.test_files = Dir['test/**/*_test.rb'].sort
|
11
|
+
test.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "generate tags for emacs"
|
15
|
+
task :tags do
|
16
|
+
sh "ctags -Re lib/ "
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
spec = Gem::Specification.new do |s|
|
21
|
+
s.name = "simple_xlsx_writer"
|
22
|
+
s.version = "0.5.3"
|
23
|
+
s.author = "Dee Zsombor"
|
24
|
+
s.email = "zsombor@primalgrasp.com"
|
25
|
+
s.homepage = "http://simplxlsxwriter.rubyforge.org"
|
26
|
+
s.rubyforge_project = "simple_xlsx_writer"
|
27
|
+
s.platform = Gem::Platform::RUBY
|
28
|
+
s.summary = "Just as the name says, simple writter for Office 2007+ Excel files"
|
29
|
+
s.files = [FileList["{bin,lib}/**/*"].to_a, "LICENSE", "Rakefile"].flatten
|
30
|
+
s.require_path = "lib"
|
31
|
+
s.test_files = [FileList["{test}/**/*test.rb"].to_a, "test/test_helper.rb"].flatten
|
32
|
+
s.has_rdoc = true
|
33
|
+
s.extra_rdoc_files = ["README"]
|
34
|
+
s.add_dependency("rubyzip", ">= 0.9.4")
|
35
|
+
s.add_dependency("fast_xs", ">= 0.7.3")
|
36
|
+
end
|
37
|
+
|
38
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
39
|
+
pkg.need_tar = true
|
40
|
+
end
|
data/lib/simple_xlsx.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__))
|
5
|
+
require 'simple_xlsx/xml_escape'
|
6
|
+
require 'simple_xlsx/monkey_patches_for_true_zip_stream'
|
7
|
+
require 'simple_xlsx/serializer'
|
8
|
+
require 'simple_xlsx/document'
|
9
|
+
require 'simple_xlsx/sheet'
|
10
|
+
|
11
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleXlsx
|
2
|
+
class Document
|
3
|
+
def initialize(io)
|
4
|
+
@sheets = []
|
5
|
+
@io = io
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :sheets
|
9
|
+
|
10
|
+
def add_sheet name, &block
|
11
|
+
@io.open_stream_for_sheet(@sheets.size) do |stream|
|
12
|
+
@sheets << Sheet.new(self, name, stream, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_shared_strings?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'zip/zip' #dep
|
2
|
+
|
3
|
+
__END__
|
4
|
+
|
5
|
+
module Zip
|
6
|
+
class ZipOutputStream
|
7
|
+
def initialize(fileName)
|
8
|
+
super()
|
9
|
+
if fileName.is_a?(String) && !fileName.empty?
|
10
|
+
@fileName = fileName
|
11
|
+
@outputStream = File.new(@fileName, "wb")
|
12
|
+
else
|
13
|
+
@outputStream = fileName
|
14
|
+
@fileName = ''
|
15
|
+
end
|
16
|
+
@entrySet = ZipEntrySet.new
|
17
|
+
@compressor = NullCompressor.instance
|
18
|
+
@closed = false
|
19
|
+
@currentEntry = nil
|
20
|
+
@comment = nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ZipFile < ZipCentralDirectory
|
25
|
+
def initialize(stream, create = nil)
|
26
|
+
super()
|
27
|
+
@name = stream.is_a?(String) ? stream : ''
|
28
|
+
@comment = ""
|
29
|
+
if stream.is_a?(String) && File.exists?(stream)
|
30
|
+
File.open(name, "rb") { |f| read_from_stream(f) }
|
31
|
+
elsif (create)
|
32
|
+
@entrySet = ZipEntrySet.new
|
33
|
+
elsif !stream.is_a?(String) && !create && !stream.respond_to(:path)
|
34
|
+
# do nothing here
|
35
|
+
elsif !stream.is_a?(String) && !create
|
36
|
+
File.open(stream.path, "rb") { |f| read_from_stream(f) }
|
37
|
+
else
|
38
|
+
raise ZipError, "File #{stream} not found"
|
39
|
+
end
|
40
|
+
@create = create
|
41
|
+
@storedEntries = @entrySet.dup
|
42
|
+
|
43
|
+
@restore_ownership = false
|
44
|
+
@restore_permissions = false
|
45
|
+
@restore_times = true
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_success_replace arg
|
49
|
+
if arg.is_a?(String) && !arg.empty?
|
50
|
+
tmpfile = get_tempfile
|
51
|
+
tmpFilename = tmpfile.path
|
52
|
+
tmpfile.close
|
53
|
+
if yield tmpFilename
|
54
|
+
File.rename(tmpFilename, name)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
yield arg
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module SimpleXlsx
|
2
|
+
|
3
|
+
class Serializer
|
4
|
+
|
5
|
+
def initialize to
|
6
|
+
@to = to
|
7
|
+
Zip::ZipFile.open(to, Zip::ZipFile::CREATE) do |zip|
|
8
|
+
@zip = zip
|
9
|
+
add_doc_props
|
10
|
+
add_worksheets_directory
|
11
|
+
add_relationship_part
|
12
|
+
add_styles
|
13
|
+
@doc = Document.new(self)
|
14
|
+
yield @doc
|
15
|
+
add_workbook_relationship_part
|
16
|
+
add_content_types
|
17
|
+
add_workbook_part
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_workbook_part
|
22
|
+
@zip.get_output_stream "xl/workbook.xml" do |f|
|
23
|
+
f.puts <<-ends
|
24
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
25
|
+
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
26
|
+
<workbookPr date1904="0" />
|
27
|
+
<sheets>
|
28
|
+
ends
|
29
|
+
@doc.sheets.each_with_index do |sheet, ndx|
|
30
|
+
f.puts "<sheet name=\"#{sheet.name}\" sheetId=\"#{ndx + 1}\" r:id=\"#{sheet.rid}\"/>"
|
31
|
+
end
|
32
|
+
f.puts "</sheets></workbook>"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_worksheets_directory
|
37
|
+
@zip.mkdir "xl"
|
38
|
+
@zip.mkdir "xl/worksheets"
|
39
|
+
end
|
40
|
+
|
41
|
+
def open_stream_for_sheet ndx
|
42
|
+
@zip.get_output_stream "xl/worksheets/sheet#{ndx + 1}.xml" do |f|
|
43
|
+
yield f
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_content_types
|
48
|
+
@zip.get_output_stream "[Content_Types].xml" do |f|
|
49
|
+
f.puts '<?xml version="1.0" encoding="UTF-8"?>'
|
50
|
+
f.puts '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
51
|
+
f.puts <<-ends
|
52
|
+
<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
53
|
+
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
|
54
|
+
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
|
55
|
+
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
56
|
+
<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
57
|
+
ends
|
58
|
+
if @doc.has_shared_strings?
|
59
|
+
f.puts '<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>'
|
60
|
+
end
|
61
|
+
@doc.sheets.each_with_index do |sheet, ndx|
|
62
|
+
f.puts "<Override PartName=\"/xl/worksheets/sheet#{ndx+1}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>"
|
63
|
+
end
|
64
|
+
f.puts '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'
|
65
|
+
f.puts "</Types>"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_workbook_relationship_part
|
70
|
+
@zip.mkdir "xl/_rels"
|
71
|
+
@zip.get_output_stream "xl/_rels/workbook.xml.rels" do |f|
|
72
|
+
f.puts <<-ends
|
73
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
74
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
75
|
+
ends
|
76
|
+
cnt = 0
|
77
|
+
f.puts "<Relationship Id=\"rId#{cnt += 1}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>"
|
78
|
+
@doc.sheets.each_with_index do |sheet, ndx|
|
79
|
+
sheet.rid = "rId#{cnt += 1}"
|
80
|
+
f.puts "<Relationship Id=\"#{sheet.rid}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet#{ndx + 1}.xml\"/>"
|
81
|
+
end
|
82
|
+
if @doc.has_shared_strings?
|
83
|
+
f.puts '<Relationship Id="rId#{cnt += 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="xl/sharedStrings.xml"/>'
|
84
|
+
end
|
85
|
+
f.puts "</Relationships>"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_relationship_part
|
90
|
+
@zip.mkdir "_rels"
|
91
|
+
@zip.get_output_stream "_rels/.rels" do |f|
|
92
|
+
f.puts <<-ends
|
93
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
94
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
95
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
96
|
+
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
|
97
|
+
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
|
98
|
+
ends
|
99
|
+
f.puts "</Relationships>"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_doc_props
|
104
|
+
@zip.mkdir "docProps"
|
105
|
+
@zip.get_output_stream "docProps/core.xml" do |f|
|
106
|
+
f.puts <<-ends
|
107
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
108
|
+
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
109
|
+
<dcterms:created xsi:type="dcterms:W3CDTF">2010-07-20T14:30:58.00Z</dcterms:created>
|
110
|
+
<cp:revision>0</cp:revision>
|
111
|
+
</cp:coreProperties>
|
112
|
+
ends
|
113
|
+
end
|
114
|
+
@zip.get_output_stream "docProps/app.xml" do |f|
|
115
|
+
f.puts <<-ends
|
116
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
117
|
+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
|
118
|
+
<TotalTime>0</TotalTime>
|
119
|
+
</Properties>
|
120
|
+
ends
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def add_styles
|
125
|
+
@zip.get_output_stream "xl/styles.xml" do |f|
|
126
|
+
f.puts <<-ends
|
127
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
128
|
+
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
129
|
+
<numFmts count="7">
|
130
|
+
<numFmt formatCode="GENERAL" numFmtId="164"/>
|
131
|
+
<numFmt formatCode=""TRUE";"TRUE";"FALSE"" numFmtId="170"/>
|
132
|
+
</numFmts>
|
133
|
+
<fonts count="5">
|
134
|
+
<font><name val="Mangal"/><family val="2"/><sz val="10"/></font>
|
135
|
+
<font><name val="Arial"/><family val="0"/><sz val="10"/></font>
|
136
|
+
<font><name val="Arial"/><family val="0"/><sz val="10"/></font>
|
137
|
+
<font><name val="Arial"/><family val="0"/><sz val="10"/></font>
|
138
|
+
<font><name val="Arial"/><family val="2"/><sz val="10"/></font>
|
139
|
+
</fonts>
|
140
|
+
<fills count="2">
|
141
|
+
<fill><patternFill patternType="none"/></fill>
|
142
|
+
<fill><patternFill patternType="gray125"/></fill>
|
143
|
+
</fills>
|
144
|
+
<borders count="1">
|
145
|
+
<border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border>
|
146
|
+
</borders>
|
147
|
+
<cellStyleXfs count="20">
|
148
|
+
<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">
|
149
|
+
<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>
|
150
|
+
<protection hidden="false" locked="true"/>
|
151
|
+
</xf>
|
152
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"></xf>
|
153
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"></xf>
|
154
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"></xf>
|
155
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"></xf>
|
156
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
157
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
158
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
159
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
160
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
161
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
162
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
163
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
164
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
165
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
|
166
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"></xf>
|
167
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"></xf>
|
168
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"></xf>
|
169
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"></xf>
|
170
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"></xf>
|
171
|
+
</cellStyleXfs>
|
172
|
+
<cellXfs count="7">
|
173
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="4" numFmtId="164" xfId="0"></xf>
|
174
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="4" numFmtId="22" xfId="0"></xf>
|
175
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="4" numFmtId="15" xfId="0"></xf>
|
176
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="4" numFmtId="1" xfId="0"></xf>
|
177
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="4" numFmtId="2" xfId="0"></xf>
|
178
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="4" numFmtId="49" xfId="0"></xf>
|
179
|
+
<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="4" numFmtId="170" xfId="0"></xf>
|
180
|
+
</cellXfs>
|
181
|
+
<cellStyles count="6"><cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>
|
182
|
+
<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>
|
183
|
+
<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>
|
184
|
+
<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>
|
185
|
+
<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>
|
186
|
+
<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>
|
187
|
+
</cellStyles>
|
188
|
+
</styleSheet>
|
189
|
+
ends
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module SimpleXlsx
|
5
|
+
|
6
|
+
class Sheet
|
7
|
+
attr_reader :name
|
8
|
+
attr_accessor :rid
|
9
|
+
|
10
|
+
def initialize document, name, stream, &block
|
11
|
+
@document = document
|
12
|
+
@stream = stream
|
13
|
+
@name = name
|
14
|
+
@row_ndx = 1
|
15
|
+
@stream.write <<-ends
|
16
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
17
|
+
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
18
|
+
<sheetData>
|
19
|
+
ends
|
20
|
+
if block_given?
|
21
|
+
yield self
|
22
|
+
end
|
23
|
+
@stream.write "</sheetData></worksheet>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_row arry
|
27
|
+
row = ["<row r=\"#{@row_ndx}\">"]
|
28
|
+
arry.each_with_index do |value, col_ndx|
|
29
|
+
kind, ccontent, cstyle = Sheet.format_field_and_type_and_style value
|
30
|
+
row << "<c r=\"#{Sheet.column_index(col_ndx)}#{@row_ndx}\" t=\"#{kind.to_s}\" s=\"#{cstyle}\">#{ccontent}</c>"
|
31
|
+
end
|
32
|
+
row << "</row>"
|
33
|
+
@row_ndx += 1
|
34
|
+
@stream.write(row.join())
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.format_field_and_type_and_style value
|
38
|
+
if value.is_a?(String)
|
39
|
+
[:inlineStr, "<is><t>#{value.to_xs}</t></is>", 5]
|
40
|
+
elsif value.is_a?(BigDecimal)
|
41
|
+
[:n, "<v>#{value.to_s('f')}</v>", 4]
|
42
|
+
elsif value.is_a?(Float)
|
43
|
+
[:n, "<v>#{value.to_s}</v>", 4]
|
44
|
+
elsif value.is_a?(Numeric)
|
45
|
+
[:n, "<v>#{value.to_s}</v>", 3]
|
46
|
+
elsif value.is_a?(Date)
|
47
|
+
[:n, "<v>#{days_since_jan_1_1900(value)}</v>", 2]
|
48
|
+
elsif value.is_a?(Time)
|
49
|
+
[:n, "<v>#{fractional_days_since_jan_1_1900(value)}</v>", 1]
|
50
|
+
elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
51
|
+
[:b, "<v>#{value ? '1' : '0'}</v>", 6]
|
52
|
+
else
|
53
|
+
[:inlineStr, "<is><t>#{value.to_s.to_xs}</t></is>", 5]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.days_since_jan_1_1900 date
|
58
|
+
@@jan_1_1904 ||= Date.parse("1904 Jan 1")
|
59
|
+
(date - @@jan_1_1904).to_i + 1462 # http://support.microsoft.com/kb/180162
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.fractional_days_since_jan_1_1900 value
|
63
|
+
@@jan_1_1904_midnight ||= ::Time.utc(1904, 1, 1)
|
64
|
+
((value - @@jan_1_1904_midnight) / 86400.0) + #24*60*60
|
65
|
+
1462 # http://support.microsoft.com/kb/180162
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.abc
|
69
|
+
@@abc ||= ('A'..'Z').to_a
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.column_index n
|
73
|
+
result = []
|
74
|
+
while n >= 26 do
|
75
|
+
result << abc[n % 26]
|
76
|
+
n /= 26
|
77
|
+
end
|
78
|
+
result << abc[result.empty? ? n : n - 1]
|
79
|
+
result.reverse.join
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper.rb'
|
2
|
+
|
3
|
+
module SimpleXlsx
|
4
|
+
|
5
|
+
class DocumentTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def open_stream_for_sheet sheets_size
|
8
|
+
assert_equal sheets_size, @doc.sheets.size
|
9
|
+
yield self
|
10
|
+
end
|
11
|
+
|
12
|
+
def write arg
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_add_sheet
|
16
|
+
@doc = Document.new self
|
17
|
+
assert_equal [], @doc.sheets
|
18
|
+
@doc.add_sheet "new sheet"
|
19
|
+
assert_equal 1, @doc.sheets.size
|
20
|
+
assert_equal 'new sheet', @doc.sheets.first.name
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper.rb'
|
2
|
+
require "rexml/document"
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module SimpleXlsx
|
6
|
+
|
7
|
+
class SheetTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_column_index
|
10
|
+
assert_equal 'A', Sheet.column_index(0)
|
11
|
+
assert_equal 'B', Sheet.column_index(1)
|
12
|
+
assert_equal 'C', Sheet.column_index(2)
|
13
|
+
assert_equal 'D', Sheet.column_index(3)
|
14
|
+
assert_equal 'Y', Sheet.column_index(24)
|
15
|
+
assert_equal 'Z', Sheet.column_index(25)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_column_index_two_digits
|
19
|
+
assert_equal 'AA', Sheet.column_index(0+26)
|
20
|
+
assert_equal 'AB', Sheet.column_index(1+26)
|
21
|
+
assert_equal 'AC', Sheet.column_index(2+26)
|
22
|
+
assert_equal 'AD', Sheet.column_index(3+26)
|
23
|
+
assert_equal 'AZ', Sheet.column_index(25+26)
|
24
|
+
assert_equal 'BA', Sheet.column_index(25+26+1)
|
25
|
+
assert_equal 'BB', Sheet.column_index(25+26+2)
|
26
|
+
assert_equal 'BC', Sheet.column_index(25+26+3)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_format_field_for_strings
|
30
|
+
v = Sheet.format_field_and_type_and_style "<escape this>"
|
31
|
+
assert_equal [:inlineStr, "<is><t><escape this></t></is>", 5], v
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_format_field_for_numbers
|
35
|
+
v = Sheet.format_field_and_type_and_style 3
|
36
|
+
assert_equal [:n, "<v>3</v>", 3], v
|
37
|
+
v = Sheet.format_field_and_type_and_style(BigDecimal.new("45"))
|
38
|
+
assert_equal [:n, "<v>45.0</v>", 4], v
|
39
|
+
v = Sheet.format_field_and_type_and_style(9.32)
|
40
|
+
assert_equal [:n, "<v>9.32</v>", 4], v
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_format_field_for_date
|
44
|
+
v = Sheet.format_field_and_type_and_style(Date.parse('2010-Jul-24'))
|
45
|
+
assert_equal [:n, "<v>#{38921+1462}</v>", 2], v
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_format_field_for_datetime
|
49
|
+
v = Sheet.format_field_and_type_and_style(Time.parse('2010-Jul-24 12:00 UTC'))
|
50
|
+
assert_equal [:n, "<v>#{38921.5+1462}</v>", 1], v
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def test_format_field_for_boolean
|
55
|
+
v = Sheet.format_field_and_type_and_style(false)
|
56
|
+
assert_equal [:b, "<v>0</v>", 6], v
|
57
|
+
v = Sheet.format_field_and_type_and_style(true)
|
58
|
+
assert_equal [:b, "<v>1</v>", 6], v
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_add_row
|
62
|
+
str = ""
|
63
|
+
io = StringIO.new(str)
|
64
|
+
Sheet.new(nil, 'name', io) do |sheet|
|
65
|
+
sheet.add_row ['this is ', 'a new row']
|
66
|
+
end
|
67
|
+
doc = REXML::Document.new str
|
68
|
+
assert_equal 'worksheet', doc.root.name
|
69
|
+
sheetdata = doc.root.elements['sheetData']
|
70
|
+
assert sheetdata
|
71
|
+
row = sheetdata.elements['row']
|
72
|
+
assert row
|
73
|
+
assert_equal '1', row.attributes['r']
|
74
|
+
assert_equal 2, row.elements.to_a.size
|
75
|
+
assert_equal ["r", "s", "t"], row.elements.to_a[0].attributes.keys
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class SimpleXlsxTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_top_level
|
7
|
+
FileUtils.rm_f "test.xlsx"
|
8
|
+
o = SimpleXlsx::Serializer.new("test.xlsx") do |doc|
|
9
|
+
doc.add_sheet "First" do |sheet|
|
10
|
+
sheet.add_row ["Hello", "World", 3.14, 7]
|
11
|
+
sheet.add_row ["Another", "Row", Date.today, Time.parse('2010-Jul-24 12:00 UTC')]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
if false
|
17
|
+
def test_top_level_stream
|
18
|
+
File.open "test_stream.xlsx", "wb" do |stream|
|
19
|
+
o = SimpleXlsx::Serializer.new(stream) do |doc|
|
20
|
+
doc.add_sheet "First" do |sheet|
|
21
|
+
sheet.add_row ["Hello", "World", 3.14]
|
22
|
+
sheet.add_row ["Another", "Row", Date.today]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_xlsx_writer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 13
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 5
|
9
|
+
- 3
|
10
|
+
version: 0.5.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Dee Zsombor
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-20 00:00:00 +03:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rubyzip
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 51
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 9
|
33
|
+
- 4
|
34
|
+
version: 0.9.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: fast_xs
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 5
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 7
|
49
|
+
- 3
|
50
|
+
version: 0.7.3
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description:
|
54
|
+
email: zsombor@primalgrasp.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files:
|
60
|
+
- README
|
61
|
+
files:
|
62
|
+
- lib/simple_xlsx.rb
|
63
|
+
- lib/simple_xlsx/xml_escape.rb
|
64
|
+
- lib/simple_xlsx/serializer.rb
|
65
|
+
- lib/simple_xlsx/monkey_patches_for_true_zip_stream.rb
|
66
|
+
- lib/simple_xlsx/document.rb
|
67
|
+
- lib/simple_xlsx/sheet.rb
|
68
|
+
- LICENSE
|
69
|
+
- Rakefile
|
70
|
+
- test/simple_xlsx_test.rb
|
71
|
+
- test/simple_xlsx/document_test.rb
|
72
|
+
- test/simple_xlsx/sheet_test.rb
|
73
|
+
- test/test_helper.rb
|
74
|
+
- README
|
75
|
+
has_rdoc: true
|
76
|
+
homepage: http://simplxlsxwriter.rubyforge.org
|
77
|
+
licenses: []
|
78
|
+
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project: simple_xlsx_writer
|
105
|
+
rubygems_version: 1.3.7
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: Just as the name says, simple writter for Office 2007+ Excel files
|
109
|
+
test_files:
|
110
|
+
- test/simple_xlsx_test.rb
|
111
|
+
- test/simple_xlsx/document_test.rb
|
112
|
+
- test/simple_xlsx/sheet_test.rb
|
113
|
+
- test/test_helper.rb
|