simple_xlsx_writer 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|