openxml-xlsx 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a010100989ff2ab9093bfdea05d3535cf8de0eb1
4
+ data.tar.gz: 55f156afa2e40ad7a5f73b82a6e366dd39972d18
5
+ SHA512:
6
+ metadata.gz: b6cbe628be8b61481747178a1cc4e6a9bbac64477412f39c34cb5ebbc9785563d910bf172030330e49e6ae01fd14a96717f719b32d58ae0384d2b9062eaf6469
7
+ data.tar.gz: ca54754a84cf214773b0d4aeb61d0ecd78d2c05e5d6660dc2e6d5059b49e8cd7dcff6e86ad899c94b3fa047174410636bfeaf6bd66ef1951bade31cd3a8c8842
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xlsx.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Bob Lail
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/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ RSpec::Core::RakeTask.new
4
+ task default: :spec
5
+ task test: :spec
6
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "xlsx"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/example ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push Dir.pwd + "/lib"
4
+ require "xlsx"
5
+ package = Xlsx::Package.new
6
+ workbook = package.workbook
7
+ worksheet = workbook.worksheets[0]
8
+
9
+ include Xlsx::Elements
10
+
11
+ workbook.add_defined_names({name: "Alpha", formula:"Sheet1!$A$1"}, {name: "Charlie", formula:"Sheet1!$C$1"})
12
+
13
+ worksheet.merge_cells("B1:G1", "A8:G8")
14
+
15
+ currency = NumberFormat.new("\"$\"#,##0.00")
16
+
17
+ heading = Font.new("Calibri", 25, {bold: true, italic: true, underline: true, color: IndexedColor.new(55)})
18
+
19
+ fill2 = PatternFill.new("solid",
20
+ ThemeColor.new(0, -4.9989318521683403E-2),
21
+ IndexedColor.new(65))
22
+ fill3 = PatternFill.new("solid",
23
+ ThemeColor.new(8, 0.79998168889431442),
24
+ IndexedColor.new(65))
25
+ fill4 = PatternFill.new("solid",
26
+ ThemeColor.new(8, 0.59999389629810485),
27
+ IndexedColor.new(65))
28
+
29
+ left = Alignment.new("left", "center")
30
+ center = Alignment.new("center", "center")
31
+ right = Alignment.new("right", "center")
32
+
33
+ border1 = Border.new(top: BorderComponent.new(BorderStyle::THICK))
34
+ border2 = Border.new(
35
+ left: BorderComponent.new(BorderStyle::HAIR, IndexedColor.new(12)),
36
+ right: BorderComponent.new(BorderStyle::SLANT_DASH_DOT, IndexedColor.new(45))
37
+ )
38
+ border3 = Border.new(bottom: BorderComponent.new(BorderStyle::DOUBLE, IndexedColor.new(13)))
39
+
40
+ worksheet.column_widths(
41
+ 1 => 3.83203125,
42
+ 2 => 11.1640625,
43
+ 5 => 36.1640625
44
+ )
45
+
46
+
47
+ worksheet.add_rows(
48
+ { number: 1,
49
+ cells: [
50
+ { column: 2, value: "Test", style: {font: heading, alignment: left} }] },
51
+ { number: 2,
52
+ cells: [
53
+ { column: 2, value: "Column 1", style: {fill: fill2, alignment: left, border: border2} },
54
+ { column: 3, value: "Column 2", style: {fill: fill2, alignment: center, border: border3} },
55
+ { column: 4, value: "Column 3", style: {fill: fill2, alignment: right, border: border1} },
56
+ { column: 5, value: "Column 4", style: {fill: fill4, alignment: right} }] },
57
+ { number: 3,
58
+ cells: [
59
+ { column: 2, value: 1, style: {alignment: left, border: border2} },
60
+ { column: 3, value: Time.new(2014, 6, 20, 8, 30), style: {format: NumberFormat::DATETIME, alignment: center, border: border3} },
61
+ { column: 4, value: 65, style: {format: currency, alignment: right, border: border1} },
62
+ { column: 5, value: "hi", style: {format: currency, fill: fill3, alignment: right} }] },
63
+ { number: 4,
64
+ hidden: true,
65
+ cells: [
66
+ { column: 2, value: 2, style: {alignment: left, border: border2} },
67
+ { column: 3, value: Date.new(2014, 7, 21), style: {format: NumberFormat::DAY_MONTH, alignment: center, border: border3} },
68
+ { column: 4, value: 14, style: {format: currency, alignment: right, border: border1} },
69
+ { column: 5, value: "hi", style: {format: currency, fill: fill3, alignment: right} }] },
70
+ { number: 5,
71
+ cells: [
72
+ { column: 2, value: 3, style: {alignment: left, border: border2} },
73
+ { column: 3, value: Date.new(2014, 8, 24), style: {format: NumberFormat::DAY_MONTH, alignment: center, border: border3} },
74
+ { column: 4, value: 13, style: {format: currency, alignment: right, border: border1} },
75
+ { column: 5, value: "hi", style: {format: currency, fill: fill3, alignment: right} }] },
76
+ { number: 6,
77
+ cells: [
78
+ { column: 2, value: 4, style: {alignment: left, border: border2} },
79
+ { column: 3, value: Date.new(2014, 8, 24), style: {format: NumberFormat::DAY_MONTH, alignment: center, border: border3} },
80
+ { column: 4, value: 9, style: {format: currency, alignment: right, border: border1} },
81
+ { column: 5, value: "hi", style: {format: currency, fill: fill3, alignment: right} }] })
82
+
83
+ worksheet.add_table 1, "Table1", "B2:E6", [
84
+ TableColumn.new("Column 1"),
85
+ TableColumn.new("Column 2"),
86
+ TableColumn.new("Column 3"),
87
+ TableColumn.new("Column 4")
88
+ ]
89
+
90
+ filename = "gemtest1.xlsx"
91
+ system "rm ~/Desktop/#{filename}"
92
+ package.save File.expand_path("~/Desktop/#{filename}")
93
+ exec "open ~/Desktop/#{filename}"
data/lib/xlsx.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Xlsx
2
+ REL_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument".freeze
3
+ REL_WORKSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet".freeze
4
+ REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles".freeze
5
+ REL_SHARED_STRINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings".freeze
6
+ REL_CALC_CHAIN = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain".freeze
7
+ REL_THEME = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme".freeze
8
+ REL_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table".freeze
9
+
10
+ TYPE_WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml".freeze
11
+ TYPE_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml".freeze
12
+ TYPE_THEME = "application/vnd.openxmlformats-officedocument.theme+xml".freeze
13
+ TYPE_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml".freeze
14
+ TYPE_SHARED_STRINGS = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml".freeze
15
+ TYPE_CALC_CHAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml".freeze
16
+ TYPE_CORE_PROPS = "application/vnd.openxmlformats-package.core-properties+xml".freeze
17
+ TYPE_APP_PROPS = "application/vnd.openxmlformats-officedocument.extended-properties+xml".freeze
18
+ TYPE_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml".freeze
19
+
20
+ def self.index!(collection, item)
21
+ collection.index(item) || collection.push(item).length - 1
22
+ end
23
+
24
+ end
25
+
26
+ require "xlsx/elements"
27
+ require "xlsx/package"
28
+ require "xlsx/parts"
@@ -0,0 +1,8 @@
1
+ module Xlsx
2
+ module Elements
3
+ end
4
+ end
5
+
6
+ Dir.glob("#{File.join(File.dirname(__FILE__), "elements", "*.rb")}").each do |file|
7
+ require file
8
+ end
@@ -0,0 +1,20 @@
1
+ module Xlsx
2
+ module Elements
3
+ class Alignment < Struct.new(:horizontal, :vertical, :indent, :wrapText)
4
+
5
+ def attributes
6
+ {}.tap do |attrs|
7
+ attrs[:horizontal] = horizontal if horizontal
8
+ attrs[:vertical] = vertical if vertical
9
+ attrs[:indent] = indent if indent
10
+ attrs[:wrapText] = wrapText if wrapText
11
+ end
12
+ end
13
+
14
+ def to_xml(xml)
15
+ xml.alignment(attributes)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ module Xlsx
2
+ module Elements
3
+ class Border
4
+ attr_reader :left_component, :right_component, :top_component, :bottom_component, :diagonal_component
5
+
6
+ def initialize(options={})
7
+ @left_component = options.fetch(:left, BorderComponent.new)
8
+ @right_component = options.fetch(:right, BorderComponent.new)
9
+ @top_component = options.fetch(:top, BorderComponent.new)
10
+ @bottom_component = options.fetch(:bottom, BorderComponent.new)
11
+ @diagonal_component = options.fetch(:diagonal, BorderComponent.new)
12
+ end
13
+
14
+ def to_xml(xml)
15
+ xml.border do
16
+ left_component.to_xml("left", xml)
17
+ right_component.to_xml("right", xml)
18
+ top_component.to_xml("top", xml)
19
+ bottom_component.to_xml("bottom", xml)
20
+ diagonal_component.to_xml("diagonal", xml)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ module Xlsx
2
+ module Elements
3
+ class BorderComponent < Struct.new(:style, :color)
4
+
5
+ def to_xml(name, xml)
6
+ if style && color
7
+ xml.public_send(name, style: style) do
8
+ color.to_xml("color", xml)
9
+ end
10
+ elsif style
11
+ xml.public_send(name, style: style)
12
+ else
13
+ xml.public_send(name)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Xlsx
2
+ module Elements
3
+ class BorderStyle
4
+ NONE = "none".freeze
5
+ THIN = "thin".freeze
6
+ MEDIUM = "medium".freeze
7
+ DASHED = "dashed".freeze
8
+ DOTTED = "dotted".freeze
9
+ THICK = "thick".freeze
10
+ DOUBLE = "double".freeze
11
+ HAIR = "hair".freeze
12
+ MEDIUM_DASHED = "mediumDashed".freeze
13
+ DASH_DOT = "dashDot".freeze
14
+ MEDIUM_DASH_DOT = "mediumDashDot".freeze
15
+ DASH_DOT_DOT = "dashDotDot".freeze
16
+ MEDIUM_DASH_DOT_DOT = "mediumDashDotDot".freeze
17
+ SLANT_DASH_DOT = "slantDashDot".freeze
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,94 @@
1
+ require "date"
2
+
3
+ module Xlsx
4
+ module Elements
5
+ class Cell
6
+ attr_reader :row, :column, :value, :type, :style, :formula
7
+
8
+ def initialize(row, options={})
9
+ @row = row
10
+ @column = options.fetch(:column)
11
+ @value = options[:value]
12
+ case value
13
+ when String
14
+ @type = :string
15
+ @string_id = package.string_ref(value)
16
+ when Date then
17
+ @type = :date
18
+ @serial_date = to_serial_date(value)
19
+ when Time then
20
+ @type = :time
21
+ @serial_time = to_serial_time(value)
22
+ else
23
+ @type = :general
24
+ end
25
+ @style = package.style_ref(options[:style]) if options.key? :style
26
+ @formula = options[:formula]
27
+ end
28
+
29
+ def id
30
+ "#{column_letter}#{row.number}"
31
+ end
32
+
33
+ def column_letter
34
+ bytes = []
35
+ remaining = column
36
+ while remaining > 0
37
+ bytes.unshift (remaining - 1) % 26 + 65
38
+ remaining = (remaining - 1) / 26
39
+ end
40
+ bytes.pack "c*"
41
+ end
42
+
43
+ def worksheet
44
+ row.worksheet
45
+ end
46
+
47
+ def workbook
48
+ worksheet.workbook
49
+ end
50
+
51
+ def package
52
+ workbook.package
53
+ end
54
+
55
+ def to_xml(xml)
56
+ attributes = {"r" => id}
57
+ attributes.merge!("s" => style) if style
58
+ attributes.merge!("t" => "s") if type == :string
59
+
60
+ value = self.value
61
+ value = string_id if type == :string
62
+ value = serial_date if type == :date
63
+ value = serial_time if type == :time
64
+
65
+ xml.c(attributes) do
66
+ xml.f formula if formula
67
+ xml.v value if value
68
+ end
69
+ end
70
+
71
+ private
72
+ attr_reader :string_id, :serial_date, :serial_time
73
+
74
+ EXCEL_ANCHOR_DATE = Date.new(1900, 1, 1).freeze
75
+ SECONDS_PER_DAY = 86400.freeze
76
+
77
+ def to_serial_date(date)
78
+ # Excel stores dates as the number of days since 1900-Jan-0
79
+ # Excel behaves as if 1900 was a leap year, so the number is
80
+ # generally 1 greater than you would expect.
81
+ # http://www.cpearson.com/excel/datetime.htm
82
+ (date - EXCEL_ANCHOR_DATE).to_i + 2
83
+ end
84
+
85
+ def to_serial_time(time)
86
+ date = to_serial_date(time.to_date)
87
+
88
+ seconds_since_midnight = time.hour * 3600 + time.min * 60 + time.sec
89
+ date + (seconds_since_midnight.to_f / SECONDS_PER_DAY)
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,11 @@
1
+ module Xlsx
2
+ module Elements
3
+ class DefinedName < Struct.new(:name, :formula)
4
+
5
+ def to_xml(xml)
6
+ xml.definedName "#{formula}", name: name
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ module Xlsx
2
+ module Elements
3
+ class Font < Struct.new(:name, :size, :styles)
4
+
5
+ def to_xml(xml)
6
+ xml.font do
7
+ xml.sz val: size
8
+ xml.name val: name
9
+ if styles
10
+ xml.b if styles[:bold]
11
+ xml.i if styles[:italic]
12
+ xml.u if styles[:underline]
13
+ styles[:color].to_xml("color", xml) if styles[:color]
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module Xlsx
2
+ module Elements
3
+ ImpliedNumberFormat = Struct.new(:id)
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Xlsx
2
+ module Elements
3
+ class IndexedColor < Struct.new(:indexed)
4
+
5
+ def to_xml(name, xml)
6
+ xml.public_send(name, indexed: indexed)
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ require "xlsx/elements/implied_number_format"
2
+
3
+ module Xlsx
4
+ module Elements
5
+ class NumberFormat < Struct.new(:format)
6
+ INTEGER = ImpliedNumberFormat.new(1).freeze # 0
7
+ DECIMAL = ImpliedNumberFormat.new(2).freeze # 0.00
8
+ INTEGER_THOUSANDS = ImpliedNumberFormat.new(3).freeze # #,##0
9
+ DECIMAL_THOUSANDS = ImpliedNumberFormat.new(4).freeze # #,##0.00
10
+
11
+ INTEGER_PERCENT = ImpliedNumberFormat.new(9).freeze # 0%
12
+ DECIMAL_PERCENT = ImpliedNumberFormat.new(10).freeze # 0.00%
13
+ SCIENTIFIC = ImpliedNumberFormat.new(11).freeze # 0.00E+00
14
+ FRACTION = ImpliedNumberFormat.new(12).freeze # ?/?
15
+ FRACTION2 = ImpliedNumberFormat.new(13).freeze # ??/??
16
+ DATE = ImpliedNumberFormat.new(14).freeze # mm-dd-yy
17
+ DATE_ALT = ImpliedNumberFormat.new(15).freeze # d-mmm-yy
18
+ DAY_MONTH = ImpliedNumberFormat.new(16).freeze # d-mmm
19
+ MONTH_YEAR = ImpliedNumberFormat.new(17).freeze # mmm-yy
20
+ TIME = ImpliedNumberFormat.new(18).freeze # h:mm AM/PM
21
+ TIME_SECONDS = ImpliedNumberFormat.new(19).freeze # h:mm:ss AM/PM
22
+ TIME_ALT = ImpliedNumberFormat.new(20).freeze # h:mm
23
+ TIME_SECONDS_ALT = ImpliedNumberFormat.new(21).freeze # h:mm:ss
24
+ DATETIME = ImpliedNumberFormat.new(22).freeze # m/d/yy h:mm
25
+
26
+ FINANCIAL_INTEGER = ImpliedNumberFormat.new(37).freeze # #,##0 ;(#,##0)
27
+ FINANCIAL_INTEGER_RED = ImpliedNumberFormat.new(38).freeze # #,##0 ;[Red](#,##0)
28
+ FINANCIAL_DECIMAL = ImpliedNumberFormat.new(39).freeze # #,##0.00 ;(#,##0.00)
29
+ FINANCIAL_DECIMAL_RED = ImpliedNumberFormat.new(40).freeze # #,##0.00 ;[Red](#,##0.00)
30
+
31
+ INTERVAL = ImpliedNumberFormat.new(45).freeze # mm:ss
32
+ INTERVAL_HOURS = ImpliedNumberFormat.new(46).freeze # [h]:mm:ss
33
+ TIMESTAMP = ImpliedNumberFormat.new(47).freeze # mmss.0
34
+ # SCIENTIFIC_ALT = ImpliedNumberFormat.new(48).freeze # ##0.0E+0
35
+ # UNKONWN = ImpliedNumberFormat.new(49).freeze # @
36
+
37
+
38
+
39
+ def to_xml(id, xml)
40
+ xml.numFmt(numFmtId: id, formatCode: format)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ module Xlsx
2
+ module Elements
3
+ class PatternFill < Struct.new(:type, :foreground_color, :background_color)
4
+
5
+ def to_xml(xml)
6
+ xml.fill do
7
+ xml.patternFill(patternType: type) do
8
+ if type.to_s == "solid"
9
+ foreground_color.to_xml("fgColor", xml)
10
+ background_color.to_xml("bgColor", xml)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require "securerandom"
2
+
3
+ module Xlsx
4
+ module Elements
5
+ class Relationship < Struct.new(:type, :target, :id)
6
+
7
+ def initialize(type, target, id=nil)
8
+ id ||= "R#{SecureRandom.hex}"
9
+ super type, target, id
10
+ end
11
+
12
+ def to_xml(xml)
13
+ xml.Relationship("Id" => id, "Type" => type, "Target" => target)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ module Xlsx
2
+ module Elements
3
+ class Row
4
+ attr_reader :worksheet, :number, :height, :hidden, :cells
5
+
6
+ def initialize(worksheet, options={})
7
+ @worksheet = worksheet
8
+ @number = options.fetch(:number)
9
+ @height = options[:height]
10
+ @hidden = options[:hidden]
11
+ @cells = []
12
+
13
+ Array(options[:cells]).each do |attributes|
14
+ add_cell attributes
15
+ end
16
+ end
17
+
18
+ def add_cell(attributes)
19
+ cells.push Xlsx::Elements::Cell.new(self, attributes)
20
+ end
21
+
22
+ def to_xml(xml)
23
+ attributes = {"r" => number}
24
+ attributes.merge!("ht" => height, "customHeight" => 1) if height
25
+ attributes.merge!("hidden" => 1) if hidden
26
+ xml.row(attributes) do
27
+ cells.each do |cell|
28
+ cell.to_xml(xml)
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ module Xlsx
2
+ module Elements
3
+ class Style < Struct.new(:format_id, :font_id, :fill_id, :border_id, :alignment)
4
+
5
+ def initialize(format_id=0, font_id=0, fill_id=0, border_id=0, alignment=nil)
6
+ super format_id || 0, font_id || 0, fill_id || 0, border_id || 0, alignment
7
+ end
8
+
9
+ def to_xml(xml)
10
+ attributes = {
11
+ numFmtId: format_id,
12
+ fontId: font_id,
13
+ fillId: fill_id,
14
+ borderId: border_id }
15
+ attributes.merge!(applyNumberFormat: 1) if format_id > 0
16
+ attributes.merge!(applyFont: 1) if font_id > 0
17
+ attributes.merge!(applyFill: 1) if fill_id > 0
18
+ attributes.merge!(applyBorder: 1) if border_id > 0
19
+ attributes.merge!(applyAlignment: 1) if alignment
20
+ xml.xf(attributes) do
21
+ alignment.to_xml(xml) if alignment
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module Xlsx
2
+ module Elements
3
+ class TableColumn < Struct.new(:name)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module Xlsx
2
+ module Elements
3
+ class ThemeColor < Struct.new(:theme, :tint)
4
+
5
+ def to_xml(name, xml)
6
+ xml.public_send(name, theme: theme, tint: tint)
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,50 @@
1
+ require "open_xml/package"
2
+
3
+ module Xlsx
4
+ class Package < OpenXml::Package
5
+ attr_reader :xl_rels,
6
+ :shared_strings,
7
+ :stylesheet,
8
+ :workbook
9
+
10
+
11
+ content_types do
12
+ override "/xl/workbook.xml", TYPE_WORKBOOK
13
+ override "/xl/worksheets/sheet1.xml", TYPE_WORKSHEET
14
+ override "/xl/sharedStrings.xml", TYPE_SHARED_STRINGS
15
+ override "/xl/styles.xml", TYPE_STYLES
16
+ end
17
+
18
+
19
+ def initialize
20
+ super
21
+ rels.add_relationship REL_DOCUMENT, "xl/workbook.xml"
22
+
23
+ @xl_rels = OpenXml::Parts::Rels.new([
24
+ { "Type" => REL_SHARED_STRINGS, "Target" => "sharedStrings.xml" },
25
+ { "Type" => REL_STYLES, "Target" => "styles.xml" }
26
+ ])
27
+ @shared_strings = Xlsx::Parts::SharedStrings.new
28
+ @stylesheet = Xlsx::Parts::Stylesheet.new
29
+ @workbook = Xlsx::Parts::Workbook.new(self)
30
+
31
+ # docProps/app.xml
32
+ # docProps/core.xml
33
+ add_part "xl/_rels/workbook.xml.rels", xl_rels
34
+ # xl/calcChain.xml
35
+ add_part "xl/sharedStrings.xml", shared_strings
36
+ add_part "xl/styles.xml", stylesheet
37
+ # xl/theme/theme1.xml
38
+ add_part "xl/workbook.xml", workbook
39
+ end
40
+
41
+ def string_ref(string)
42
+ shared_strings.reference_of(string)
43
+ end
44
+
45
+ def style_ref(style)
46
+ stylesheet.reference_of(style)
47
+ end
48
+
49
+ end
50
+ end
data/lib/xlsx/parts.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Xlsx
2
+ module Parts
3
+ end
4
+ end
5
+
6
+ Dir.glob("#{File.join(File.dirname(__FILE__), "parts", "*.rb")}").sort.each do |file|
7
+ require file
8
+ end
@@ -0,0 +1,26 @@
1
+ module Xlsx
2
+ module Parts
3
+ class SharedStrings < OpenXml::Part
4
+ attr_reader :strings
5
+
6
+ def initialize
7
+ @strings = []
8
+ end
9
+
10
+ def reference_of(string)
11
+ Xlsx.index!(strings, string)
12
+ end
13
+
14
+ def to_xml
15
+ build_standalone_xml do |xml|
16
+ xml.sst(xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main", uniqueCount: strings.length) do
17
+ strings.each do |string|
18
+ xml.si { xml.t(string) }
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,78 @@
1
+ module Xlsx
2
+ module Parts
3
+ class Stylesheet < OpenXml::Part
4
+ include Xlsx::Elements
5
+
6
+ attr_reader :formats, :fonts, :fills, :borders, :styles
7
+
8
+ def initialize
9
+ @formats = []
10
+ @fonts = [Font.new("Calibri", 12)]
11
+ @fills = [PatternFill.new("none"), PatternFill.new("gray125")]
12
+ @borders = [Border.new]
13
+ @styles = [Style.new]
14
+ end
15
+
16
+ def reference_of(options={})
17
+ case format = options[:format]
18
+ when NumberFormat
19
+ options[:format_id] = Xlsx.index!(formats, format) + NUMBER_FORMAT_START_ID
20
+ when ImpliedNumberFormat
21
+ options[:format_id] = format.id
22
+ end
23
+ options[:font_id] = Xlsx.index!(fonts, options[:font]) if options.key? :font
24
+ options[:fill_id] = Xlsx.index!(fills, options[:fill]) if options.key? :fill
25
+ options[:border_id] = Xlsx.index!(borders, options[:border]) if options.key? :border
26
+
27
+ style = Style.new(
28
+ options.fetch(:format_id, 0),
29
+ options.fetch(:font_id, 0),
30
+ options.fetch(:fill_id, 0),
31
+ options.fetch(:border_id, 0),
32
+ options.fetch(:alignment, nil))
33
+
34
+ Xlsx.index!(styles, style)
35
+ end
36
+
37
+ def to_xml
38
+ build_standalone_xml do |xml|
39
+ xml.styleSheet(xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main", "xmlns:mc" => "http://schemas.openxmlformats.org/markup-compatibility/2006") do
40
+ xml.numFmts(count: formats.length) do
41
+ formats.each_with_index do |format, index|
42
+ format.to_xml(index + NUMBER_FORMAT_START_ID, xml)
43
+ end
44
+ end
45
+
46
+ xml.fonts(count: fonts.length) do
47
+ fonts.each do |font|
48
+ font.to_xml(xml)
49
+ end
50
+ end
51
+
52
+ xml.fills(count: fills.length) do
53
+ fills.each do |fill|
54
+ fill.to_xml(xml)
55
+ end
56
+ end
57
+
58
+ xml.borders(count: borders.length) do
59
+ borders.each do |border|
60
+ border.to_xml(xml)
61
+ end
62
+ end
63
+
64
+ xml.cellXfs(count: styles.length) do
65
+ styles.each do |style|
66
+ style.to_xml(xml)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+ NUMBER_FORMAT_START_ID = 165.freeze
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,34 @@
1
+ module Xlsx
2
+ module Parts
3
+ class Table < OpenXml::Part
4
+ attr_reader :id, :name, :ref, :columns
5
+
6
+ def initialize(id, name, ref, columns)
7
+ @id = id
8
+ @name = name
9
+ @ref = ref
10
+ @columns = columns
11
+ end
12
+
13
+ def filename
14
+ "table#{id}.xml"
15
+ end
16
+
17
+ def to_xml
18
+ build_standalone_xml do |xml|
19
+ xml.table(xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
20
+ id: id, name: name, displayName: name, ref: ref, totalsRowShown: 0) do
21
+ xml.autoFilter ref: ref
22
+ xml.tableColumns(count: columns.length) do
23
+ columns.each_with_index do |column, index|
24
+ xml.tableColumn(id: index + 1, name: column.name)
25
+ end
26
+ end
27
+ xml.tableStyleInfo(name: "TableStyleLight6", showFirstColumn: 0, showLastColumn: 0, showRowStripes: 1, showColumnStripes: 0)
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ module Xlsx
2
+ module Parts
3
+ class Workbook < OpenXml::Part
4
+ attr_reader :package, :worksheets, :tables, :defined_names
5
+
6
+ def initialize(package)
7
+ @package = package
8
+ @worksheets = []
9
+ @tables = []
10
+ @defined_names =[]
11
+ add_worksheet
12
+ end
13
+
14
+ def add_worksheet
15
+ worksheet = Worksheet.new(self, worksheets.length + 1)
16
+ package.xl_rels.add_relationship(
17
+ REL_WORKSHEET,
18
+ "worksheets/sheet#{worksheet.index}.xml",
19
+ "rId#{worksheet.index}")
20
+ package.add_part "xl/worksheets/_rels/sheet#{worksheet.index}.xml.rels", worksheet.rels
21
+ package.add_part "xl/worksheets/sheet#{worksheet.index}.xml", worksheet
22
+ worksheets.push worksheet
23
+ end
24
+
25
+ def add_table(table)
26
+ package.content_types.add_override "/xl/tables/#{table.filename}", TYPE_TABLE
27
+ package.add_part "xl/tables/#{table.filename}", table
28
+ tables.push table
29
+ end
30
+
31
+ def add_defined_names(*defined_names)
32
+ defined_names.flatten.each do |attributes|
33
+ add_defined_name attributes
34
+ end
35
+ end
36
+
37
+ def add_defined_name(attributes)
38
+ defined_names.push Xlsx::Elements::DefinedName.new(attributes[:name], attributes[:formula])
39
+ end
40
+
41
+ def to_xml
42
+ build_standalone_xml do |xml|
43
+ xml.workbook(root_namespaces) {
44
+ xml.sheets { worksheets.each { |worksheet|
45
+ xml.sheet(
46
+ "name" => worksheet.name,
47
+ "sheetId" => worksheet.index,
48
+ "r:id" => "rId#{worksheet.index}")
49
+ } }
50
+ xml.definedNames do
51
+ defined_names.each { |defined_name| defined_name.to_xml(xml) }
52
+ end if defined_names.any?
53
+ }
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def root_namespaces
60
+ { "xmlns" => "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
61
+ "xmlns:r" => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' }
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,88 @@
1
+ module Xlsx
2
+ module Parts
3
+ class Worksheet < OpenXml::Part
4
+ attr_reader :workbook, :index, :rows, :tables, :rels, :cell_ranges
5
+
6
+ def initialize(workbook, index)
7
+ @workbook = workbook
8
+ @index = index
9
+ @rows = []
10
+ @tables = []
11
+ @cell_ranges = []
12
+ @rels = OpenXml::Parts::Rels.new
13
+ @column_widths = {}
14
+ end
15
+
16
+ def column_widths(*args)
17
+ return @column_widths if args.none?
18
+ @column_widths = args.first
19
+ end
20
+
21
+ def add_rows(*rows)
22
+ rows.flatten.each do |attributes|
23
+ add_row attributes
24
+ end
25
+ end
26
+
27
+ def add_row(attributes)
28
+ rows.push Xlsx::Elements::Row.new(self, attributes)
29
+ end
30
+
31
+ def merge_cells(*ranges)
32
+ ranges.each { |range| cell_ranges.push range }
33
+ end
34
+
35
+ def add_table(id, name, ref, columns)
36
+ table = Xlsx::Parts::Table.new(id, name, ref, columns)
37
+ rels.add_relationship(REL_TABLE, "../tables/#{table.filename}")
38
+ workbook.add_table table
39
+ end
40
+
41
+ def to_xml
42
+ build_standalone_xml do |xml|
43
+ xml.worksheet(root_namespaces) do
44
+ xml.sheetViews do
45
+ xml.sheetView(showGridLines: 0, tabSelected: 1, workbookViewId: 0)
46
+ end
47
+ xml.sheetFormatPr(baseColWidth: 10, defaultColWidth: 13.33203125, defaultRowHeight: 20, customHeight: 1)
48
+ xml.cols do
49
+ column_widths.each do |column, width|
50
+ xml.col(min: column, max: column, width: width, customWidth: 1)
51
+ end
52
+ end if column_widths.any?
53
+ xml.sheetData do
54
+ rows.each { |row| row.to_xml(xml) }
55
+ end
56
+ xml.mergeCells(count: merge_cells.size) do
57
+ cell_ranges.each { |range| xml.mergeCell ref: range }
58
+ end if cell_ranges.any?
59
+ xml.pageMargins(left: 0.75, right: 0.75, top: 1, bottom: 1, header: 0.5, footer: 0.5)
60
+ xml.pageSetup(orientation: "portrait", horizontalDpi: 4294967292, verticalDpi: 4294967292)
61
+ xml.tableParts(count: tables.count) do
62
+ tables.each do |rel|
63
+ xml.tablePart("r:id" => rel.id)
64
+ end
65
+ end if tables.any?
66
+ end
67
+ end
68
+ end
69
+
70
+ def name
71
+ "Sheet#{index}"
72
+ end
73
+
74
+ private
75
+
76
+ def root_namespaces
77
+ { "xmlns" => "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
78
+ "xmlns:r" => "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
79
+ "xmlns:mc" => "http://schemas.openxmlformats.org/markup-compatibility/2006" }
80
+ end
81
+
82
+ def tables
83
+ rels.select { |rel| rel.type == REL_TABLE }
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,3 @@
1
+ module Xlsx
2
+ VERSION = "0.1.1"
3
+ end
data/xlsx.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "xlsx/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "openxml-xlsx"
8
+ spec.version = Xlsx::VERSION
9
+ spec.authors = ["Bob Lail"]
10
+ spec.email = ["bob.lail@cph.org"]
11
+
12
+ spec.description = %q{Create Microsoft Excel (.xlsx) files.}
13
+ spec.summary = %q{Using a simple API, create xlsx files programmatically}
14
+ spec.license = "MIT"
15
+ spec.homepage = "https://github.com/concordia-publishing-house/xlsx"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.required_ruby_version = "~> 2.0"
23
+ spec.add_dependency "nokogiri"
24
+ spec.add_dependency "open_xml_package", "0.1.0"
25
+
26
+ spec.add_development_dependency "pry"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "rr"
30
+ spec.add_development_dependency "simplecov"
31
+ spec.add_development_dependency "timecop"
32
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openxml-xlsx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Bob Lail
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: open_xml_package
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Create Microsoft Excel (.xlsx) files.
126
+ email:
127
+ - bob.lail@cph.org
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - Gemfile
134
+ - Gemfile.lock
135
+ - LICENSE.txt
136
+ - Rakefile
137
+ - bin/console
138
+ - bin/setup
139
+ - example
140
+ - lib/xlsx.rb
141
+ - lib/xlsx/elements.rb
142
+ - lib/xlsx/elements/alignment.rb
143
+ - lib/xlsx/elements/border.rb
144
+ - lib/xlsx/elements/border_component.rb
145
+ - lib/xlsx/elements/border_style.rb
146
+ - lib/xlsx/elements/cell.rb
147
+ - lib/xlsx/elements/defined_name.rb
148
+ - lib/xlsx/elements/font.rb
149
+ - lib/xlsx/elements/implied_number_format.rb
150
+ - lib/xlsx/elements/indexed_color.rb
151
+ - lib/xlsx/elements/number_format.rb
152
+ - lib/xlsx/elements/pattern_fill.rb
153
+ - lib/xlsx/elements/relationship.rb
154
+ - lib/xlsx/elements/row.rb
155
+ - lib/xlsx/elements/style.rb
156
+ - lib/xlsx/elements/table_column.rb
157
+ - lib/xlsx/elements/theme_color.rb
158
+ - lib/xlsx/package.rb
159
+ - lib/xlsx/parts.rb
160
+ - lib/xlsx/parts/shared_strings.rb
161
+ - lib/xlsx/parts/stylesheet.rb
162
+ - lib/xlsx/parts/table.rb
163
+ - lib/xlsx/parts/workbook.rb
164
+ - lib/xlsx/parts/worksheet.rb
165
+ - lib/xlsx/version.rb
166
+ - xlsx.gemspec
167
+ homepage: https://github.com/concordia-publishing-house/xlsx
168
+ licenses:
169
+ - MIT
170
+ metadata: {}
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '2.0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubyforge_project:
187
+ rubygems_version: 2.2.2
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: Using a simple API, create xlsx files programmatically
191
+ test_files: []