osheet 0.10.0 → 1.0.0.rc.1

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