axlsx 1.0.15 → 1.1.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 (184) hide show
  1. data/.yardopts +1 -0
  2. data/CHANGELOG.md +58 -2
  3. data/README.md +425 -207
  4. data/Rakefile +6 -2
  5. data/examples/axlsx.xlsx +0 -0
  6. data/examples/example.csv +1000 -0
  7. data/examples/example.rb +266 -132
  8. data/examples/example.xlsx +0 -0
  9. data/examples/example_streamed.xlsx +0 -0
  10. data/examples/no-use_autowidth.xlsx +0 -0
  11. data/examples/real_example.rb +63 -0
  12. data/examples/sample.png +0 -0
  13. data/examples/shared_strings_example.xlsx +0 -0
  14. data/lib/axlsx/content_type/content_type.rb +13 -12
  15. data/lib/axlsx/content_type/default.rb +10 -6
  16. data/lib/axlsx/content_type/override.rb +13 -8
  17. data/lib/axlsx/doc_props/app.rb +38 -41
  18. data/lib/axlsx/doc_props/core.rb +13 -17
  19. data/lib/axlsx/drawing/axis.rb +39 -19
  20. data/lib/axlsx/drawing/bar_3D_chart.rb +34 -32
  21. data/lib/axlsx/drawing/bar_series.rb +14 -14
  22. data/lib/axlsx/drawing/cat_axis.rb +16 -14
  23. data/lib/axlsx/drawing/cat_axis_data.rb +17 -18
  24. data/lib/axlsx/drawing/chart.rb +40 -41
  25. data/lib/axlsx/drawing/drawing.rb +27 -12
  26. data/lib/axlsx/drawing/graphic_frame.rb +22 -21
  27. data/lib/axlsx/drawing/hlink_click.rb~ +0 -0
  28. data/lib/axlsx/drawing/hyperlink.rb +96 -0
  29. data/lib/axlsx/drawing/hyperlink.rb~ +64 -0
  30. data/lib/axlsx/drawing/line_3D_chart.rb +31 -28
  31. data/lib/axlsx/drawing/line_series.rb +12 -11
  32. data/lib/axlsx/drawing/marker.rb +11 -8
  33. data/lib/axlsx/drawing/named_axis_data.rb +36 -0
  34. data/lib/axlsx/drawing/one_cell_anchor.rb +18 -16
  35. data/lib/axlsx/drawing/pic.rb +47 -33
  36. data/lib/axlsx/drawing/picture_locking.rb +22 -18
  37. data/lib/axlsx/drawing/pie_3D_chart.rb +11 -8
  38. data/lib/axlsx/drawing/pie_series.rb +16 -12
  39. data/lib/axlsx/drawing/scaling.rb +11 -10
  40. data/lib/axlsx/drawing/scatter_chart.rb +69 -0
  41. data/lib/axlsx/drawing/scatter_series.rb +39 -0
  42. data/lib/axlsx/drawing/ser_axis.rb +11 -10
  43. data/lib/axlsx/drawing/series.rb +16 -15
  44. data/lib/axlsx/drawing/series_title.rb +15 -14
  45. data/lib/axlsx/drawing/title.rb +27 -26
  46. data/lib/axlsx/drawing/two_cell_anchor.rb +19 -20
  47. data/lib/axlsx/drawing/val_axis.rb +9 -7
  48. data/lib/axlsx/drawing/val_axis_data.rb +18 -17
  49. data/lib/axlsx/drawing/view_3D.rb +23 -20
  50. data/lib/axlsx/package.rb +117 -52
  51. data/lib/axlsx/rels/relationship.rb +27 -8
  52. data/lib/axlsx/rels/relationships.rb +9 -2
  53. data/lib/axlsx/stylesheet/border.rb +28 -23
  54. data/lib/axlsx/stylesheet/border_pr.rb +17 -15
  55. data/lib/axlsx/stylesheet/cell_alignment.rb +24 -21
  56. data/lib/axlsx/stylesheet/cell_protection.rb +11 -7
  57. data/lib/axlsx/stylesheet/cell_style.rb +9 -5
  58. data/lib/axlsx/stylesheet/color.rb +20 -14
  59. data/lib/axlsx/stylesheet/fill.rb +8 -5
  60. data/lib/axlsx/stylesheet/font.rb +22 -14
  61. data/lib/axlsx/stylesheet/gradient_fill.rb +20 -17
  62. data/lib/axlsx/stylesheet/gradient_stop.rb +10 -6
  63. data/lib/axlsx/stylesheet/num_fmt.rb +13 -6
  64. data/lib/axlsx/stylesheet/pattern_fill.rb +26 -10
  65. data/lib/axlsx/stylesheet/styles.rb +55 -41
  66. data/lib/axlsx/stylesheet/table_style.rb +10 -4
  67. data/lib/axlsx/stylesheet/table_style_element.rb +11 -7
  68. data/lib/axlsx/stylesheet/table_styles.rb +12 -8
  69. data/lib/axlsx/stylesheet/xf.rb +30 -25
  70. data/lib/axlsx/util/cbf.rb +69 -33
  71. data/lib/axlsx/util/constants.rb +39 -20
  72. data/lib/axlsx/util/doc/_index.html +84 -0
  73. data/lib/axlsx/util/doc/class_list.html +47 -0
  74. data/lib/axlsx/util/doc/css/common.css +1 -0
  75. data/lib/axlsx/util/doc/css/full_list.css +55 -0
  76. data/lib/axlsx/util/doc/css/style.css +322 -0
  77. data/lib/axlsx/util/doc/file_list.html +46 -0
  78. data/lib/axlsx/util/doc/frames.html +13 -0
  79. data/lib/axlsx/util/doc/index.html +84 -0
  80. data/lib/axlsx/util/doc/js/app.js +205 -0
  81. data/lib/axlsx/util/doc/js/full_list.js +173 -0
  82. data/lib/axlsx/util/doc/js/jquery.js +16 -0
  83. data/lib/axlsx/util/doc/method_list.html +46 -0
  84. data/lib/axlsx/util/doc/top-level-namespace.html +95 -0
  85. data/lib/axlsx/util/font_tables.rb~ +0 -0
  86. data/lib/axlsx/util/ms_off_crypto.rb +10 -10
  87. data/lib/axlsx/util/parser.rb +1 -0
  88. data/lib/axlsx/util/simple_typed_list.rb +20 -10
  89. data/lib/axlsx/util/storage.rb +1 -1
  90. data/lib/axlsx/util/validators.rb +23 -7
  91. data/lib/axlsx/version.rb +5 -2
  92. data/lib/axlsx/workbook/shared_strings_table.rb +68 -0
  93. data/lib/axlsx/workbook/shared_strings_table.rb~ +69 -0
  94. data/lib/axlsx/workbook/workbook.rb +82 -26
  95. data/lib/axlsx/workbook/worksheet/cell.rb +166 -130
  96. data/lib/axlsx/workbook/worksheet/col.rb +114 -0
  97. data/lib/axlsx/workbook/worksheet/col.rb~ +0 -0
  98. data/lib/axlsx/workbook/worksheet/date_time_converter.rb +29 -0
  99. data/lib/axlsx/workbook/worksheet/page_margins.rb +97 -0
  100. data/lib/axlsx/workbook/worksheet/row.rb +53 -21
  101. data/lib/axlsx/workbook/worksheet/shared_strings_table.rb~ +0 -0
  102. data/lib/axlsx/workbook/worksheet/table.rb +96 -0
  103. data/lib/axlsx/workbook/worksheet/table.rb~ +97 -0
  104. data/lib/axlsx/workbook/worksheet/worksheet.rb +275 -124
  105. data/lib/axlsx.rb +41 -14
  106. data/lib/schema/dc.xsd +5 -5
  107. data/lib/schema/dcmitype.xsd +5 -3
  108. data/lib/schema/dcterms.xsd +15 -15
  109. data/lib/schema/opc-coreProperties.xsd +6 -2
  110. data/lib/schema/xml.xsd +7 -8
  111. data/test/benchmark.rb +73 -0
  112. data/test/content_type/tc_content_type.rb +31 -31
  113. data/test/content_type/tc_default.rb +9 -22
  114. data/test/content_type/tc_override.rb +8 -21
  115. data/test/doc_props/tc_app.rb +2 -8
  116. data/test/doc_props/tc_core.rb +6 -7
  117. data/test/drawing/tc_axis.rb +7 -3
  118. data/test/drawing/tc_bar_3D_chart.rb +6 -7
  119. data/test/drawing/tc_bar_series.rb +4 -5
  120. data/test/drawing/tc_cat_axis.rb +2 -3
  121. data/test/drawing/tc_cat_axis_data.rb +2 -3
  122. data/test/drawing/tc_chart.rb +13 -14
  123. data/test/drawing/tc_drawing.rb +7 -8
  124. data/test/drawing/tc_graphic_frame.rb +3 -4
  125. data/test/drawing/tc_hyperlink.rb +69 -0
  126. data/test/drawing/tc_line_3d_chart.rb +5 -6
  127. data/test/drawing/tc_line_series.rb +3 -4
  128. data/test/drawing/tc_marker.rb +3 -4
  129. data/test/drawing/tc_one_cell_anchor.rb +6 -7
  130. data/test/drawing/tc_pic.rb +13 -8
  131. data/test/drawing/tc_picture_locking.rb +2 -3
  132. data/test/drawing/tc_pie_3D_chart.rb +5 -6
  133. data/test/drawing/tc_pie_series.rb +4 -5
  134. data/test/drawing/tc_scaling.rb +3 -4
  135. data/test/drawing/tc_scatter_chart.rb +43 -0
  136. data/test/drawing/tc_scatter_series.rb +20 -0
  137. data/test/drawing/tc_ser_axis.rb +2 -3
  138. data/test/drawing/tc_series.rb +4 -5
  139. data/test/drawing/tc_series_title.rb +4 -5
  140. data/test/drawing/tc_title.rb +4 -5
  141. data/test/drawing/tc_two_cell_anchor.rb +4 -5
  142. data/test/drawing/tc_val_axis.rb +2 -3
  143. data/test/drawing/tc_val_axis_data.rb +2 -3
  144. data/test/drawing/tc_view_3D.rb +6 -7
  145. data/test/profile.rb +34 -0
  146. data/test/rels/tc_relationship.rb +10 -6
  147. data/test/rels/tc_relationships.rb +4 -5
  148. data/test/stylesheet/tc_border.rb +3 -4
  149. data/test/stylesheet/tc_border_pr.rb +3 -4
  150. data/test/stylesheet/tc_cell_alignment.rb +10 -6
  151. data/test/stylesheet/tc_cell_protection.rb +2 -3
  152. data/test/stylesheet/tc_cell_style.rb +2 -3
  153. data/test/stylesheet/tc_color.rb +2 -3
  154. data/test/stylesheet/tc_fill.rb +1 -2
  155. data/test/stylesheet/tc_font.rb +12 -5
  156. data/test/stylesheet/tc_gradient_fill.rb +1 -2
  157. data/test/stylesheet/tc_gradient_stop.rb +1 -2
  158. data/test/stylesheet/tc_num_fmt.rb +1 -2
  159. data/test/stylesheet/tc_pattern_fill.rb +3 -4
  160. data/test/stylesheet/tc_styles.rb +31 -6
  161. data/test/stylesheet/tc_table_style.rb +2 -3
  162. data/test/stylesheet/tc_table_style_element.rb +2 -3
  163. data/test/stylesheet/tc_table_styles.rb +3 -4
  164. data/test/stylesheet/tc_xf.rb +16 -17
  165. data/test/tc_axlsx.rb +39 -0
  166. data/test/tc_helper.rb +3 -0
  167. data/test/tc_package.rb +43 -9
  168. data/test/util/tc_simple_typed_list.rb +8 -9
  169. data/test/util/tc_validators.rb +7 -8
  170. data/test/workbook/tc_shared_strings_table.rb +38 -0
  171. data/test/workbook/tc_workbook.rb +32 -6
  172. data/test/workbook/worksheet/table/tc_table.rb +71 -0
  173. data/test/workbook/worksheet/table/tc_table.rb~ +72 -0
  174. data/test/workbook/worksheet/tc_cell.rb +80 -13
  175. data/test/workbook/worksheet/tc_col.rb +59 -0
  176. data/test/workbook/worksheet/tc_col.rb~ +10 -0
  177. data/test/workbook/worksheet/tc_date_time_converter.rb +132 -0
  178. data/test/workbook/worksheet/tc_page_margins.rb +97 -0
  179. data/test/workbook/worksheet/tc_row.rb +54 -4
  180. data/test/workbook/worksheet/tc_worksheet.rb +205 -31
  181. metadata +78 -84
  182. data/examples/example.rb~ +0 -112
  183. data/test/drawing/tc_line_series.tc~ +0 -34
  184. data/test/drawing/tc_picture_locking.rb~ +0 -77
@@ -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,145 @@ 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
+
84
+ # definition of characters which are less than the maximum width of 0-9 in the default font for use in String#count.
85
+ # This is used for autowidth calculations
86
+ # @return [String]
87
+ def self.thin_chars
88
+ @thin_chars ||= "^.acefijklrstxyzFIJL()-"
89
+ end
90
+
38
91
  # Creates a new worksheet.
39
92
  # @note the recommended way to manage worksheets is Workbook#add_worksheet
40
93
  # @see Workbook#add_worksheet
41
- # @option options [String] name The name of this sheet.
94
+ # @option options [String] name The name of this worksheet.
95
+ # @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
96
+ # @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
42
97
  def initialize(wb, options={})
43
- @drawing = nil
44
- @rows = SimpleTypedList.new Row
45
98
  self.workbook = wb
46
99
  @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
100
+
101
+ @drawing = @page_margins = @auto_filter = nil
51
102
  @merged_cells = []
103
+ @auto_fit_data = []
104
+
105
+ @selected = false
106
+ @show_gridlines = true
107
+ self.name = "Sheet" + (index+1).to_s
108
+ @page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
109
+
110
+ @rows = SimpleTypedList.new Row
111
+ @column_info = SimpleTypedList.new Col
112
+ # @cols = SimpleTypedList.new Cell
113
+ @tables = SimpleTypedList.new Table
114
+
115
+ options.each do |o|
116
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
117
+ end
118
+ end
119
+
120
+ # convinience method to access all cells in this worksheet
121
+ # @return [Array] cells
122
+ def cells
123
+ rows.flatten
52
124
  end
53
125
 
54
- # Creates merge information for this worksheet.
126
+ # Creates merge information for this worksheet.
55
127
  # Cells can be merged by calling the merge_cells method on a worksheet.
56
- # @example This would merge the three cells C1..E1 #
128
+ # @example This would merge the three cells C1..E1 #
57
129
  # worksheet.merge_cells "C1:E1"
58
130
  # # you can also provide an array of cells to be merged
59
131
  # worksheet.merge_cells worksheet.rows.first.cells[(2..4)]
60
132
  # #alternatively you can do it from a single cell
61
133
  # worksheet["C1"].merge worksheet["E1"]
62
- # @param [Array, string]
134
+ # @param [Array, string]
63
135
  def merge_cells(cells)
64
136
  @merged_cells << if cells.is_a?(String)
65
137
  cells
66
138
  elsif cells.is_a?(Array)
67
139
  cells = cells.sort { |x, y| x.r <=> y.r }
68
140
  "#{cells.first.r}:#{cells.last.r}"
69
- end
141
+ end
70
142
  end
71
143
 
72
144
 
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]
145
+ # The demensions of a worksheet. This is not actually a required element by the spec,
146
+ # but at least a few other document readers expect this for conversion
147
+ # @return [String] the A1:B2 style reference for the first and last row column intersection in the workbook
148
+ def dimension
149
+ dim_start = rows.first.cells.first == nil ? 'A1' : rows.first.cells.first.r
150
+ dim_end = rows.last.cells.last == nil ? 'AA:200' : rows.last.cells.last.r
151
+ "#{dim_start}:#{dim_end}"
152
+ end
79
153
 
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
154
+ # Indicates if gridlines should be shown in the sheet.
155
+ # This is true by default.
156
+ # @return [Boolean]
157
+ def show_gridlines=(v)
158
+ Axlsx::validate_boolean v
159
+ @show_gridlines = v
160
+ end
161
+
162
+ # @see selected
163
+ # @return [Boolean]
164
+ def selected=(v)
165
+ Axlsx::validate_boolean v
166
+ @selected = v
92
167
  end
93
168
 
169
+
170
+ # Indicates if the worksheet should print in a single page.
171
+ # This is true by default.
172
+ # @return [Boolean]
173
+ def fit_to_page=(v)
174
+ Axlsx::validate_boolean v
175
+ @fit_to_page = v
176
+ end
177
+
178
+
94
179
  # returns the column and row index for a named based cell
95
180
  # @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
96
181
  # @return [Cell]
@@ -101,18 +186,26 @@ module Axlsx
101
186
  end
102
187
 
103
188
  # The name of the worksheet
189
+ # The name of a worksheet must be unique in the workbook, and must not exceed 31 characters
104
190
  # @param [String] v
105
- def name=(v)
191
+ def name=(v)
106
192
  DataTypeValidator.validate "Worksheet.name", String, v
193
+ raise ArgumentError, (ERR_SHEET_NAME_TOO_LONG % v) if v.size > 31
107
194
  sheet_names = @workbook.worksheets.map { |s| s.name }
108
- raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
109
- @name=v
195
+ raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
196
+ @name=v
197
+ end
198
+
199
+ # The absolute auto filter range
200
+ # @see auto_filter
201
+ def abs_auto_filter
202
+ Axlsx.cell_range(@auto_filter.split(':').collect { |name| name_to_cell(name)}) if @auto_filter
110
203
  end
111
204
 
112
205
  # The auto filter range for the worksheet
113
206
  # @param [String] v
114
207
  # @see auto_filter
115
- def auto_filter=(v)
208
+ def auto_filter=(v)
116
209
  DataTypeValidator.validate "Worksheet.auto_filter", String, v
117
210
  @auto_filter = v
118
211
  end
@@ -149,18 +242,55 @@ module Axlsx
149
242
  @drawing || @drawing = Axlsx::Drawing.new(self)
150
243
  end
151
244
 
152
- # Adds a row to the worksheet and updates auto fit data
245
+ # Adds a row to the worksheet and updates auto fit data.
246
+ # @example - put a vanilla row in your spreadsheet
247
+ # ws.add_row [1, 'fish on my pl', '8']
248
+ #
249
+ # @example - specify a fixed width for a column in your spreadsheet
250
+ # # The first column will ignore the content of this cell when calculating column autowidth.
251
+ # # The second column will include this text in calculating the columns autowidth
252
+ # # The third cell will set a fixed with of 80 for the column.
253
+ # # If you need to un-fix a column width, use :auto. That will recalculate the column width based on all content in the column
254
+ #
255
+ # ws.add_row ['I wish', 'for a fish', 'on my fish wish dish'], :widths=>[:ignore, :auto, 80]
256
+ #
257
+ # @example - specify a fixed height for a row
258
+ # ws.add_row ['I wish', 'for a fish', 'on my fish wish dish'], :height => 40
259
+ #
260
+ # @example - create and use a style for all cells in the row
261
+ # blue = ws.styles.add_style :color => "#00FF00"
262
+ # ws.add_row [1, 2, 3], :style=>blue
263
+ #
264
+ # @example - only style some cells
265
+ # blue = ws.styles.add_style :color => "#00FF00"
266
+ # red = ws.styles.add_style :color => "#FF0000"
267
+ # big = ws.styles.add_style :sz => 40
268
+ # ws.add_row ["red fish", "blue fish", "one fish", "two fish"], :style=>[red, blue, nil, big] # the last nil is optional
269
+ #
270
+ #
271
+ # @example - force the second cell to be a float value
272
+ # ws.add_row [3, 4, 5], :types => [nil, :float]
273
+ #
274
+ # @example - use << alias
275
+ # ws << [3, 4, 5], :types => [nil, :float]
276
+ #
277
+ # @see Worksheet#column_widths
153
278
  # @return [Row]
154
279
  # @option options [Array] values
155
- # @option options [Array, Symbol] types
156
- # @option options [Array, Integer] style
280
+ # @option options [Array, Symbol] types
281
+ # @option options [Array, Integer] style
282
+ # @option options [Array] widths each member of the widths array will affect how auto_fit behavies.
283
+ # @option options [Float] height the row's height (in points)
157
284
  def add_row(values=[], options={})
158
285
  Row.new(self, values, options)
159
- update_auto_fit_data @rows.last.cells
286
+ update_column_info @rows.last.cells, options.delete(:widths) ||[], options.delete(:style) || []
287
+ # update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
160
288
  yield @rows.last if block_given?
161
289
  @rows.last
162
290
  end
163
291
 
292
+ alias :<< :add_row
293
+
164
294
  # Set the style for cells in a specific row
165
295
  # @param [Integer] index or range of indexes in the table
166
296
  # @param [Integer] the cellXfs index
@@ -193,8 +323,9 @@ module Axlsx
193
323
  # @see README.md for an example
194
324
  def col_style(index, style, options={})
195
325
  offset = options.delete(:row_offset) || 0
196
- @rows[(offset..-1)].each do |r|
326
+ @rows[(offset..-1)].each do |r|
197
327
  cells = r.cells[index]
328
+ next unless cells
198
329
  if cells.is_a?(Array)
199
330
  cells.each { |c| c.style = style }
200
331
  else
@@ -202,15 +333,31 @@ module Axlsx
202
333
  end
203
334
  end
204
335
  end
205
-
206
- # 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.
336
+
337
+ # This is a helper method that Lets you specify a fixed width for multiple columns in a worksheet in one go.
338
+ # Axlsx is sparse, so if you have not set data for a column, you cannot set the width.
339
+ # Setting a fixed column width to nil will revert the behaviour back to calculating the width for you.
340
+ # @example This would set the first and third column widhts but leave the second column in autofit state.
341
+ # ws.column_widths 7.2, nil, 3
342
+ # @note For updating only a single column it is probably easier to just set the width of the ws.column_info[col_index].width directly
343
+ # @param [Integer|Float|Fixnum|nil] values
344
+ def column_widths(*args)
345
+ args.each_with_index do |value, index|
346
+ next if value == nil
347
+ Axlsx::validate_unsigned_numeric(value) unless value == nil
348
+ @column_info[index] ||= Col.new index+1, index+1
349
+ @column_info[index].width = value
350
+ end
351
+ end
352
+
353
+ # 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.
207
354
  # @param [Class] chart_type
208
355
  # @option options [Array] start_at
209
356
  # @option options [Array] end_at
210
357
  # @option options [Cell, String] title
211
358
  # @option options [Boolean] show_legend
212
- # @option options [Integer] style
213
- # @note each chart type also specifies additional options
359
+ # @option options [Integer] style
360
+ # @note each chart type also specifies additional options
214
361
  # @see Chart
215
362
  # @see Pie3DChart
216
363
  # @see Bar3DChart
@@ -222,6 +369,14 @@ module Axlsx
222
369
  chart
223
370
  end
224
371
 
372
+ # needs documentation
373
+ def add_table(ref, options={})
374
+ table = Table.new(ref, self, options)
375
+ @tables << table
376
+ yield table if block_given?
377
+ table
378
+ end
379
+
225
380
  # Adds a media item to the worksheets drawing
226
381
  # @param [Class] media_type
227
382
  # @option options [] unknown
@@ -231,105 +386,101 @@ module Axlsx
231
386
  image
232
387
  end
233
388
 
234
- # Serializes the worksheet document
389
+ # Serializes the object
390
+ # @param [String] str
235
391
  # @return [String]
236
- def to_xml
237
- builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml|
238
- xml.worksheet(:xmlns => XML_NS,
239
- :'xmlns:r' => XML_NS_R) {
240
- if @auto_fit_data.size > 0
241
- xml.cols {
242
- @auto_fit_data.each_with_index do |col, index|
243
- min_max = index+1
244
- xml.col(:min=>min_max, :max=>min_max, :width => auto_width(col), :customWidth=>1)
245
- end
246
- }
247
- end
248
- xml.sheetData {
249
- @rows.each do |row|
250
- row.to_xml(xml)
251
- end
252
- }
253
- xml.autoFilter :ref=>@auto_filter if @auto_filter
254
- xml.mergeCells(:count=>@merged_cells.size) { @merged_cells.each { | mc | xml.mergeCell(:ref=>mc) } } unless @merged_cells.empty?
255
- xml.drawing :"r:id"=>"rId1" if @drawing
256
- }
392
+ def to_xml_string
393
+ str = '<?xml version="1.0" encoding="UTF-8"?>'
394
+ str.concat "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
395
+ str.concat "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % fit_to_page if fit_to_page
396
+ str.concat "<dimension ref=\"%s\"></dimension>" % dimension unless rows.size == 0
397
+ str.concat "<sheetViews><sheetView tabSelected='%s' workbookViewId='0' showGridLines='%s'><selection activeCell=\"A1\" sqref=\"A1\"/></sheetView></sheetViews>" % [@selected, show_gridlines]
398
+
399
+ if @column_info.size > 0
400
+ str << "<cols>"
401
+ @column_info.each { |col| col.to_xml_string(str) }
402
+ str.concat '</cols>'
257
403
  end
258
- builder.to_xml
404
+ str.concat '<sheetData>'
405
+ @rows.each_with_index { |row, index| row.to_xml_string(index, str) }
406
+ str.concat '</sheetData>'
407
+ str.concat "<autoFilter ref='%s'></autoFilter>" % @auto_filter if @auto_filter
408
+ str.concat "<mergeCells count='%s'>%s</mergeCells>" % [@merged_cells.size, @merged_cells.reduce('') { |memo, obj| memo += "<mergeCell ref='%s'></mergeCell>" % obj } ] unless @merged_cells.empty?
409
+ page_margins.to_xml_string(str) if @page_margins
410
+ str.concat "<drawing r:id='rId1'></drawing>" if @drawing
411
+ unless @tables.empty?
412
+ str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }]
413
+ end
414
+ str + '</worksheet>'
259
415
  end
260
416
 
261
417
  # The worksheet relationships. This is managed automatically by the worksheet
262
418
  # @return [Relationships]
263
419
  def relationships
264
420
  r = Relationships.new
421
+ @tables.each do |table|
422
+ r << Relationship.new(TABLE_R, "../#{table.pn}")
423
+ end
265
424
  r << Relationship.new(DRAWING_R, "../#{@drawing.pn}") if @drawing
266
425
  r
267
426
  end
268
427
 
269
- private
428
+ # Returns the cell or cells defined using excel style A1:B3 references.
429
+ # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber
430
+ # @return [Cell, Array]
431
+ def [] (cell_def)
432
+ return rows[cell_def] if cell_def.is_a?(Integer)
433
+
434
+ parts = cell_def.split(':')
435
+ first = name_to_cell parts[0]
436
+ if parts.size == 1
437
+ first
438
+ else
439
+ cells = []
440
+ last = name_to_cell(parts[1])
441
+ rows[(first.row.index..last.row.index)].each do |r|
442
+ r.cells[(first.index..last.index)].each do |c|
443
+ cells << c
444
+ end
445
+ end
446
+ cells
447
+ end
448
+ end
449
+
450
+ private
270
451
 
271
452
  # assigns the owner workbook for this worksheet
272
453
  def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
273
454
 
274
- # Updates auto fit data.
275
- # 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
276
- # attempt to identify the longest cell in the column. This is not 100% accurate as it needs to take into account
277
- # any formatting that will be applied to the data, as well as the actual rendering size when the length and size is equal
278
- # for two cells.
279
- # @return [Array] of Cell objects
280
- # @param [Array] cells an array of cells
281
- def update_auto_fit_data(cells)
282
- # TODO delay this until rendering. too much work when we dont know what they are going to do to the sheet.
455
+
456
+ # TODO this needs cleanup!
457
+ def update_column_info(cells, widths=[], style=[])
283
458
  styles = self.workbook.styles
284
459
  cellXfs, fonts = styles.cellXfs, styles.fonts
285
460
  sz = 11
286
- cells.each_with_index do |item, index|
287
- # ignore formula - there is no way for us to know the result
288
- next if item.value.is_a?(String) && item.value.start_with?('=')
289
-
290
- col = @auto_fit_data[index] || {:longest=>"", :sz=>sz}
291
- cell_xf = cellXfs[item.style]
292
- font = fonts[cell_xf.fontId || 0]
293
- sz = item.sz || font.sz || fonts[0].sz
294
- if (col[:longest].scan(/./mu).size * col[:sz]) < (item.value.to_s.scan(/./mu).size * sz)
295
- col[:sz] = sz
296
- col[:longest] = item.value.to_s
461
+
462
+ cells.each_with_index do |cell, index|
463
+ @column_info[index] ||= Col.new index+1, index+1
464
+ col = @column_info[index]
465
+ width = widths[index]
466
+ col.width = width if [Integer, Float, Fixnum].include?(width.class)
467
+ c_style = style[index] if [Integer, Fixnum].include?(style[index].class)
468
+ next if width == :ignore || (cell.value.is_a?(String) && cell.value.start_with?('=') || cell.value == nil)
469
+ if self.workbook.use_autowidth
470
+ cell_xf = cellXfs[(c_style || 0)]
471
+ font = fonts[(cell_xf.fontId || 0)]
472
+ sz = cell.sz || font.sz || sz
473
+ col.width = [(col.width || 0), calculate_width(cell.value.to_s, sz)].max
297
474
  end
298
- @auto_fit_data[index] = col
299
- end
300
- cells
301
- end
302
-
303
- # Determines the proper width for a column based on content.
304
- # @note
305
- # width = Truncate([!{Number of Characters} * !{Maximum Digit Width} + !{5 pixel padding}]/!{Maximum Digit Width}*256)/256
306
- # @return [Float]
307
- # @param [Hash] A hash of auto_fit_data
308
- def auto_width(col)
309
- mdw_count, font_scale, mdw = 0, col[:sz]/11.0, 6.0
310
- mdw_count = col[:longest].scan(/./mu).reduce(0) do | count, char |
311
- count +=1 if @magick_draw.get_type_metrics(char).max_advance >= mdw
312
- count
313
475
  end
314
- ((mdw_count * mdw + 5) / mdw * 256) / 256.0 * font_scale
315
476
  end
316
477
 
317
- # Something to look into:
318
- # width calculation actually needs to be done agains the formatted value for items that apply a
319
- # format
320
- # def excel_format(cell)
321
- # # The most common case.
322
- # return time.value.to_s if cell.style == 0
323
- #
324
- # # The second most common case
325
- # num_fmt = workbook.styles.cellXfs[items.style].numFmtId
326
- # return value.to_s if num_fmt == 0
327
- #
328
- # format_code = workbook.styles.numFmts[num_fmt]
329
- # # need to find some exceptionally fast way of parsing value according to
330
- # # an excel format_code
331
- # item.value.to_s
332
- # end
333
478
 
479
+ # If you *REALLY* want to know what this does, check the spec for how column width is specified.
480
+ def calculate_width(text, sz)
481
+ mdw = 1.78 #magic numbers ftw! This is the widest width of 0..9 in the "standard" font (arial@10px)
482
+ font_scale = sz/10.0
483
+ ((text.count(Worksheet.thin_chars) * mdw + 5) / mdw * 256) / 256.0 * font_scale
484
+ end
334
485
  end
335
486
  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>