osheet-xmlss 1.0.0.rc.1

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.
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
3
+ <Styles>
4
+ <Style ss:ID="..currency_dollar_2_comma_black">
5
+ <NumberFormat ss:Format="&quot;$&quot;#,##0.00" />
6
+ </Style>
7
+ </Styles>
8
+ <Worksheet ss:Name="one dollar">
9
+ <Table>
10
+ <Column />
11
+ <Row>
12
+ <Cell ss:StyleID="..currency_dollar_2_comma_black">
13
+ <Data ss:Type="Number">1</Data>
14
+ </Cell>
15
+ </Row>
16
+ </Table>
17
+ </Worksheet>
18
+ </Workbook>
@@ -0,0 +1,63 @@
1
+ require 'osheet/style'
2
+ require 'osheet/xmlss/style_settings'
3
+
4
+ module Osheet::Xmlss
5
+ class StyleCache
6
+
7
+ attr_reader :styles
8
+
9
+ def initialize(osheet_workbook, xmlss_workbook)
10
+ @osheet_workbook = osheet_workbook
11
+ @xmlss_workbook = xmlss_workbook
12
+ @styles = {}
13
+ end
14
+
15
+ def [](key); @styles[key]; end
16
+ def empty?; @styles.empty?; end
17
+ def size; @styles.keys.size; end
18
+
19
+ def keys
20
+ @styles.keys
21
+ end
22
+
23
+ def get(style_class, format)
24
+ # generate the style key and get the get the cached style or
25
+ # build and cache and return a style for the key
26
+ return nil if (key = self.key(style_class, format.key)).empty?
27
+ @styles[key] ||
28
+ build_and_cache(key, @osheet_workbook.styles.for(style_class), format)
29
+ end
30
+
31
+ # build a unique key for styling based off the style and format keys
32
+ def key(class_value, format_key)
33
+ (class_value || '').strip.split(/\s+/).collect do |c|
34
+ ".#{c}"
35
+ end.join('') + (format_key.nil? || format_key.empty? ? '' : "..#{format_key}")
36
+ end
37
+
38
+ protected
39
+
40
+ # build and cache an xmlss style
41
+ def build_and_cache(key, styles, format)
42
+ settings = StyleSettings.new(styles)
43
+ @styles[key] = @xmlss_workbook.style(key) {
44
+ settings.setting(:align) { @xmlss_workbook.alignment(settings[:align]) }
45
+ settings.setting(:font) { @xmlss_workbook.font(settings[:font]) }
46
+ settings.setting(:bg) { @xmlss_workbook.interior(settings[:bg]) }
47
+
48
+ border_set = ::Osheet::Style::BORDERS.inject([]) do |set, bp|
49
+ settings.setting(bp) { set << settings[bp] }
50
+ set
51
+ end
52
+ if !border_set.empty?
53
+ @xmlss_workbook.borders {
54
+ border_set.each { |setting| @xmlss_workbook.border(setting) }
55
+ }
56
+ end
57
+
58
+ @xmlss_workbook.number_format(format.style)
59
+ }
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,148 @@
1
+ require 'osheet/style'
2
+ require 'osheet/xmlss_writer'
3
+
4
+ module Osheet::Xmlss
5
+ class StyleSettings
6
+
7
+ attr_reader :styles, :value
8
+
9
+ def initialize(styles)
10
+ @styles = styles
11
+ @value = @styles.inject({}) do |value, style|
12
+ merged_settings(value, style_settings(style))
13
+ end
14
+ end
15
+
16
+ def [](setting); @value[setting]; end
17
+
18
+ # call the given block passing it the setting if that setting
19
+ # exists and is not empty
20
+ def setting(s, &block)
21
+ block.call if self.value[s] && !self.value[s].empty?
22
+ end
23
+
24
+ protected
25
+
26
+ def merged_settings(current, add)
27
+ # concat values for keys in both sets
28
+ current.keys.each do |k|
29
+ current[k].merge!(add.delete(k) || {})
30
+ end
31
+ # merge keys for anything not in the current
32
+ current.merge(add)
33
+ end
34
+
35
+ def style_settings(osheet_style_obj)
36
+ settings = {}
37
+ Osheet::Style::SETTINGS.each do |setting|
38
+ if !(v = osheet_style_obj.send(setting)).empty?
39
+ settings[setting] = self.send("#{setting}_settings", v)
40
+ end
41
+ end
42
+ settings
43
+ end
44
+
45
+ def align_settings(osheet_align_directives)
46
+ osheet_align_directives.inject({}) do |settings, directive|
47
+ case directive
48
+ when :left, :center, :right
49
+ settings[:horizontal] = directive
50
+ when :top, :bottom
51
+ settings[:vertical] = directive
52
+ when :middle
53
+ settings[:vertical] = :center
54
+ when :wrap
55
+ settings[:wrap_text] = true
56
+ when ::Fixnum
57
+ settings[:rotate] = directive
58
+ end
59
+ settings
60
+ end
61
+ end
62
+
63
+ def font_settings(osheet_font_directives)
64
+ osheet_font_directives.inject({}) do |settings, directive|
65
+ case directive
66
+ when ::Fixnum
67
+ settings[:size] = directive
68
+ when ::String
69
+ if directive =~ /^#/
70
+ settings[:color] = directive
71
+ else
72
+ settings[:name] = directive
73
+ end
74
+ when :bold, :italic, :shadow
75
+ settings[directive] = true
76
+ when :subscript, :superscript
77
+ settings[:alignment] = directive
78
+ when :strikethrough
79
+ settings[:strike_through] = true
80
+ when :underline
81
+ settings[:underline] = :single
82
+ when :double_underline
83
+ settings[:underline] = :double
84
+ when :accounting_underline
85
+ settings[:underline] = :single_accounting
86
+ when :double_accounting_underline
87
+ settings[:underline] = :double_accounting
88
+ end
89
+ settings
90
+ end
91
+ end
92
+
93
+ def bg_settings(osheet_bg_directives)
94
+ osheet_bg_directives.inject({}) do |settings, directive|
95
+ case directive
96
+ when ::String
97
+ if directive =~ /^#/
98
+ settings[:color] = directive
99
+ end
100
+ when ::Symbol
101
+ if ::Xmlss::Style::Interior.pattern_set.include?(directive)
102
+ settings[:pattern] = directive
103
+ end
104
+ when ::Hash
105
+ directive.each do |pattern, color|
106
+ if ::Xmlss::Style::Interior.pattern_set.include?(pattern) && color =~ /^#/
107
+ settings[:pattern] = pattern
108
+ settings[:pattern_color] = color
109
+ end
110
+ end
111
+ end
112
+
113
+ if !settings[:color].nil? && settings[:pattern].nil?
114
+ settings[:pattern] = :solid
115
+ end
116
+
117
+ settings
118
+ end
119
+ end
120
+
121
+ def border_settings(osheet_border_directives)
122
+ osheet_border_directives.inject({}) do |settings, directive|
123
+ case directive
124
+ when ::String
125
+ if directive =~ /^#/
126
+ settings[:color] = directive
127
+ end
128
+ when ::Symbol
129
+ if ::Xmlss::Style::Border.position_set.include?(directive)
130
+ settings[:position] = directive
131
+ elsif ::Xmlss::Style::Border.weight_set.include?(directive)
132
+ settings[:weight] = directive
133
+ elsif ::Xmlss::Style::Border.line_style_set.include?(directive)
134
+ settings[:line_style] = directive
135
+ end
136
+ end
137
+ settings
138
+ end
139
+ end
140
+
141
+ ::Osheet::Style::BORDER_POSITIONS.each do |p|
142
+ define_method("border_#{p}_settings") do |cmds|
143
+ border_settings(cmds+[p])
144
+ end
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,4 @@
1
+ module Osheet;end
2
+ module Osheet::Xmlss
3
+ VERSION = "1.0.0.rc.1"
4
+ end
@@ -0,0 +1,7 @@
1
+ require 'osheet'
2
+ require 'xmlss'
3
+ require 'osheet/xmlss_writer'
4
+
5
+ module Osheet; end
6
+ module Osheet::Xmlss; end
7
+
@@ -0,0 +1,143 @@
1
+ require 'osheet'
2
+ require 'xmlss'
3
+
4
+ require 'osheet/xmlss/style_cache'
5
+
6
+ module Osheet
7
+ class XmlssWriter
8
+
9
+ # The Osheet::XmlssWriter provides the logic/translation needed to drive
10
+ # an xmlss workbook using the Osheet API. The writer creates an xmlss
11
+ # workbook and uses the builder approach to drive the workbook creation.
12
+
13
+ # The writer maintains a set of used xmlss styles and handles creating
14
+ # xmlss style objects as needed and manages style keys
15
+
16
+ attr_reader :style_cache, :xmlss_workbook
17
+
18
+ def initialize(*args, &block)
19
+ @xmlss_workbook = ::Xmlss::Workbook.new(::Xmlss::Writer.new(*args, &block))
20
+ @osheet_workbook = nil
21
+ @osheet_worksheet_names = []
22
+ @style_cache = nil
23
+ end
24
+
25
+ def bind(osheet_workbook)
26
+ @osheet_workbook = osheet_workbook
27
+ @style_cache = Osheet::Xmlss::StyleCache.new(@osheet_workbook, @xmlss_workbook)
28
+ end
29
+
30
+ def to_s
31
+ @xmlss_workbook.to_s
32
+ end
33
+ alias_method :to_data, :to_s
34
+
35
+ def to_file(file_path)
36
+ @xmlss_workbook.to_file(file_path)
37
+ end
38
+
39
+ # Element writers
40
+
41
+ def worksheet(worksheet, &build)
42
+ if @osheet_workbook && @osheet_worksheet_names.include?(worksheet.name.to_s)
43
+ raise ArgumentError, "you can't write two worksheets with the same name ('#{worksheet.name}')"
44
+ end
45
+ @osheet_worksheet_names << worksheet.name.to_s
46
+ @xmlss_workbook.worksheet(worksheet.name, &build)
47
+ end
48
+
49
+ def column(column, &build)
50
+ attrs = {
51
+ :width => column.width,
52
+ :auto_fit_width => column.autofit,
53
+ :hidden => column.hidden
54
+ }
55
+ if s = @style_cache.get(column.style_class, column.format)
56
+ attrs[:style_id] = s.id
57
+ end
58
+ @xmlss_workbook.column(attrs, &build)
59
+ end
60
+
61
+ def row(row, &build)
62
+ attrs = {
63
+ :height => row.height,
64
+ :auto_fit_height => row.autofit,
65
+ :hidden => row.hidden
66
+ }
67
+ if s = @style_cache.get(row.style_class, row.format)
68
+ attrs[:style_id] = s.id
69
+ end
70
+ @xmlss_workbook.row(attrs, &build)
71
+ end
72
+
73
+ def cell(cell, &build)
74
+ attrs = {
75
+ :href => cell.href,
76
+ :index => cell.index,
77
+ :merge_across => cell_merge(cell.colspan),
78
+ :merge_down => cell_merge(cell.rowspan),
79
+ :formula => cell.formula
80
+ }
81
+ if s = @style_cache.get(cell.style_class, cell.format)
82
+ attrs[:style_id] = s.id
83
+ end
84
+ @xmlss_workbook.cell(cell.data, attrs, &build)
85
+ end
86
+
87
+ # Element style writers
88
+
89
+ # given an elements style_class or format attributes:
90
+ # 1) write a new xmlss style object and
91
+ # 2) set the current element's style_id attribute
92
+
93
+ def style(style_class, format=nil)
94
+ @style_cache.get(
95
+ style_class,
96
+ format || Osheet::Format.new(:general)
97
+ ).tap do |xmlss_style|
98
+ @xmlss_workbook.style_id(xmlss_style.id) if xmlss_style
99
+ end
100
+ end
101
+
102
+ def colspan(count)
103
+ @xmlss_workbook.merge_across(cell_merge(count))
104
+ end
105
+
106
+ def rowspan(count)
107
+ @xmlss_workbook.merge_down(cell_merge(count))
108
+ end
109
+
110
+ def name(value)
111
+ @osheet_worksheet_names.pop
112
+ @osheet_worksheet_names << value
113
+ @xmlss_workbook.name(value)
114
+ end
115
+
116
+ # Element attribute writers
117
+
118
+ [ :width, # column
119
+ :height, # row
120
+ :autofit, # column, row
121
+ :autofit?, # column, row
122
+ :hidden, # column, row
123
+ :hidden?, # column, row
124
+ :data, # cell
125
+ :format, # cell
126
+ :href, # cell
127
+ :formula, # cell
128
+ :index # cell
129
+ ].each do |meth|
130
+ define_method(meth) do |*args, &block|
131
+ @xmlss_workbook.send(meth, *args, &block)
132
+ end
133
+ end
134
+
135
+ protected
136
+
137
+ # convert osheet col/row span value to xmlss merge value
138
+ def cell_merge(span)
139
+ span.kind_of?(::Fixnum) && span > 1 ? span-1 : 0
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/osheet/xmlss/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "osheet-xmlss"
6
+ gem.version = Osheet::Xmlss::VERSION
7
+ gem.description = %q{An Osheet writer (https://github.com/kellyredding/osheet) for the XMLSS format (https://github.com/kellyredding/xmlss)}
8
+ gem.summary = %q{An Osheet writer (https://github.com/kellyredding/osheet) for the XMLSS format (https://github.com/kellyredding/xmlss)}
9
+
10
+ gem.authors = ["Kelly Redding"]
11
+ gem.email = ["kelly@kellyredding.com"]
12
+ gem.homepage = "http://github.com/kellyredding/osheet-xmlss"
13
+
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_development_dependency("assert")
20
+
21
+ gem.add_dependency("osheet", ["~> 1.0.0.rc.2"])
22
+ gem.add_dependency("xmlss", ["~> 1.0.0.rc"])
23
+
24
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ # this file is automatically required in when you require 'assert' in your tests
2
+ # put test helpers here
3
+
4
+ # add root dir to the load path
5
+ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
6
+
7
+ class Assert::Context
8
+
9
+ def xmlss_style_markup(writer)
10
+ Xmlss::Workbook.writer(writer.xmlss_workbook).styles_markup.flush.to_s
11
+ end
12
+
13
+ def xmlss_element_markup(writer)
14
+ Xmlss::Workbook.writer(writer.xmlss_workbook).worksheets_markup.flush.to_s
15
+ end
16
+
17
+ end
data/test/irb.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'assert/setup'
2
+
3
+ # this file is required in when the 'irb' rake test is run.
4
+ # b/c 'assert/setup' is required above, the test helper will be
5
+ # required in as well.
6
+
7
+ # put any IRB setup code here
8
+
9
+ require 'osheet/xmlss'
@@ -0,0 +1,139 @@
1
+ require "assert"
2
+
3
+ require 'osheet/xmlss_writer'
4
+
5
+ module Osheet::Xmlss
6
+
7
+ class ApiTests < Assert::Context
8
+ before do
9
+ @writer = Osheet::XmlssWriter.new
10
+ @workbook = Osheet::Workbook.new(@writer)
11
+ end
12
+ subject { @writer }
13
+
14
+ end
15
+
16
+ class CellTests < ApiTests
17
+ desc "when writing a cell"
18
+ before do
19
+ @cell = Osheet::Cell.new(100)
20
+ @cell.style_class "awesome thing"
21
+ @cell.format :number
22
+ @cell.href 'http://example.com'
23
+ @cell.rowspan 2
24
+ @cell.colspan 5
25
+ @cell.index 3
26
+ @cell.formula "=R1C1"
27
+ @xmlss_cell = subject.cell(@cell)
28
+ end
29
+
30
+ should "create an Xmlss::Cell with correct attributes" do
31
+ assert_kind_of ::Xmlss::Element::Cell, @xmlss_cell
32
+ assert_equal 100, @xmlss_cell.data
33
+ assert_equal 'http://example.com', @xmlss_cell.href
34
+ assert_equal 3, @xmlss_cell.index
35
+ assert_equal "=R1C1", @xmlss_cell.formula
36
+ assert_equal 1, @xmlss_cell.merge_down
37
+ assert_equal 4, @xmlss_cell.merge_across
38
+ end
39
+
40
+ should "style the cell" do
41
+ assert_equal ".awesome.thing..number_none_0_nocomma_black", @xmlss_cell.style_id
42
+ assert_equal 1, subject.style_cache.size
43
+ end
44
+
45
+ should "write cell element markup" do
46
+ assert_equal(
47
+ "<Cell ss:Formula=\"=R1C1\" ss:HRef=\"http://example.com\" ss:Index=\"3\" ss:MergeAcross=\"4\" ss:MergeDown=\"1\" ss:StyleID=\".awesome.thing..number_none_0_nocomma_black\"><Data ss:Type=\"Number\">100</Data></Cell>",
48
+ xmlss_element_markup(subject)
49
+ )
50
+ end
51
+
52
+ end
53
+
54
+ class RowTests < ApiTests
55
+ desc "when writing a row"
56
+ before do
57
+ @row = Osheet::Row.new
58
+ @row.style_class "awesome thing"
59
+ @row.height 100
60
+ @row.autofit true
61
+ @row.hidden true
62
+ @xmlss_row = subject.row(@row)
63
+ end
64
+
65
+ should "create an Xmlss::Row with correct attributes" do
66
+ assert_kind_of ::Xmlss::Element::Row, @xmlss_row
67
+ assert_equal 100, @xmlss_row.height
68
+ assert_equal true, @xmlss_row.auto_fit_height
69
+ assert_equal true, @xmlss_row.hidden
70
+ end
71
+
72
+ should "style the row" do
73
+ assert_equal ".awesome.thing", @xmlss_row.style_id
74
+ assert_equal 1, subject.style_cache.size
75
+ end
76
+
77
+ should "write row element markup" do
78
+ assert_equal(
79
+ "<Row ss:AutoFitHeight=\"1\" ss:Height=\"100\" ss:Hidden=\"1\" ss:StyleID=\".awesome.thing\" />",
80
+ xmlss_element_markup(subject)
81
+ )
82
+ end
83
+
84
+ end
85
+
86
+ class ColumnTests < ApiTests
87
+ desc "when writing a column"
88
+ before do
89
+ @column = Osheet::Column.new
90
+ @column.style_class "awesome"
91
+ @column.width 100
92
+ @column.autofit true
93
+ @column.hidden true
94
+ @xmlss_column = subject.column(@column)
95
+ end
96
+
97
+ should "create an Xmlss::Column with correct attributes" do
98
+ assert_kind_of ::Xmlss::Element::Column, @xmlss_column
99
+ assert_equal 100, @xmlss_column.width
100
+ assert_equal true, @xmlss_column.auto_fit_width
101
+ assert_equal true, @xmlss_column.hidden
102
+ end
103
+
104
+ should "style an Xmlss column" do
105
+ assert_equal ".awesome", @xmlss_column.style_id
106
+ assert_equal 1, subject.style_cache.size
107
+ end
108
+
109
+ should "write column element markup" do
110
+ assert_equal(
111
+ "<Column ss:AutoFitWidth=\"1\" ss:Hidden=\"1\" ss:StyleID=\".awesome\" ss:Width=\"100\" />",
112
+ xmlss_element_markup(subject)
113
+ )
114
+ end
115
+
116
+ end
117
+
118
+ class WorksheetTests < ApiTests
119
+ desc "when writing a worksheet"
120
+ before do
121
+ @worksheet = Osheet::Worksheet.new("testsheet2")
122
+ @xmlss_worksheet = subject.worksheet(@worksheet)
123
+ end
124
+
125
+ should "create an Xmlss::Worksheet with correct attributes" do
126
+ assert_kind_of ::Xmlss::Element::Worksheet, @xmlss_worksheet
127
+ assert_equal "testsheet2", @xmlss_worksheet.name
128
+ end
129
+
130
+ should "write worksheet element markup" do
131
+ assert_equal(
132
+ "<Worksheet ss:Name=\"testsheet2\"><Table /></Worksheet>",
133
+ xmlss_element_markup(subject)
134
+ )
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -0,0 +1,65 @@
1
+ require "assert"
2
+
3
+ require 'osheet/xmlss/style_cache'
4
+
5
+ module Osheet::Xmlss
6
+
7
+ class StyleCacheTests < Assert::Context
8
+ desc "the style cache"
9
+ before do
10
+ @workbook = Osheet::Workbook.new
11
+ @workbook.style('.align.center') { @workbook.align :center }
12
+ @workbook.style('.font.size') { @workbook.font 14 }
13
+ @workbook.style('.font.weight') { @workbook.font :bold }
14
+ @workbook.style('.font.style') { @workbook.font :italic }
15
+ @workbook.style('.bg.color') { @workbook.bg '#FF0000' }
16
+ @workbook.style('.border.color') { @workbook.border '#FF0000', :thin }
17
+ @xmlss_workbook = ::Xmlss::Workbook.new(Xmlss::Writer.new)
18
+
19
+ @cache = StyleCache.new(@workbook, @xmlss_workbook)
20
+ end
21
+ subject { @cache }
22
+
23
+ should have_reader :styles
24
+ should have_instance_method :get, :keys, :empty?, :size, :[]
25
+
26
+ should "have no cached styles by default" do
27
+ assert_empty subject
28
+ end
29
+
30
+ should "key based off class value and format key" do
31
+ assert_equal '', subject.send(:key, '', nil)
32
+ assert_equal '.awesome', subject.send(:key, 'awesome', nil)
33
+ assert_equal '.awesome.thing', subject.send(:key, 'awesome thing', nil)
34
+ assert_equal '.awesome..something', subject.send(:key, 'awesome', 'something')
35
+ assert_equal '..something', subject.send(:key, '', 'something')
36
+ end
37
+
38
+ should "return nil if trying to get the style for an empty class and general format" do
39
+ assert_not_nil subject.get('font', Osheet::Format.new(:general))
40
+ assert_nil subject.get('', Osheet::Format.new(:general))
41
+ end
42
+
43
+ should "build and cache styles if not already cached" do
44
+ assert_equal 0, subject.size
45
+ subject.get('font', Osheet::Format.new(:currency))
46
+ assert_equal 1, subject.size
47
+ end
48
+
49
+ should "return cached style if requesting an already cached style" do
50
+ assert_equal 0, subject.size
51
+ subject.get('font', Osheet::Format.new(:currency))
52
+ subject.get('font', Osheet::Format.new(:currency))
53
+ assert_equal 1, subject.size
54
+ end
55
+
56
+ should "build styles with ids matching the cache key" do
57
+ key = subject.key('font', Osheet::Format.new(:currency).key)
58
+ assert_equal key, subject.get('font', Osheet::Format.new(:currency)).id
59
+ end
60
+
61
+ end
62
+
63
+
64
+
65
+ end