osheet 0.10.0 → 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.
Files changed (75) hide show
  1. data/Gemfile +0 -1
  2. data/Gemfile.lock +9 -6
  3. data/Rakefile +35 -19
  4. data/bench/bench_runner.rb +91 -0
  5. data/bench/profiler_runner.rb +1 -0
  6. data/examples/basic.rb +1 -1
  7. data/examples/basic.xls +2 -1
  8. data/examples/basic_with_templates.rb +2 -2
  9. data/examples/basic_with_templates.xls +3 -3
  10. data/examples/formats.rb +2 -2
  11. data/examples/formats.xls +46 -46
  12. data/examples/formula.rb +2 -2
  13. data/examples/styles.rb +2 -2
  14. data/examples/styles.xls +5 -5
  15. data/examples/trivial.rb +2 -2
  16. data/lib/osheet/cell.rb +48 -46
  17. data/lib/osheet/column.rb +23 -29
  18. data/lib/osheet/format.rb +3 -3
  19. data/lib/osheet/meta_element.rb +2 -1
  20. data/lib/osheet/mixin.rb +21 -9
  21. data/lib/osheet/partial.rb +5 -9
  22. data/lib/osheet/row.rb +28 -32
  23. data/lib/osheet/style.rb +11 -25
  24. data/lib/osheet/styled_element.rb +9 -1
  25. data/lib/osheet/template.rb +3 -8
  26. data/lib/osheet/version.rb +1 -1
  27. data/lib/osheet/workbook.rb +135 -43
  28. data/lib/osheet/workbook_api.rb +208 -0
  29. data/lib/osheet/workbook_element.rb +225 -8
  30. data/lib/osheet/worksheet.rb +22 -28
  31. data/lib/osheet/xmlss_writer/style_cache.rb +64 -0
  32. data/lib/osheet/xmlss_writer/style_settings.rb +148 -0
  33. data/lib/osheet/xmlss_writer.rb +143 -1
  34. data/lib/osheet.rb +3 -29
  35. data/osheet.gemspec +4 -1
  36. data/test/cell_test.rb +33 -98
  37. data/test/column_test.rb +20 -88
  38. data/test/{mixins.rb → fixtures/mixins.rb} +6 -4
  39. data/test/fixtures/test_writer.rb +68 -0
  40. data/test/format_test.rb +2 -2
  41. data/test/helper.rb +34 -68
  42. data/test/mixin_test.rb +37 -43
  43. data/test/partial_test.rb +3 -26
  44. data/test/row_test.rb +32 -85
  45. data/test/style_test.rb +9 -26
  46. data/test/template_test.rb +5 -6
  47. data/test/workbook_element_test.rb +231 -0
  48. data/test/workbook_test.rb +225 -116
  49. data/test/worksheet_test.rb +51 -98
  50. data/test/xmlss_writer/api_test.rb +139 -0
  51. data/test/xmlss_writer/style_cache_test.rb +65 -0
  52. data/test/xmlss_writer/style_settings_test.rb +263 -0
  53. data/test/xmlss_writer/styles_test.rb +121 -153
  54. data/test/xmlss_writer_test.rb +91 -0
  55. metadata +75 -50
  56. data/lib/osheet/associations.rb +0 -58
  57. data/lib/osheet/instance.rb +0 -30
  58. data/lib/osheet/markup_element.rb +0 -22
  59. data/lib/osheet/partial_set.rb +0 -57
  60. data/lib/osheet/railtie.rb +0 -9
  61. data/lib/osheet/style_set.rb +0 -39
  62. data/lib/osheet/template_set.rb +0 -51
  63. data/lib/osheet/view_handler/rails.rb +0 -44
  64. data/lib/osheet/view_handler/tilt.rb +0 -42
  65. data/lib/osheet/view_handler.rb +0 -2
  66. data/lib/osheet/worksheet_element.rb +0 -17
  67. data/lib/osheet/xmlss_writer/base.rb +0 -49
  68. data/lib/osheet/xmlss_writer/elements.rb +0 -56
  69. data/lib/osheet/xmlss_writer/styles.rb +0 -216
  70. data/test/osheet_test.rb +0 -13
  71. data/test/partial_set_test.rb +0 -64
  72. data/test/style_set_test.rb +0 -47
  73. data/test/template_set_test.rb +0 -74
  74. data/test/xmlss_writer/base_test.rb +0 -103
  75. data/test/xmlss_writer/elements_test.rb +0 -172
@@ -0,0 +1,208 @@
1
+ module Osheet; end
2
+ module Osheet::WorkbookApi
3
+
4
+ # reference API
5
+
6
+ [ :templates,
7
+ :partials,
8
+ :styles
9
+ ].each do |meth|
10
+ define_method(meth) do |*args|
11
+ workbook.send(meth, *args)
12
+ end
13
+ end
14
+
15
+ def worksheets
16
+ workbook.worksheets
17
+ end
18
+
19
+ def columns
20
+ worksheets.last.columns
21
+ end
22
+
23
+ def rows
24
+ worksheets.last.rows
25
+ end
26
+
27
+ def cells
28
+ rows.last.cells
29
+ end
30
+
31
+ # markup handling API
32
+
33
+ def template(*args, &build)
34
+ Osheet::Template.new(*args, &build).tap do |template|
35
+ element_stack.current.template(template)
36
+ end
37
+ end
38
+
39
+ def partial(*args, &build)
40
+ Osheet::Partial.new(*args, &build).tap do |partial|
41
+ element_stack.current.partial(partial)
42
+ end
43
+ end
44
+
45
+ class StyleBuild
46
+
47
+ def initialize(scope, *args, &build)
48
+ @scope = scope
49
+ @style = Osheet::Style.new(*args, &build)
50
+ end
51
+
52
+ def add
53
+ @scope.element_stack.current.style(@style)
54
+ if block_given?
55
+ @scope.element_stack.using(@style) { yield @style.build }
56
+ end
57
+ end
58
+ end
59
+
60
+ def style(*args, &build)
61
+ StyleBuild.new(self, *args, &build).add do |build|
62
+ build.call
63
+ end
64
+ end
65
+
66
+ # element handling API
67
+
68
+ class TemplatedElement
69
+
70
+ # this class is used to create elements that can be templated.
71
+ # Arguments are handled differently if building an element from
72
+ # a template vs. building from a build block.
73
+ # After the element is built, it is added to the current stack element
74
+ # and either the build or writer is called.
75
+
76
+ ELEMENT_CLASS = {
77
+ :worksheet => Osheet::Worksheet,
78
+ :column => Osheet::Column,
79
+ :row => Osheet::Row,
80
+ :cell => Osheet::Cell
81
+ }
82
+
83
+ def initialize(scope, name, *args, &build)
84
+ @scope = scope
85
+ @workbook = @scope.workbook
86
+ @name = name
87
+ @args = args
88
+ if (@template = @workbook.templates.get(@name, @args.first))
89
+ @element = ELEMENT_CLASS[@name].new
90
+ @build = Proc.new { @scope.instance_exec(*@args[1..-1], &@template) }
91
+ else
92
+ @element = ELEMENT_CLASS[@name].new(*@args)
93
+ @build = build || Proc.new {}
94
+ end
95
+ end
96
+
97
+ def add
98
+ @scope.element_stack.current.send(@name, @element)
99
+ @scope.element_stack.using(@element) do
100
+ if @scope.writer
101
+ @scope.writer.send(@name, @element, &@build)
102
+ else
103
+ @build.call
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ # used on: workbook
111
+ def worksheet(*args, &block)
112
+ if args.empty? && block.nil?
113
+ worksheets.last
114
+ else
115
+ TemplatedElement.new(self, :worksheet, *args, &block).add
116
+ end
117
+ end
118
+
119
+ # used on: worksheet
120
+ def column(*args, &block)
121
+ TemplatedElement.new(self, :column, *args, &block).add
122
+ end
123
+
124
+ # used on: worksheet
125
+ def row(*args, &block)
126
+ if args.empty? && block.nil?
127
+ rows.last
128
+ else
129
+ TemplatedElement.new(self, :row, *args, &block).add
130
+ end
131
+ end
132
+
133
+ # used on: row
134
+ def cell(*args, &block)
135
+ if args.empty? && block.nil?
136
+ cells.last
137
+ else
138
+ TemplatedElement.new(self, :cell, *args, &block).add
139
+ end
140
+ end
141
+
142
+ # style attribute API
143
+
144
+ Osheet::Style::SETTINGS.each do |setting|
145
+ define_method(setting) do |*args|
146
+ element_stack.current.send(setting, *args)
147
+ end
148
+ end
149
+
150
+ def border(*args)
151
+ element_stack.current.border(*args)
152
+ end
153
+
154
+ # element attribute API
155
+
156
+ def meta(*args)
157
+ element_stack.current.meta(*args)
158
+ end
159
+
160
+ # on both style_class and format attribute updates
161
+ # call the writer's style hook, passing it the current element's
162
+ # style_class and format values. Both are needed to write
163
+ # an elements style data and style_id.
164
+
165
+ # used by: column, row, cell
166
+ def style_class(value)
167
+ current_class = element_stack.current.style_class(value) # for self referencing
168
+ if self.writer # for writing
169
+ self.writer.style(current_class, element_stack.current.format)
170
+ end
171
+ end
172
+
173
+ # used by: cell
174
+ def format(*args)
175
+ current_format = element_stack.current.format(*args) # for self referencing
176
+ if self.writer # for writing
177
+ self.writer.style(element_stack.current.style_class, current_format)
178
+ end
179
+ end
180
+
181
+ # used by: workbook_element
182
+ def title(*args)
183
+ element_stack.current.title(*args)
184
+ end
185
+
186
+ [ :name, # worksheet
187
+ :width, # column
188
+ :height, # row
189
+ :autofit, # column, row
190
+ :autofit?, # column, row
191
+ :hidden, # column, row
192
+ :hidden?, # column, row
193
+ :data, # cell
194
+ :href, # cell
195
+ :formula, # cell
196
+ :index, # cell
197
+ :rowspan, # cell
198
+ :colspan, # cell
199
+ ].each do |meth|
200
+ define_method(meth) do |*args|
201
+ element_stack.current.send(meth, *args) # for self referencing
202
+ if self.writer # for writing
203
+ self.writer.send(meth, *args)
204
+ end
205
+ end
206
+ end
207
+
208
+ end
@@ -1,17 +1,234 @@
1
- module Osheet::WorkbookElement
1
+ require 'osheet/style'
2
+ require 'osheet/worksheet'
3
+
4
+ module Osheet
5
+
6
+
7
+ class WorkbookElement
8
+
9
+ # This 'WorkbookElement' class handles all workbook state. It is setup
10
+ # and referenced by the 'Workbook' class when it runs builds
11
+
12
+ class PartialSet < ::Hash; end
13
+ class TemplateSet < PartialSet; end
14
+ class StyleSet < ::Array; end
15
+ class WorksheetSet < ::Array; end
16
+
17
+ attr_reader :title
18
+ attr_reader :templates, :partials, :styles, :worksheets
19
+
20
+ def initialize
21
+ @title = nil
22
+
23
+ @templates = TemplateSet.new
24
+ @partials = PartialSet.new
25
+ @styles = StyleSet.new
26
+ @worksheets = WorksheetSet.new
27
+ end
28
+
29
+ def title(value=nil)
30
+ value.nil? ? @title : @title = value.to_s
31
+ end
32
+
33
+ def template(template)
34
+ @templates << template
35
+ end
36
+
37
+ def partial(partial)
38
+ @partials << partial
39
+ end
40
+
41
+ def style(style)
42
+ @styles << style
43
+ end
44
+
45
+ def worksheet(worksheet)
46
+ @worksheets << worksheet
47
+ end
48
+
49
+ def styles(*args)
50
+ @styles.for(*args)
51
+ end
52
+
53
+ def ==(other)
54
+ title == other.title &&
55
+ templates == other.templates &&
56
+ partials == other.partials &&
57
+ styles == other.styles &&
58
+ worksheets == other.worksheets
59
+ end
60
+
61
+ end
62
+
63
+
64
+
65
+ class WorkbookElement::PartialSet
66
+
67
+ # this class is a Hash that behaves kinda like a set. I want to
68
+ # push partials into the set using the '<<' operator, only allow
69
+ # Osheet::Partial objs to be pushed, and then be able to reference
70
+ # a particular partial using its name
71
+
72
+ def initialize
73
+ super
74
+ end
75
+
76
+ def <<(partial)
77
+ if (key = verify(partial))
78
+ push(key, partial)
79
+ end
80
+ end
81
+
82
+ # return the named partial
83
+ def get(name)
84
+ lookup(key(name.to_s))
85
+ end
86
+
87
+ private
88
+
89
+ def lookup(key)
90
+ self[key]
91
+ end
92
+
93
+ # push the partial onto the key
94
+ def push(key, partial)
95
+ self[key] = partial
96
+ end
97
+
98
+ # verify the partial, init and return the key
99
+ # otherwise ArgumentError it up
100
+ def verify(partial)
101
+ unless partial.kind_of?(Partial)
102
+ raise ArgumentError, 'you can only push Osheet::Partial objs to the partial set'
103
+ end
104
+ pkey = partial_key(partial)
105
+ self[pkey] ||= nil
106
+ pkey
107
+ end
108
+
109
+ def partial_key(partial)
110
+ key(partial.instance_variable_get("@name"))
111
+ end
112
+
113
+ def key(name)
114
+ name.to_s
115
+ end
116
+
117
+ end
118
+
119
+
120
+
121
+ class WorkbookElement::TemplateSet < WorkbookElement::PartialSet
122
+
123
+ # this class is a PartialSet that keys off of the template element
124
+ # and name. Only Osheet::Template objs can be pushed, and you reference
125
+ # a particular template by its element and name
126
+
127
+ def initialize
128
+ super
129
+ end
130
+
131
+ # return the template set for the named element
132
+ def get(element, name)
133
+ lookup(key(element.to_s, name.to_s))
134
+ end
135
+
136
+ private
137
+
138
+ def lookup(key)
139
+ self[key.first][key.last] if self[key.first]
140
+ end
141
+
142
+ # push the template onto the key
143
+ def push(key, template)
144
+ self[key.first][key.last] = template
145
+ end
146
+
147
+ # verify the template, init the key set, and return the key string
148
+ # otherwise ArgumentError it up
149
+ def verify(template)
150
+ unless template.kind_of?(Template)
151
+ raise ArgumentError, 'you can only push Osheet::Template objs to the template set'
152
+ end
153
+ key = template_key(template)
154
+ self[key.first] ||= {}
155
+ self[key.first][key.last] ||= nil
156
+ key
157
+ end
158
+
159
+ def template_key(template)
160
+ key(template.instance_variable_get("@element"), template.instance_variable_get("@name"))
161
+ end
162
+
163
+ def key(element, name)
164
+ [element.to_s, name.to_s]
165
+ end
2
166
 
3
- def workbook
4
- get_ivar(:workbook)
5
167
  end
6
168
 
7
- [:styles, :templates].each do |thing|
8
- define_method(thing) do
9
- if workbook && workbook.respond_to?(thing)
10
- workbook.send(thing)
169
+
170
+
171
+ class WorkbookElement::StyleSet < ::Array
172
+
173
+ # this class is an Array with some helper methods. I want to
174
+ # push styles into the set using the '<<' operator, only allow
175
+ # Osheet::Style objs to be pushed, and then be able to reference
176
+ # a particular set of styles using a style class.
177
+
178
+ def initialize
179
+ super
180
+ end
181
+
182
+ def <<(value)
183
+ super if verify(value)
184
+ end
185
+
186
+ # return the style set for the style class
187
+ def for(style_class=nil)
188
+ style_class.nil? ? self : self.select{|s| s.match?(style_class)}
189
+ end
190
+
191
+ private
192
+
193
+ # verify the style, otherwise ArgumentError it up
194
+ def verify(style)
195
+ if style.kind_of?(Style)
196
+ true
197
+ else
198
+ raise ArgumentError, 'you can only push Osheet::Style objs to the style set'
199
+ end
200
+ end
201
+
202
+ end
203
+
204
+
205
+
206
+ class WorkbookElement::WorksheetSet < ::Array
207
+
208
+ # this class is just a wrapper to Array. I want to push worksheets
209
+ # into the set using the '<<' operator, but only allow Worksheet objs
210
+ # to be pushed.
211
+
212
+ def initialize
213
+ super
214
+ end
215
+
216
+ def <<(value)
217
+ super if verify(value)
218
+ end
219
+
220
+ private
221
+
222
+ # verify the worksheet, otherwise ArgumentError it up
223
+ def verify(worksheet)
224
+ if worksheet.kind_of?(Worksheet)
225
+ true
11
226
  else
12
- nil
227
+ raise ArgumentError, 'can only push Osheet::Worksheet to the set'
13
228
  end
14
229
  end
230
+
15
231
  end
16
232
 
233
+
17
234
  end
@@ -1,44 +1,38 @@
1
+ require 'osheet/meta_element'
1
2
  require 'osheet/column'
2
3
  require 'osheet/row'
3
4
 
5
+ # this class is collects and validates worksheet meta-data. It allows
6
+ # for storing a set of columns for referencing when building rows. It is
7
+ # up to the writer to take this data and use it as needed.
8
+
4
9
  module Osheet
5
10
  class Worksheet
6
- include Instance
7
- include Associations
8
- include WorkbookElement
11
+
9
12
  include MetaElement
10
- include MarkupElement
11
-
12
- hm :columns
13
- hm :rows
14
-
15
- def initialize(workbook=nil, *args, &block)
16
- set_ivar(:workbook, workbook)
17
- set_ivar(:name, nil)
18
- if block_given?
19
- set_binding_ivars(block.binding)
20
- instance_exec(*args, &block)
21
- end
13
+
14
+ attr_reader :columns, :rows
15
+
16
+ def initialize(name=nil, *args)
17
+ @name = name
18
+ @columns = []
19
+ @rows = []
22
20
  end
23
21
 
24
22
  def name(value=nil)
25
- !value.nil? ? set_ivar(:name, sanitized_name(value)) : get_ivar(:name)
23
+ value.nil? ? @name : @name = value.to_s
26
24
  end
27
25
 
28
- def attributes
29
- { :name => get_ivar(:name) }
26
+ def column(column_obj)
27
+ @columns << column_obj
30
28
  end
31
29
 
32
- private
33
-
34
- def sanitized_name(name_value)
35
- if get_ivar(:workbook) && get_ivar(:workbook).worksheets.collect{|ws| ws.name}.include?(name_value)
36
- raise ArgumentError, "the sheet name '#{name_value}' is already in use. choose a sheet name that is not used by another sheet"
37
- end
38
- if name_value.to_s.length > 31
39
- raise ArgumentError, "worksheet names must be less than 32 characters long"
40
- end
41
- name_value.to_s
30
+ # Osheet only stores the latest row in memory for reference
31
+ # memory bloat would be unmanageable in large spreadsheets if
32
+ # all rows were stored
33
+ def row(row_obj)
34
+ @rows.pop
35
+ @rows << row_obj
42
36
  end
43
37
 
44
38
  end
@@ -0,0 +1,64 @@
1
+ require 'osheet/style'
2
+ require 'osheet/xmlss_writer'
3
+ require 'osheet/xmlss_writer/style_settings'
4
+
5
+ class Osheet::XmlssWriter
6
+ class StyleCache
7
+
8
+ attr_reader :styles
9
+
10
+ def initialize(osheet_workbook, xmlss_workbook)
11
+ @osheet_workbook = osheet_workbook
12
+ @xmlss_workbook = xmlss_workbook
13
+ @styles = {}
14
+ end
15
+
16
+ def [](key); @styles[key]; end
17
+ def empty?; @styles.empty?; end
18
+ def size; @styles.keys.size; end
19
+
20
+ def keys
21
+ @styles.keys
22
+ end
23
+
24
+ def get(style_class, format)
25
+ # generate the style key and get the get the cached style or
26
+ # build and cache and return a style for the key
27
+ return nil if (key = self.key(style_class, format.key)).empty?
28
+ @styles[key] ||
29
+ build_and_cache(key, @osheet_workbook.styles.for(style_class), format)
30
+ end
31
+
32
+ protected
33
+
34
+ # build a unique key for styling based off the style and format keys
35
+ def key(class_value, format_key)
36
+ (class_value || '').strip.split(/\s+/).collect do |c|
37
+ ".#{c}"
38
+ end.join('') + (format_key.nil? || format_key.empty? ? '' : "..#{format_key}")
39
+ end
40
+
41
+ # build and cache an xmlss style
42
+ def build_and_cache(key, styles, format)
43
+ settings = StyleSettings.new(styles)
44
+ @styles[key] = @xmlss_workbook.style(key) {
45
+ settings.setting(:align) { @xmlss_workbook.alignment(settings[:align]) }
46
+ settings.setting(:font) { @xmlss_workbook.font(settings[:font]) }
47
+ settings.setting(:bg) { @xmlss_workbook.interior(settings[:bg]) }
48
+
49
+ border_set = ::Osheet::Style::BORDERS.inject([]) do |set, bp|
50
+ settings.setting(bp) { set << settings[bp] }
51
+ set
52
+ end
53
+ if !border_set.empty?
54
+ @xmlss_workbook.borders {
55
+ border_set.each { |setting| @xmlss_workbook.border(setting) }
56
+ }
57
+ end
58
+
59
+ @xmlss_workbook.number_format(format.style)
60
+ }
61
+ end
62
+
63
+ end
64
+ end