axlsx 1.0.16 → 1.1.0

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 (173) hide show
  1. data/.yardopts +1 -0
  2. data/CHANGELOG.md +18 -3
  3. data/README.md +189 -55
  4. data/examples/example.csv +1000 -0
  5. data/examples/example.rb +264 -142
  6. data/examples/example.xlsx +0 -0
  7. data/examples/example_streamed.xlsx +0 -0
  8. data/examples/no-use_autowidth.xlsx +0 -0
  9. data/examples/shared_strings_example.xlsx +0 -0
  10. data/lib/axlsx/content_type/content_type.rb +13 -12
  11. data/lib/axlsx/content_type/default.rb +10 -6
  12. data/lib/axlsx/content_type/override.rb +13 -8
  13. data/lib/axlsx/doc_props/app.rb +38 -41
  14. data/lib/axlsx/doc_props/core.rb +13 -17
  15. data/lib/axlsx/drawing/axis.rb +39 -19
  16. data/lib/axlsx/drawing/bar_3D_chart.rb +34 -32
  17. data/lib/axlsx/drawing/bar_series.rb +14 -14
  18. data/lib/axlsx/drawing/cat_axis.rb +16 -14
  19. data/lib/axlsx/drawing/cat_axis_data.rb +17 -18
  20. data/lib/axlsx/drawing/chart.rb +40 -41
  21. data/lib/axlsx/drawing/drawing.rb +16 -12
  22. data/lib/axlsx/drawing/graphic_frame.rb +22 -21
  23. data/lib/axlsx/drawing/hyperlink.rb +13 -12
  24. data/lib/axlsx/drawing/line_3D_chart.rb +31 -28
  25. data/lib/axlsx/drawing/line_series.rb +12 -11
  26. data/lib/axlsx/drawing/marker.rb +11 -8
  27. data/lib/axlsx/drawing/named_axis_data.rb +36 -0
  28. data/lib/axlsx/drawing/one_cell_anchor.rb +18 -16
  29. data/lib/axlsx/drawing/pic.rb +26 -38
  30. data/lib/axlsx/drawing/picture_locking.rb +22 -18
  31. data/lib/axlsx/drawing/pie_3D_chart.rb +11 -8
  32. data/lib/axlsx/drawing/pie_series.rb +16 -12
  33. data/lib/axlsx/drawing/scaling.rb +11 -10
  34. data/lib/axlsx/drawing/scatter_chart.rb +69 -0
  35. data/lib/axlsx/drawing/scatter_series.rb +39 -0
  36. data/lib/axlsx/drawing/ser_axis.rb +11 -10
  37. data/lib/axlsx/drawing/series.rb +16 -15
  38. data/lib/axlsx/drawing/series_title.rb +15 -14
  39. data/lib/axlsx/drawing/title.rb +27 -26
  40. data/lib/axlsx/drawing/two_cell_anchor.rb +19 -20
  41. data/lib/axlsx/drawing/val_axis.rb +9 -7
  42. data/lib/axlsx/drawing/val_axis_data.rb +18 -17
  43. data/lib/axlsx/drawing/view_3D.rb +23 -20
  44. data/lib/axlsx/package.rb +116 -51
  45. data/lib/axlsx/rels/relationship.rb +12 -8
  46. data/lib/axlsx/rels/relationships.rb +8 -1
  47. data/lib/axlsx/stylesheet/#num_fmt.rb# +69 -0
  48. data/lib/axlsx/stylesheet/border.rb +28 -23
  49. data/lib/axlsx/stylesheet/border_pr.rb +17 -15
  50. data/lib/axlsx/stylesheet/cell_alignment.rb +24 -21
  51. data/lib/axlsx/stylesheet/cell_protection.rb +11 -7
  52. data/lib/axlsx/stylesheet/cell_style.rb +9 -5
  53. data/lib/axlsx/stylesheet/color.rb +21 -14
  54. data/lib/axlsx/stylesheet/fill.rb +8 -5
  55. data/lib/axlsx/stylesheet/font.rb +22 -14
  56. data/lib/axlsx/stylesheet/gradient_fill.rb +20 -17
  57. data/lib/axlsx/stylesheet/gradient_stop.rb +10 -6
  58. data/lib/axlsx/stylesheet/num_fmt.rb +13 -6
  59. data/lib/axlsx/stylesheet/pattern_fill.rb +26 -10
  60. data/lib/axlsx/stylesheet/styles.rb +53 -41
  61. data/lib/axlsx/stylesheet/table_style.rb +10 -4
  62. data/lib/axlsx/stylesheet/table_style_element.rb +11 -7
  63. data/lib/axlsx/stylesheet/table_styles.rb +12 -8
  64. data/lib/axlsx/stylesheet/xf.rb +30 -25
  65. data/lib/axlsx/util/cbf.rb +1 -0
  66. data/lib/axlsx/util/constants.rb +27 -14
  67. data/lib/axlsx/util/ms_off_crypto.rb +1 -1
  68. data/lib/axlsx/util/parser.rb +1 -0
  69. data/lib/axlsx/util/simple_typed_list.rb +20 -10
  70. data/lib/axlsx/util/storage.rb +1 -0
  71. data/lib/axlsx/util/validators.rb +23 -7
  72. data/lib/axlsx/version.rb +2 -1
  73. data/lib/axlsx/workbook/shared_strings_table.rb +69 -0
  74. data/lib/axlsx/workbook/shared_strings_table.rb~ +69 -0
  75. data/lib/axlsx/workbook/workbook.rb +81 -35
  76. data/lib/axlsx/workbook/worksheet/cell.rb +172 -117
  77. data/lib/axlsx/workbook/worksheet/col.rb +114 -0
  78. data/lib/axlsx/workbook/worksheet/col.rb~ +0 -0
  79. data/lib/axlsx/workbook/worksheet/date_time_converter.rb +29 -0
  80. data/lib/axlsx/workbook/worksheet/page_margins.rb +97 -0
  81. data/lib/axlsx/workbook/worksheet/row.rb +53 -21
  82. data/lib/axlsx/workbook/worksheet/shared_strings_table.rb~ +0 -0
  83. data/lib/axlsx/workbook/worksheet/table.rb +96 -0
  84. data/lib/axlsx/workbook/worksheet/table.rb~ +97 -0
  85. data/lib/axlsx/workbook/worksheet/worksheet.rb +269 -121
  86. data/lib/axlsx.rb +41 -14
  87. data/lib/schema/dc.xsd +5 -5
  88. data/lib/schema/dcmitype.xsd +5 -3
  89. data/lib/schema/dcterms.xsd +15 -15
  90. data/lib/schema/opc-coreProperties.xsd +6 -2
  91. data/lib/schema/xml.xsd +7 -8
  92. data/test/#benchmark.txt# +7 -0
  93. data/test/#tc_helper.rb# +3 -0
  94. data/test/benchmark.rb +81 -0
  95. data/test/benchmark.rb~ +0 -0
  96. data/test/benchmark.txt +6 -0
  97. data/test/benchmark.txt~ +6 -0
  98. data/test/content_type/tc_content_type.rb +31 -31
  99. data/test/content_type/tc_default.rb +9 -22
  100. data/test/content_type/tc_override.rb +8 -21
  101. data/test/doc_props/tc_app.rb +2 -8
  102. data/test/doc_props/tc_core.rb +6 -7
  103. data/test/drawing/tc_axis.rb +7 -3
  104. data/test/drawing/tc_bar_3D_chart.rb +6 -7
  105. data/test/drawing/tc_bar_series.rb +4 -5
  106. data/test/drawing/tc_cat_axis.rb +2 -3
  107. data/test/drawing/tc_cat_axis_data.rb +2 -3
  108. data/test/drawing/tc_chart.rb +13 -14
  109. data/test/drawing/tc_drawing.rb +7 -8
  110. data/test/drawing/tc_graphic_frame.rb +3 -4
  111. data/test/drawing/tc_hyperlink.rb +2 -3
  112. data/test/drawing/tc_line_3d_chart.rb +5 -6
  113. data/test/drawing/tc_line_series.rb +3 -4
  114. data/test/drawing/tc_marker.rb +3 -4
  115. data/test/drawing/tc_one_cell_anchor.rb +6 -7
  116. data/test/drawing/tc_pic.rb +8 -9
  117. data/test/drawing/tc_picture_locking.rb +2 -3
  118. data/test/drawing/tc_pie_3D_chart.rb +5 -6
  119. data/test/drawing/tc_pie_series.rb +4 -5
  120. data/test/drawing/tc_scaling.rb +3 -4
  121. data/test/drawing/tc_scatter_chart.rb +43 -0
  122. data/test/drawing/tc_scatter_series.rb +20 -0
  123. data/test/drawing/tc_ser_axis.rb +2 -3
  124. data/test/drawing/tc_series.rb +4 -5
  125. data/test/drawing/tc_series_title.rb +4 -5
  126. data/test/drawing/tc_title.rb +4 -5
  127. data/test/drawing/tc_two_cell_anchor.rb +4 -5
  128. data/test/drawing/tc_val_axis.rb +2 -3
  129. data/test/drawing/tc_val_axis_data.rb +2 -3
  130. data/test/drawing/tc_view_3D.rb +6 -7
  131. data/test/example.csv +1000 -0
  132. data/test/example.xlsx +0 -0
  133. data/test/example_streamed.xlsx +0 -0
  134. data/test/profile.rb +33 -0
  135. data/test/rels/tc_relationship.rb +5 -6
  136. data/test/rels/tc_relationships.rb +4 -5
  137. data/test/stylesheet/tc_border.rb +3 -4
  138. data/test/stylesheet/tc_border_pr.rb +3 -4
  139. data/test/stylesheet/tc_cell_alignment.rb +10 -6
  140. data/test/stylesheet/tc_cell_protection.rb +2 -3
  141. data/test/stylesheet/tc_cell_style.rb +2 -3
  142. data/test/stylesheet/tc_color.rb +2 -3
  143. data/test/stylesheet/tc_fill.rb +1 -2
  144. data/test/stylesheet/tc_font.rb +12 -5
  145. data/test/stylesheet/tc_gradient_fill.rb +1 -2
  146. data/test/stylesheet/tc_gradient_stop.rb +1 -2
  147. data/test/stylesheet/tc_num_fmt.rb +1 -2
  148. data/test/stylesheet/tc_pattern_fill.rb +3 -4
  149. data/test/stylesheet/tc_styles.rb +16 -6
  150. data/test/stylesheet/tc_table_style.rb +2 -3
  151. data/test/stylesheet/tc_table_style_element.rb +2 -3
  152. data/test/stylesheet/tc_table_styles.rb +3 -4
  153. data/test/stylesheet/tc_xf.rb +16 -17
  154. data/test/tc_axlsx.rb +39 -0
  155. data/test/tc_axlsx.rb~ +0 -0
  156. data/test/tc_helper.rb +3 -0
  157. data/test/tc_helper.rb~ +3 -0
  158. data/test/tc_package.rb +43 -9
  159. data/test/util/tc_simple_typed_list.rb +8 -9
  160. data/test/util/tc_validators.rb +7 -8
  161. data/test/workbook/tc_shared_strings_table.rb +38 -0
  162. data/test/workbook/tc_workbook.rb +32 -6
  163. data/test/workbook/worksheet/table/tc_table.rb +71 -0
  164. data/test/workbook/worksheet/table/tc_table.rb~ +72 -0
  165. data/test/workbook/worksheet/tc_cell.rb +70 -13
  166. data/test/workbook/worksheet/tc_col.rb +59 -0
  167. data/test/workbook/worksheet/tc_col.rb~ +10 -0
  168. data/test/workbook/worksheet/tc_date_time_converter.rb +126 -0
  169. data/test/workbook/worksheet/tc_page_margins.rb +97 -0
  170. data/test/workbook/worksheet/tc_row.rb +54 -4
  171. data/test/workbook/worksheet/tc_worksheet.rb +191 -31
  172. metadata +162 -109
  173. data/test/drawing/tc_hyperlink.rb~ +0 -71
@@ -1,9 +1,9 @@
1
- # -*- coding: utf-8 -*-
1
+ # encoding: UTF-8
2
2
  module Axlsx
3
-
4
- # The Worksheet class represents a worksheet in the workbook.
3
+
4
+ # The Worksheet class represents a worksheet in the workbook.
5
5
  class Worksheet
6
-
6
+
7
7
  # The name of the worksheet
8
8
  # @return [String]
9
9
  attr_reader :name
@@ -12,6 +12,9 @@ module Axlsx
12
12
  # @return [Workbook]
13
13
  attr_reader :workbook
14
14
 
15
+ # The tables in this worksheet
16
+ # @return [Array] of Table
17
+ attr_reader :tables
15
18
 
16
19
  # The rows in this worksheet
17
20
  # @note The recommended way to manage rows is Worksheet#add_row
@@ -21,7 +24,7 @@ module Axlsx
21
24
 
22
25
  # An array of content based calculated column widths.
23
26
  # @note a single auto fit data item is a hash with :longest => [String] and :sz=> [Integer] members.
24
- # @return [Array] of Hash
27
+ # @return [Array] of Hash
25
28
  attr_reader :auto_fit_data
26
29
 
27
30
  # An array of merged cell ranges e.d "A1:B3"
@@ -34,63 +37,144 @@ module Axlsx
34
37
  # The first row is considered the header, while subsequent rows are considerd to be data.
35
38
  # @return Array
36
39
  attr_reader :auto_filter
37
-
40
+
41
+ # Indicates if the worksheet should show gridlines or not
42
+ # @return Boolean
43
+ attr_reader :show_gridlines
44
+
45
+
46
+ # Indicates if the worksheet is selected in the workbook
47
+ # It is possible to have more than one worksheet selected, however it might cause issues
48
+ # in some older versions of excel when using copy and paste.
49
+ # @return Boolean
50
+ attr_reader :selected
51
+
52
+ # Indicates if the worksheet should print in a single page
53
+ # @return Boolean
54
+ attr_reader :fit_to_page
55
+
56
+
57
+ # Column info for the sheet
58
+ # @return [SimpleTypedList]
59
+ attr_reader :column_info
60
+
61
+ # Page margins for printing the worksheet.
62
+ # @example
63
+ # wb = Axlsx::Package.new.workbook
64
+ # # using options when creating the worksheet.
65
+ # ws = wb.add_worksheet :page_margins => {:left => 1.9, :header => 0.1}
66
+ #
67
+ # # use the set method of the page_margins object
68
+ # ws.page_margins.set(:bottom => 3, :footer => 0.7)
69
+ #
70
+ # # set page margins in a block
71
+ # ws.page_margins do |margins|
72
+ # margins.right = 6
73
+ # margins.top = 0.2
74
+ # end
75
+ # @see PageMargins#initialize
76
+ # @return [PageMargins]
77
+ def page_margins
78
+ @page_margins ||= PageMargins.new
79
+ yield @page_margins if block_given?
80
+ @page_margins
81
+
82
+ end
83
+
38
84
  # Creates a new worksheet.
39
85
  # @note the recommended way to manage worksheets is Workbook#add_worksheet
40
86
  # @see Workbook#add_worksheet
41
- # @option options [String] name The name of this sheet.
87
+ # @option options [String] name The name of this worksheet.
88
+ # @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
89
+ # @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
42
90
  def initialize(wb, options={})
43
- @drawing = nil
44
- @rows = SimpleTypedList.new Row
45
91
  self.workbook = wb
46
92
  @workbook.worksheets << self
47
- @auto_fit_data = []
48
- self.name = options[:name] || "Sheet" + (index+1).to_s
49
- @magick_draw = Magick::Draw.new
50
- @cols = SimpleTypedList.new Cell
93
+
94
+ @drawing = @page_margins = @auto_filter = nil
51
95
  @merged_cells = []
96
+ @auto_fit_data = []
97
+
98
+ @selected = false
99
+ @show_gridlines = true
100
+ self.name = "Sheet" + (index+1).to_s
101
+ @page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
102
+
103
+ @rows = SimpleTypedList.new Row
104
+ @column_info = SimpleTypedList.new Col
105
+ # @cols = SimpleTypedList.new Cell
106
+ @tables = SimpleTypedList.new Table
107
+
108
+ if self.workbook.use_autowidth
109
+ require 'RMagick' unless defined?(Magick)
110
+ @magick_draw = Magick::Draw.new
111
+ else
112
+ @magick_draw = nil
113
+ end
114
+
115
+ options.each do |o|
116
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
117
+ end
118
+
52
119
  end
53
120
 
54
- # Creates merge information for this worksheet.
121
+ # convinience method to access all cells in this worksheet
122
+ # @return [Array] cells
123
+ def cells
124
+ rows.flatten
125
+ end
126
+
127
+ # Creates merge information for this worksheet.
55
128
  # Cells can be merged by calling the merge_cells method on a worksheet.
56
- # @example This would merge the three cells C1..E1 #
129
+ # @example This would merge the three cells C1..E1 #
57
130
  # worksheet.merge_cells "C1:E1"
58
131
  # # you can also provide an array of cells to be merged
59
132
  # worksheet.merge_cells worksheet.rows.first.cells[(2..4)]
60
133
  # #alternatively you can do it from a single cell
61
134
  # worksheet["C1"].merge worksheet["E1"]
62
- # @param [Array, string]
135
+ # @param [Array, string]
63
136
  def merge_cells(cells)
64
137
  @merged_cells << if cells.is_a?(String)
65
138
  cells
66
139
  elsif cells.is_a?(Array)
67
140
  cells = cells.sort { |x, y| x.r <=> y.r }
68
141
  "#{cells.first.r}:#{cells.last.r}"
69
- end
142
+ end
70
143
  end
71
144
 
72
145
 
73
- # Returns the cell or cells defined using excel style A1:B3 references.
74
- # @param [String] cell_def the string defining the cell or range of cells
75
- # @return [Cell, Array]
76
- def [](cell_def)
77
- parts = cell_def.split(':')
78
- first = name_to_cell parts[0]
146
+ # The demensions of a worksheet. This is not actually a required element by the spec,
147
+ # but at least a few other document readers expect this for conversion
148
+ # @return [String] the A1:B2 style reference for the first and last row column intersection in the workbook
149
+ def dimension
150
+ "#{rows.first.cells.first.r}:#{rows.last.cells.last.r}"
151
+ end
79
152
 
80
- if parts.size == 1
81
- first
82
- else
83
- cells = []
84
- last = name_to_cell(parts[1])
85
- rows[(first.row.index..last.row.index)].each do |r|
86
- r.cells[(first.index..last.index)].each do |c|
87
- cells << c
88
- end
89
- end
90
- cells
91
- end
153
+ # Indicates if gridlines should be shown in the sheet.
154
+ # This is true by default.
155
+ # @return [Boolean]
156
+ def show_gridlines=(v)
157
+ Axlsx::validate_boolean v
158
+ @show_gridlines = v
159
+ end
160
+
161
+ # @see selected
162
+ # @return [Boolean]
163
+ def selected=(v)
164
+ Axlsx::validate_boolean v
165
+ @selected = v
166
+ end
167
+
168
+
169
+ # Indicates if the worksheet should print in a single page.
170
+ # This is true by default.
171
+ # @return [Boolean]
172
+ def fit_to_page=(v)
173
+ Axlsx::validate_boolean v
174
+ @fit_to_page = v
92
175
  end
93
176
 
177
+
94
178
  # returns the column and row index for a named based cell
95
179
  # @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
96
180
  # @return [Cell]
@@ -101,18 +185,26 @@ module Axlsx
101
185
  end
102
186
 
103
187
  # The name of the worksheet
188
+ # The name of a worksheet must be unique in the workbook, and must not exceed 31 characters
104
189
  # @param [String] v
105
- def name=(v)
190
+ def name=(v)
106
191
  DataTypeValidator.validate "Worksheet.name", String, v
192
+ raise ArgumentError, (ERR_SHEET_NAME_TOO_LONG % v) if v.size > 31
107
193
  sheet_names = @workbook.worksheets.map { |s| s.name }
108
- raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
109
- @name=v
194
+ raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
195
+ @name=v
196
+ end
197
+
198
+ # The absolute auto filter range
199
+ # @see auto_filter
200
+ def abs_auto_filter
201
+ Axlsx.cell_range(@auto_filter.split(':').collect { |name| name_to_cell(name)}) if @auto_filter
110
202
  end
111
203
 
112
204
  # The auto filter range for the worksheet
113
205
  # @param [String] v
114
206
  # @see auto_filter
115
- def auto_filter=(v)
207
+ def auto_filter=(v)
116
208
  DataTypeValidator.validate "Worksheet.auto_filter", String, v
117
209
  @auto_filter = v
118
210
  end
@@ -149,18 +241,55 @@ module Axlsx
149
241
  @drawing || @drawing = Axlsx::Drawing.new(self)
150
242
  end
151
243
 
152
- # Adds a row to the worksheet and updates auto fit data
244
+ # Adds a row to the worksheet and updates auto fit data.
245
+ # @example - put a vanilla row in your spreadsheet
246
+ # ws.add_row [1, 'fish on my pl', '8']
247
+ #
248
+ # @example - specify a fixed width for a column in your spreadsheet
249
+ # # The first column will ignore the content of this cell when calculating column autowidth.
250
+ # # The second column will include this text in calculating the columns autowidth
251
+ # # The third cell will set a fixed with of 80 for the column.
252
+ # # If you need to un-fix a column width, use :auto. That will recalculate the column width based on all content in the column
253
+ #
254
+ # ws.add_row ['I wish', 'for a fish', 'on my fish wish dish'], :widths=>[:ignore, :auto, 80]
255
+ #
256
+ # @example - specify a fixed height for a row
257
+ # ws.add_row ['I wish', 'for a fish', 'on my fish wish dish'], :height => 40
258
+ #
259
+ # @example - create and use a style for all cells in the row
260
+ # blue = ws.styles.add_style :color => "#00FF00"
261
+ # ws.add_row [1, 2, 3], :style=>blue
262
+ #
263
+ # @example - only style some cells
264
+ # blue = ws.styles.add_style :color => "#00FF00"
265
+ # red = ws.styles.add_style :color => "#FF0000"
266
+ # big = ws.styles.add_style :sz => 40
267
+ # ws.add_row ["red fish", "blue fish", "one fish", "two fish"], :style=>[red, blue, nil, big] # the last nil is optional
268
+ #
269
+ #
270
+ # @example - force the second cell to be a float value
271
+ # ws.add_row [3, 4, 5], :types => [nil, :float]
272
+ #
273
+ # @example - use << alias
274
+ # ws << [3, 4, 5], :types => [nil, :float]
275
+ #
276
+ # @see Worksheet#column_widths
153
277
  # @return [Row]
154
278
  # @option options [Array] values
155
- # @option options [Array, Symbol] types
156
- # @option options [Array, Integer] style
279
+ # @option options [Array, Symbol] types
280
+ # @option options [Array, Integer] style
281
+ # @option options [Array] widths each member of the widths array will affect how auto_fit behavies.
282
+ # @option options [Float] height the row's height (in points)
157
283
  def add_row(values=[], options={})
158
284
  Row.new(self, values, options)
159
- update_auto_fit_data @rows.last.cells
285
+ update_column_info @rows.last.cells, options.delete(:widths) ||[], options.delete(:style) || []
286
+ # update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
160
287
  yield @rows.last if block_given?
161
288
  @rows.last
162
289
  end
163
290
 
291
+ alias :<< :add_row
292
+
164
293
  # Set the style for cells in a specific row
165
294
  # @param [Integer] index or range of indexes in the table
166
295
  # @param [Integer] the cellXfs index
@@ -193,7 +322,7 @@ module Axlsx
193
322
  # @see README.md for an example
194
323
  def col_style(index, style, options={})
195
324
  offset = options.delete(:row_offset) || 0
196
- @rows[(offset..-1)].each do |r|
325
+ @rows[(offset..-1)].each do |r|
197
326
  cells = r.cells[index]
198
327
  next unless cells
199
328
  if cells.is_a?(Array)
@@ -203,15 +332,30 @@ module Axlsx
203
332
  end
204
333
  end
205
334
  end
206
-
207
- # Adds a chart to this worksheets drawing. This is the recommended way to create charts for your worksheet. This method wraps the complexity of dealing with ooxml drawing, anchors, markers graphic frames chart objects and all the other dirty details.
335
+
336
+ # This is a helper method that Lets you specify a fixed width for multiple columns in a worksheet in one go.
337
+ # Axlsx is sparse, so if you have not set data for a column, you cannot set the width.
338
+ # Setting a fixed column width to nil will revert the behaviour back to calculating the width for you.
339
+ # @example This would set the first and third column widhts but leave the second column in autofit state.
340
+ # ws.column_widths 7.2, nil, 3
341
+ # @note For updating only a single column it is probably easier to just set ws.auto_fit_data[col_index][:fixed] directly
342
+ # @param [Integer|Float|Fixnum|nil] values
343
+ def column_widths(*args)
344
+ args.each_with_index do |value, index|
345
+ raise ArgumentError, "Invalid column specification" unless index < @column_info.size
346
+ Axlsx::validate_unsigned_numeric(value) unless value == nil
347
+ @column_info[index].width = value
348
+ end
349
+ end
350
+
351
+ # Adds a chart to this worksheets drawing. This is the recommended way to create charts for your worksheet. This method wraps the complexity of dealing with ooxml drawing, anchors, markers graphic frames chart objects and all the other dirty details.
208
352
  # @param [Class] chart_type
209
353
  # @option options [Array] start_at
210
354
  # @option options [Array] end_at
211
355
  # @option options [Cell, String] title
212
356
  # @option options [Boolean] show_legend
213
- # @option options [Integer] style
214
- # @note each chart type also specifies additional options
357
+ # @option options [Integer] style
358
+ # @note each chart type also specifies additional options
215
359
  # @see Chart
216
360
  # @see Pie3DChart
217
361
  # @see Bar3DChart
@@ -223,6 +367,14 @@ module Axlsx
223
367
  chart
224
368
  end
225
369
 
370
+ # needs documentation
371
+ def add_table(ref, options={})
372
+ table = Table.new(ref, self, options)
373
+ @tables << table
374
+ yield table if block_given?
375
+ table
376
+ end
377
+
226
378
  # Adds a media item to the worksheets drawing
227
379
  # @param [Class] media_type
228
380
  # @option options [] unknown
@@ -232,105 +384,101 @@ module Axlsx
232
384
  image
233
385
  end
234
386
 
235
- # Serializes the worksheet document
387
+ # Serializes the object
388
+ # @param [String] str
236
389
  # @return [String]
237
- def to_xml
238
- builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml|
239
- xml.worksheet(:xmlns => XML_NS,
240
- :'xmlns:r' => XML_NS_R) {
241
- if @auto_fit_data.size > 0
242
- xml.cols {
243
- @auto_fit_data.each_with_index do |col, index|
244
- min_max = index+1
245
- xml.col(:min=>min_max, :max=>min_max, :width => auto_width(col), :customWidth=>1)
246
- end
247
- }
248
- end
249
- xml.sheetData {
250
- @rows.each do |row|
251
- row.to_xml(xml)
252
- end
253
- }
254
- xml.autoFilter :ref=>@auto_filter if @auto_filter
255
- xml.mergeCells(:count=>@merged_cells.size) { @merged_cells.each { | mc | xml.mergeCell(:ref=>mc) } } unless @merged_cells.empty?
256
- xml.drawing :"r:id"=>"rId1" if @drawing
257
- }
390
+ def to_xml_string
391
+ str = '<?xml version="1.0" encoding="UTF-8"?>'
392
+ str.concat "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
393
+ str.concat "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % fit_to_page if fit_to_page
394
+ str.concat "<dimension ref=\"%s\"></dimension>" % dimension unless rows.size == 0
395
+ str.concat "<sheetViews><sheetView tabSelected='%s' workbookViewId='0' showGridLines='%s'><selection activeCell=\"A1\" sqref=\"A1\"/></sheetView></sheetViews>" % [@selected, show_gridlines]
396
+
397
+ if @column_info.size > 0
398
+ str << "<cols>"
399
+ @column_info.each { |col| col.to_xml_string(str) }
400
+ str.concat '</cols>'
401
+ end
402
+ str.concat '<sheetData>'
403
+ @rows.each_with_index { |row, index| row.to_xml_string(index, str) }
404
+ str.concat '</sheetData>'
405
+ page_margins.to_xml_string(str) if @page_margins
406
+ str.concat "<autoFilter ref='%s'></autoFilter>" % @auto_filter if @auto_filter
407
+ str.concat "<mergeCells count='%s'>%s</mergeCells>" % [@merged_cells.size, @merged_cells.reduce('') { |memo, obj| "<mergeCell ref='%s'></mergeCell>" % obj } ] unless @merged_cells.empty?
408
+ str.concat "<drawing r:id='rId1'></drawing>" if @drawing
409
+ unless @tables.empty?
410
+ str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }]
258
411
  end
259
- builder.to_xml(:save_with => 0)
412
+ str + '</worksheet>'
260
413
  end
261
414
 
262
415
  # The worksheet relationships. This is managed automatically by the worksheet
263
416
  # @return [Relationships]
264
417
  def relationships
265
418
  r = Relationships.new
419
+ @tables.each do |table|
420
+ r << Relationship.new(TABLE_R, "../#{table.pn}")
421
+ end
266
422
  r << Relationship.new(DRAWING_R, "../#{@drawing.pn}") if @drawing
267
423
  r
268
424
  end
269
425
 
270
- private
426
+ # Returns the cell or cells defined using excel style A1:B3 references.
427
+ # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber
428
+ # @return [Cell, Array]
429
+
430
+ def [] (cell_def)
431
+ return rows[cell_def] if cell_def.is_a?(Integer)
432
+
433
+ parts = cell_def.split(':')
434
+ first = name_to_cell parts[0]
435
+ if parts.size == 1
436
+ first
437
+ else
438
+ cells = []
439
+ last = name_to_cell(parts[1])
440
+ rows[(first.row.index..last.row.index)].each do |r|
441
+ r.cells[(first.index..last.index)].each do |c|
442
+ cells << c
443
+ end
444
+ end
445
+ cells
446
+ end
447
+ end
448
+
449
+ private
271
450
 
272
451
  # assigns the owner workbook for this worksheet
273
452
  def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
274
453
 
275
- # Updates auto fit data.
276
- # We store an auto_fit_data item for each column. when a row is added we multiple the font size by the length of the text to
277
- # attempt to identify the longest cell in the column. This is not 100% accurate as it needs to take into account
278
- # any formatting that will be applied to the data, as well as the actual rendering size when the length and size is equal
279
- # for two cells.
280
- # @return [Array] of Cell objects
281
- # @param [Array] cells an array of cells
282
- def update_auto_fit_data(cells)
283
- # TODO delay this until rendering. too much work when we dont know what they are going to do to the sheet.
454
+
455
+ def update_column_info(cells, widths=[], style=[])
284
456
  styles = self.workbook.styles
285
457
  cellXfs, fonts = styles.cellXfs, styles.fonts
286
458
  sz = 11
287
- cells.each_with_index do |item, index|
288
- # ignore formula - there is no way for us to know the result
289
- next if item.value.is_a?(String) && item.value.start_with?('=')
290
-
291
- col = @auto_fit_data[index] || {:longest=>"", :sz=>sz}
292
- cell_xf = cellXfs[item.style]
293
- font = fonts[cell_xf.fontId || 0]
294
- sz = item.sz || font.sz || fonts[0].sz
295
- if (col[:longest].scan(/./mu).size * col[:sz]) < (item.value.to_s.scan(/./mu).size * sz)
296
- col[:sz] = sz
297
- col[:longest] = item.value.to_s
459
+ cells.each_with_index do |cell, index|
460
+ @column_info[index] ||= Col.new index+1, index+1
461
+ col = @column_info[index]
462
+ width = widths[index]
463
+ col.width = width if [Integer, Float, Fixnum].include?(width.class)
464
+ c_style = style[index] if [Integer, Fixnum].include?(style[index].class)
465
+ next if width == :ignore || col.width || (cell.value.is_a?(String) && cell.value.start_with?('='))
466
+ if self.workbook.use_autowidth
467
+ cell_xf = cellXfs[(c_style || 0)]
468
+ font = fonts[(cell_xf.fontId || 0)]
469
+ sz = cell.sz || font.sz || sz
470
+ col.width = [(col.width || 0), calculate_width(cell.value.to_s, sz)].max
298
471
  end
299
- @auto_fit_data[index] = col
300
472
  end
301
- cells
302
473
  end
303
-
304
- # Determines the proper width for a column based on content.
305
- # @note
306
- # width = Truncate([!{Number of Characters} * !{Maximum Digit Width} + !{5 pixel padding}]/!{Maximum Digit Width}*256)/256
307
- # @return [Float]
308
- # @param [Hash] A hash of auto_fit_data
309
- def auto_width(col)
310
- mdw_count, font_scale, mdw = 0, col[:sz]/11.0, 6.0
311
- mdw_count = col[:longest].scan(/./mu).reduce(0) do | count, char |
474
+
475
+ def calculate_width(text, sz)
476
+ mdw_count, font_scale, mdw = 0, sz/11.0, 6.0
477
+ mdw_count = text.scan(/./mu).reduce(0) do | count, char |
312
478
  count +=1 if @magick_draw.get_type_metrics(char).max_advance >= mdw
313
479
  count
314
480
  end
315
481
  ((mdw_count * mdw + 5) / mdw * 256) / 256.0 * font_scale
316
482
  end
317
-
318
- # Something to look into:
319
- # width calculation actually needs to be done agains the formatted value for items that apply a
320
- # format
321
- # def excel_format(cell)
322
- # # The most common case.
323
- # return time.value.to_s if cell.style == 0
324
- #
325
- # # The second most common case
326
- # num_fmt = workbook.styles.cellXfs[items.style].numFmtId
327
- # return value.to_s if num_fmt == 0
328
- #
329
- # format_code = workbook.styles.numFmts[num_fmt]
330
- # # need to find some exceptionally fast way of parsing value according to
331
- # # an excel format_code
332
- # item.value.to_s
333
- # end
334
-
335
483
  end
336
484
  end
data/lib/axlsx.rb CHANGED
@@ -1,14 +1,15 @@
1
- Encoding::default_internal = 'UTF-8' unless RUBY_VERSION < '1.9'
2
- Encoding::default_external = 'UTF-8' unless RUBY_VERSION < '1.9'
3
-
1
+ # encoding: UTF-8
4
2
  require 'axlsx/version.rb'
5
3
 
6
4
  require 'axlsx/util/simple_typed_list.rb'
7
5
  require 'axlsx/util/constants.rb'
8
6
  require 'axlsx/util/validators.rb'
9
- # require 'axlsx/util/storage.rb'
10
- # require 'axlsx/util/cbf.rb'
11
- # require 'axlsx/util/ms_off_crypto.rb'
7
+ require 'axlsx/util/storage.rb'
8
+
9
+ #not even close to being ready but it does not break anything so it stays for now.
10
+ # needs a full re-write to use agile-encryption properly
11
+ require 'axlsx/util/cbf.rb'
12
+ require 'axlsx/util/ms_off_crypto.rb'
12
13
 
13
14
 
14
15
  # to be included with parsable intitites.
@@ -24,24 +25,29 @@ require 'axlsx/rels/relationships.rb'
24
25
  require 'axlsx/drawing/drawing.rb'
25
26
  require 'axlsx/workbook/workbook.rb'
26
27
  require 'axlsx/package.rb'
27
-
28
28
  #required gems
29
29
  require 'nokogiri'
30
- require 'active_support/core_ext/object/instance_variables'
31
- require 'active_support/inflector'
32
- require 'RMagick'
33
30
  require 'zip/zip'
34
31
 
35
32
  #core dependencies
36
33
  require 'bigdecimal'
37
34
  require 'time'
38
35
 
36
+ #if object does not have this already, I am borrowing it from active_support.
37
+ # I am a very big fan of activesupports instance_values method, but do not want to require nor include the entire
38
+ # library just for this one method.
39
+ if !Object.respond_to?(:instance_values)
40
+ Object.send :public # patch for 1.8.7 as it uses private scope
41
+ Object.send :define_method, :instance_values do
42
+ Hash[instance_variables.map { |name| [name.to_s[1..-1], instance_variable_get(name)] }]
43
+ end
44
+ end
39
45
 
40
46
  # xlsx generation with charts, images, automated column width, customizable styles and full schema validation. Axlsx excels at helping you generate beautiful Office Open XML Spreadsheet documents without having to understand the entire ECMA specification. Check out the README for some examples of how easy it is. Best of all, you can validate your xlsx file before serialization so you know for sure that anything generated is going to load on your client's machine.
41
47
  module Axlsx
42
48
  # determines the cell range for the items provided
43
49
  def self.cell_range(items)
44
- return "" unless items.first.is_a? Cell
50
+ return "" unless items.first.is_a? Cell
45
51
  ref = "'#{items.first.row.worksheet.name}'!" +
46
52
  "#{items.first.r_abs}"
47
53
  ref += ":#{items.last.r_abs}" if items.size > 1
@@ -50,11 +56,32 @@ module Axlsx
50
56
 
51
57
  def self.name_to_indices(name)
52
58
  raise ArgumentError, 'invalid cell name' unless name.size > 1
53
- v = name[/[A-Z]+/].reverse.chars.reduce({:base=>1, :i=>0}) do |v, c|
54
- v[:i] += ((c.bytes.first - 65) + v[:base]); v[:base] *= 26; v
59
+ v = name[/[A-Z]+/].reverse.chars.reduce({:base=>1, :i=>0}) do |val, c|
60
+ val[:i] += ((c.bytes.first - 64) * val[:base]); val[:base] *= 26; val
55
61
  end
56
62
 
57
- [v[:i]-1, ((name[/[1-9]+/]).to_i)-1]
63
+ [v[:i]-1, ((name[/[1-9][0-9]*/]).to_i)-1]
58
64
 
59
65
  end
66
+
67
+ # converts the column index into alphabetical values.
68
+ # @note This follows the standard spreadsheet convention of naming columns A to Z, followed by AA to AZ etc.
69
+ # @return [String]
70
+ def self.col_ref(index)
71
+ chars = []
72
+ while index >= 26 do
73
+ chars << ((index % 26) + 65).chr
74
+ index = index / 26 - 1
75
+ end
76
+ chars << (index + 65).chr
77
+ chars.reverse.join
78
+ end
79
+
80
+ # @return [String] The alpha(column)numeric(row) reference for this sell.
81
+ # @example Relative Cell Reference
82
+ # ws.rows.first.cells.first.r #=> "A1"
83
+ def self.cell_r(c_index, r_index)
84
+ Axlsx::col_ref(c_index).to_s << (r_index+1).to_s
85
+ end
86
+
60
87
  end
data/lib/schema/dc.xsd CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  Created 2003-04-02
14
14
 
15
- Created by
15
+ Created by
16
16
 
17
17
  Tim Cole (t-cole3@uiuc.edu)
18
18
  Tom Habing (thabing@uiuc.edu)
@@ -23,14 +23,14 @@
23
23
  This schema declares XML elements for the 15 DC elements from the
24
24
  http://purl.org/dc/elements/1.1/ namespace.
25
25
 
26
- It defines a complexType SimpleLiteral which permits mixed content
26
+ It defines a complexType SimpleLiteral which permits mixed content
27
27
  and makes the xml:lang attribute available. It disallows child elements by
28
28
  use of minOcccurs/maxOccurs.
29
29
 
30
30
  However, this complexType does permit the derivation of other complexTypes
31
31
  which would permit child elements.
32
32
 
33
- All elements are declared as substitutable for the abstract element any,
33
+ All elements are declared as substitutable for the abstract element any,
34
34
  which means that the default type for all elements is dc:SimpleLiteral.
35
35
 
36
36
  </xs:documentation>
@@ -49,7 +49,7 @@
49
49
  It permits text content only with optional
50
50
  xml:lang attribute.
51
51
  Text is allowed because mixed="true", but sub-elements
52
- are disallowed because minOccurs="0" and maxOccurs="0"
52
+ are disallowed because minOccurs="0" and maxOccurs="0"
53
53
  are on the xs:any tag.
54
54
 
55
55
  This complexType allows for restriction or extension permitting
@@ -89,7 +89,7 @@
89
89
  <xs:annotation>
90
90
  <xs:documentation xml:lang="en">
91
91
  This group is included as a convenience for schema authors
92
- who need to refer to all the elements in the
92
+ who need to refer to all the elements in the
93
93
  http://purl.org/dc/elements/1.1/ namespace.
94
94
  </xs:documentation>
95
95
  </xs:annotation>