axlsx 1.1.8 → 1.2.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 (60) hide show
  1. data/.yardopts +5 -2
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +48 -46
  4. data/Rakefile +3 -3
  5. data/examples/basic_charts.rb +8 -0
  6. data/examples/example.rb +7 -1
  7. data/examples/example.xlsx +0 -0
  8. data/examples/example_streamed.xlsx +0 -0
  9. data/examples/no-use_autowidth.xlsx +0 -0
  10. data/examples/scraping_html.rb +91 -0
  11. data/examples/shared_strings_example.xlsx +0 -0
  12. data/lib/axlsx.rb +14 -8
  13. data/lib/axlsx/drawing/bar_3D_chart.rb +2 -8
  14. data/lib/axlsx/drawing/chart.rb +29 -25
  15. data/lib/axlsx/drawing/d_lbls.rb +100 -0
  16. data/lib/axlsx/drawing/drawing.rb +2 -0
  17. data/lib/axlsx/drawing/line_3D_chart.rb +2 -9
  18. data/lib/axlsx/drawing/pie_3D_chart.rb +3 -0
  19. data/lib/axlsx/drawing/scatter_chart.rb +2 -8
  20. data/lib/axlsx/drawing/two_cell_anchor.rb +38 -1
  21. data/lib/axlsx/util/simple_typed_list.rb +13 -6
  22. data/lib/axlsx/version.rb +2 -7
  23. data/lib/axlsx/workbook/defined_name.rb +174 -0
  24. data/lib/axlsx/workbook/defined_names.rb +21 -0
  25. data/lib/axlsx/workbook/workbook.rb +39 -13
  26. data/lib/axlsx/workbook/worksheet/auto_filter.rb +34 -0
  27. data/lib/axlsx/workbook/worksheet/cell.rb +24 -1
  28. data/lib/axlsx/workbook/worksheet/col.rb +15 -0
  29. data/lib/axlsx/workbook/worksheet/cols.rb +20 -0
  30. data/lib/axlsx/workbook/worksheet/comments.rb +8 -0
  31. data/lib/axlsx/workbook/worksheet/conditional_formattings.rb +25 -0
  32. data/lib/axlsx/workbook/worksheet/data_validations.rb +28 -0
  33. data/lib/axlsx/workbook/worksheet/dimension.rb +65 -0
  34. data/lib/axlsx/workbook/worksheet/merged_cells.rb +35 -0
  35. data/lib/axlsx/workbook/worksheet/protected_ranges.rb +34 -0
  36. data/lib/axlsx/workbook/worksheet/row.rb +1 -1
  37. data/lib/axlsx/workbook/worksheet/sheet_data.rb +25 -0
  38. data/lib/axlsx/workbook/worksheet/sheet_pr.rb +24 -0
  39. data/lib/axlsx/workbook/worksheet/tables.rb +31 -0
  40. data/lib/axlsx/workbook/worksheet/worksheet.rb +263 -380
  41. data/lib/axlsx/workbook/worksheet/worksheet_comments.rb +57 -0
  42. data/lib/axlsx/workbook/worksheet/worksheet_drawing.rb +64 -0
  43. data/test/drawing/tc_bar_series.rb +1 -1
  44. data/test/drawing/tc_chart.rb +7 -1
  45. data/test/drawing/tc_d_lbls.rb +47 -0
  46. data/test/drawing/tc_drawing.rb +5 -4
  47. data/test/drawing/tc_line_series.rb +1 -1
  48. data/test/drawing/tc_pie_3D_chart.rb +1 -1
  49. data/test/drawing/tc_pie_series.rb +1 -1
  50. data/test/drawing/tc_scatter_series.rb +1 -1
  51. data/test/drawing/tc_series.rb +1 -1
  52. data/test/tc_package.rb +16 -1
  53. data/test/workbook/tc_defined_name.rb +41 -0
  54. data/test/workbook/tc_workbook.rb +5 -3
  55. data/test/workbook/worksheet/table/tc_table.rb +0 -8
  56. data/test/workbook/worksheet/tc_cell.rb +2 -4
  57. data/test/workbook/worksheet/tc_protected_range.rb +0 -1
  58. data/test/workbook/worksheet/tc_row.rb +2 -2
  59. data/test/workbook/worksheet/tc_worksheet.rb +19 -21
  60. metadata +48 -7
@@ -0,0 +1,21 @@
1
+ module Axlsx
2
+ # a simple types list of DefinedName objects
3
+ class DefinedNames < SimpleTypedList
4
+
5
+ # creates the DefinedNames object
6
+ def initialize
7
+ super DefinedName
8
+ end
9
+
10
+ # Serialize to xml
11
+ # @param [String] str
12
+ # @return [String]
13
+ def to_xml_string(str = '')
14
+ return if @list.empty?
15
+ str << "<definedNames>"
16
+ each { |defined_name| defined_name.to_xml_string(str) }
17
+ str << '</definedNames>'
18
+ end
19
+ end
20
+ end
21
+
@@ -1,8 +1,9 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  module Axlsx
3
-
3
+ require 'axlsx/workbook/worksheet/auto_filter.rb'
4
4
  require 'axlsx/workbook/worksheet/date_time_converter.rb'
5
5
  require 'axlsx/workbook/worksheet/protected_range.rb'
6
+ require 'axlsx/workbook/worksheet/protected_ranges.rb'
6
7
  require 'axlsx/workbook/worksheet/cell.rb'
7
8
  require 'axlsx/workbook/worksheet/page_margins.rb'
8
9
  require 'axlsx/workbook/worksheet/page_setup.rb'
@@ -13,15 +14,27 @@ require 'axlsx/workbook/worksheet/data_bar.rb'
13
14
  require 'axlsx/workbook/worksheet/icon_set.rb'
14
15
  require 'axlsx/workbook/worksheet/conditional_formatting.rb'
15
16
  require 'axlsx/workbook/worksheet/conditional_formatting_rule.rb'
17
+ require 'axlsx/workbook/worksheet/conditional_formattings.rb'
16
18
  require 'axlsx/workbook/worksheet/row.rb'
17
19
  require 'axlsx/workbook/worksheet/col.rb'
20
+ require 'axlsx/workbook/worksheet/cols.rb'
18
21
  require 'axlsx/workbook/worksheet/comments.rb'
19
22
  require 'axlsx/workbook/worksheet/comment.rb'
23
+ require 'axlsx/workbook/worksheet/merged_cells.rb'
20
24
  require 'axlsx/workbook/worksheet/sheet_protection.rb'
25
+ require 'axlsx/workbook/worksheet/sheet_pr.rb'
26
+ require 'axlsx/workbook/worksheet/dimension.rb'
27
+ require 'axlsx/workbook/worksheet/sheet_data.rb'
28
+ require 'axlsx/workbook/worksheet/worksheet_drawing.rb'
29
+ require 'axlsx/workbook/worksheet/worksheet_comments.rb'
21
30
  require 'axlsx/workbook/worksheet/worksheet.rb'
22
31
  require 'axlsx/workbook/shared_strings_table.rb'
32
+ require 'axlsx/workbook/defined_name.rb'
33
+ require 'axlsx/workbook/defined_names.rb'
23
34
  require 'axlsx/workbook/worksheet/table.rb'
35
+ require 'axlsx/workbook/worksheet/tables.rb'
24
36
  require 'axlsx/workbook/worksheet/data_validation.rb'
37
+ require 'axlsx/workbook/worksheet/data_validations.rb'
25
38
  require 'axlsx/workbook/worksheet/sheet_view.rb'
26
39
  require 'axlsx/workbook/worksheet/pane.rb'
27
40
  require 'axlsx/workbook/worksheet/selection.rb'
@@ -101,13 +114,22 @@ require 'axlsx/workbook/worksheet/selection.rb'
101
114
  # @return [SimpleTypedList]
102
115
  attr_reader :tables
103
116
 
104
- # A colllection of comments associated with this workbook
105
- # @note The recommended way to manage comments is Worksheet#add_comment
117
+
118
+ # A collection of defined names for this workbook
119
+ # @note The recommended way to manage defined names is Workbook#add_defined_name
120
+ # @see DefinedName
121
+ # @return [DefinedNames]
122
+ def defined_names
123
+ @defined_names ||= DefinedNames.new
124
+ end
125
+
126
+ # A collection of comments associated with this workbook
127
+ # @note The recommended way to manage comments is WOrksheet#add_comment
106
128
  # @see Worksheet#add_comment
107
129
  # @see Comment
108
130
  # @return [Comments]
109
131
  def comments
110
- self.worksheets.map { |ws| ws.comments }.compact
132
+ worksheets.map { |sheet| sheet.comments }.compact
111
133
  end
112
134
 
113
135
  # The styles associated with this workbook
@@ -190,6 +212,14 @@ require 'axlsx/workbook/worksheet/selection.rb'
190
212
  worksheet
191
213
  end
192
214
 
215
+ # Adds a defined name to this workbook
216
+ # @return [DefinedName]
217
+ # @param [String] formula @see DefinedName
218
+ # @param [Hash] options @see DefinedName
219
+ def add_defined_name(formula, options)
220
+ defined_names << DefinedName.new(formula, options)
221
+ end
222
+
193
223
  # The workbook relationships. This is managed automatically by the workbook
194
224
  # @return [Relationships]
195
225
  def relationships
@@ -231,17 +261,13 @@ require 'axlsx/workbook/worksheet/selection.rb'
231
261
  str << '<workbookPr date1904="' << @@date1904.to_s << '"/>'
232
262
  str << '<sheets>'
233
263
  @worksheets.each_with_index do |sheet, index|
234
- str << '<sheet name="' << sheet.name << '" sheetId="' << (index+1).to_s << '" r:id="' << sheet.rId << '"/>'
235
- end
236
- str << '</sheets>'
237
- str << '<definedNames>'
238
- @worksheets.each_with_index do |sheet, index|
239
- if sheet.auto_filter
240
- str << '<definedName name="_xlnm._FilterDatabase" localSheetId="' << index.to_s << '" hidden="1">'
241
- str << sheet.abs_auto_filter << '</definedName>'
264
+ str << '<sheet name="' << sheet.name << '" sheetId="' << (index+1).to_s << '" r:id="' << sheet.rId << '"/>'
265
+ if defined_name = sheet.auto_filter.defined_name
266
+ add_defined_name defined_name, :name => '_xlnm._FilterDatabase', :local_sheet_id => index, :hidden => 1
242
267
  end
243
268
  end
244
- str << '</definedNames>'
269
+ str << '</sheets>'
270
+ defined_names.to_xml_string(str)
245
271
  str << '</workbook>'
246
272
  end
247
273
 
@@ -0,0 +1,34 @@
1
+ module Axlsx
2
+
3
+ #This class represents an auto filter range in a worksheet
4
+ class AutoFilter
5
+
6
+ # creates a new Autofilter object
7
+ # @param [Worksheet] worksheet
8
+ def initialize(worksheet)
9
+ raise ArgumentError, 'you must provide a worksheet' unless worksheet.is_a?(Worksheet)
10
+ @worksheet = worksheet
11
+ end
12
+
13
+ attr_reader :worksheet
14
+
15
+ # The range the autofilter should be applied to.
16
+ # This should be a string like 'A1:B8'
17
+ # @return [String]
18
+ attr_accessor :range
19
+
20
+ # the formula for the defined name required for this auto filter
21
+ # @return [String]
22
+ def defined_name
23
+ return unless range
24
+ Axlsx.cell_range(range.split(':').collect { |name| worksheet.name_to_cell(name)})
25
+ end
26
+
27
+ # serialize the object
28
+ # @return [String]
29
+ def to_xml_string(str='')
30
+ str << "<autoFilter ref='#{range}'></autoFilter>"
31
+ end
32
+
33
+ end
34
+ end
@@ -318,11 +318,34 @@ module Axlsx
318
318
  end
319
319
 
320
320
  def is_formula?
321
- @type == :string && @value.start_with?('=')
321
+ @type == :string && @value.to_s.start_with?('=')
322
322
  end
323
323
 
324
+ # This is still not perfect...
325
+ # - scaling is not linear as font sizes increst
326
+ # - different fonts have different mdw and char widths
327
+ def autowidth
328
+ return if is_formula? || value == nil
329
+ mdw = 1.78 #This is the widest width of 0..9 in arial@10px)
330
+ font_scale = (font_size/10.0).to_f
331
+ ((value.to_s.count(Worksheet.thin_chars) * mdw + 5) / mdw * 256) / 256.0 * font_scale
332
+ end
333
+
334
+ # returns the absolute or relative string style reference for
335
+ # this cell.
336
+ # @param [Boolean] absolute -when false a relative reference will be
337
+ # returned.
338
+ # @return [String]
339
+ def reference(absolute=true)
340
+ absolute ? r_abs : r
341
+ end
342
+
324
343
  private
325
344
 
345
+ def font_size
346
+ sz || @styles.fonts[@styles.cellXfs[style].fontId].sz
347
+ end
348
+
326
349
  # Utility method for setting inline style attributes
327
350
  def set_run_style( validator, attr, value)
328
351
  return unless INLINE_STYLES.include?(attr.to_s)
@@ -102,6 +102,21 @@ module Axlsx
102
102
  self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
103
103
  end
104
104
  end
105
+
106
+ # updates the width for this col based on the cells autowidth and
107
+ # an optionally specified fixed width
108
+ # @param [Cell] cell The cell to use in updating this col's width
109
+ # @param [Integer] fixed_width If this is specified the width is set
110
+ # to this value and the cell's attributes are ignored.
111
+ # @param [Boolean] use_autowidth If this is false, the cell's
112
+ # autowidth value will be ignored.
113
+ def update_width(cell, fixed_width=nil, use_autowidth=true)
114
+ if fixed_width.is_a? Numeric
115
+ self.width = fixed_width
116
+ elsif use_autowidth
117
+ self.width = [width || 0, cell.autowidth || 0].max
118
+ end
119
+ end
105
120
 
106
121
  # Serialize this columns data to an xml string
107
122
  # @param [String] str
@@ -0,0 +1,20 @@
1
+ module Axlsx
2
+
3
+ # The cols class manages the col object used to manage column widths.
4
+ # This is where the magic happens with autowidth
5
+ class Cols < SimpleTypedList
6
+
7
+ def initialize(worksheet)
8
+ raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
9
+ super Col
10
+ @worksheet = worksheet
11
+ end
12
+
13
+ def to_xml_string(str = '')
14
+ return if empty?
15
+ str << '<cols>'
16
+ each { |item| item.to_xml_string(str) }
17
+ str << '</cols>'
18
+ end
19
+ end
20
+ end
@@ -53,6 +53,14 @@ module Axlsx
53
53
  @list.map { |comment| comment.author.to_s }.uniq.sort
54
54
  end
55
55
 
56
+ # The relationships required by this object
57
+ # @return [Array]
58
+ def relationships
59
+ [Relationship.new(VML_DRAWING_R, "../#{vml_drawing.pn}"),
60
+ Relationship.new(COMMENT_R, "../#{pn}"),
61
+ Relationship.new(COMMENT_R_NULL, "NULL")]
62
+ end
63
+
56
64
  # serialize the object
57
65
  # @param [String] str
58
66
  # @return [String]
@@ -0,0 +1,25 @@
1
+ module Axlsx
2
+
3
+ # A simple, self serializing class for storing conditional formattings
4
+ class ConditionalFormattings < SimpleTypedList
5
+
6
+ # creates a new Tables object
7
+ def initialize(worksheet)
8
+ raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
9
+ super ConditionalFormatting
10
+ @worksheet = worksheet
11
+ end
12
+
13
+ # The worksheet that owns this collection of tables
14
+ # @return [Worksheet]
15
+ attr_reader :worksheet
16
+
17
+ # serialize the conditional formattings
18
+ def to_xml_string(str = "")
19
+ return if empty?
20
+ each { |item| item.to_xml_string(str) }
21
+ end
22
+ end
23
+
24
+ end
25
+
@@ -0,0 +1,28 @@
1
+ module Axlsx
2
+
3
+ # A simple, self serializing class for storing conditional formattings
4
+ class DataValidations < SimpleTypedList
5
+
6
+ # creates a new Tables object
7
+ def initialize(worksheet)
8
+ raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
9
+ super DataValidation
10
+ @worksheet = worksheet
11
+ end
12
+
13
+ # The worksheet that owns this collection of tables
14
+ # @return [Worksheet]
15
+ attr_reader :worksheet
16
+
17
+ # serialize the conditional formattings
18
+ def to_xml_string(str = "")
19
+ return if empty?
20
+ str << "<dataValidations count='#{size}'>"
21
+ each { |item| item.to_xml_string(str) }
22
+ str << '</dataValidations>'
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+
@@ -0,0 +1,65 @@
1
+ module Axlsx
2
+
3
+ # This class manages the dimensions for a worksheet.
4
+ # While this node is optional in the specification some readers like
5
+ # LibraOffice require this node to render the sheet
6
+ class Dimension
7
+
8
+ # the default value for the first cell in the dimension
9
+ # @return [String]
10
+ def self.default_first
11
+ @@default_first ||= 'A1'
12
+ end
13
+
14
+ # the default value for the last cell in the dimension
15
+ # @return [String]
16
+ def self.default_last
17
+ @@default_last ||= 'AA200'
18
+ end
19
+
20
+
21
+ # Creates a new dimension object
22
+ # @param[Worksheet] worksheet - the worksheet this dimension applies
23
+ # to.
24
+ def initialize(worksheet)
25
+ raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
26
+ @worksheet = worksheet
27
+ end
28
+
29
+ attr_reader :worksheet
30
+
31
+ # the full refernece for this dimension
32
+ # @return [String]
33
+ def sqref
34
+ "#{first_cell_reference}:#{last_cell_reference}"
35
+ end
36
+
37
+ # serialize the object
38
+ # @return [String]
39
+ def to_xml_string(str = '')
40
+ return if worksheet.rows.empty?
41
+ str << "<dimension ref=\"%s\"></dimension>" % sqref
42
+ end
43
+
44
+ # The first cell in the dimension
45
+ # @return [String]
46
+ def first_cell_reference
47
+ dimension_reference(worksheet.rows.first.cells.first, Dimension.default_first)
48
+ end
49
+
50
+ # the last cell in the dimension
51
+ # @return [String]
52
+ def last_cell_reference
53
+ dimension_reference(worksheet.rows.last.cells.last, Dimension.default_last)
54
+ end
55
+
56
+ private
57
+
58
+ # Returns the reference of a cell or the default specified
59
+ # @return [String]
60
+ def dimension_reference(cell, default)
61
+ return default unless cell.respond_to?(:r)
62
+ cell.r
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,35 @@
1
+ module Axlsx
2
+
3
+ # A simple list of merged cells
4
+ class MergedCells < SimpleTypedList
5
+
6
+ # creates a new MergedCells object
7
+ # @param [Worksheet] worksheet
8
+ def initialize(worksheet)
9
+ raise ArgumentError, 'you must provide a worksheet' unless worksheet.is_a?(Worksheet)
10
+ super String
11
+ end
12
+
13
+ # adds cells to the merged cells collection
14
+ # @param [Array||String] cells The cells to add to the merged cells
15
+ # collection. This can be an array of actual cells or a string style
16
+ # range like 'A1:C1'
17
+ def add(cells)
18
+ @list << if cells.is_a?(String)
19
+ cells
20
+ elsif cells.is_a?(Array)
21
+ Axlsx::cell_range(cells, false)
22
+ end
23
+ end
24
+
25
+ # serialize the object
26
+ # @param [String] str
27
+ # @return [String]
28
+ def to_xml_string(str = '')
29
+ return if @list.empty?
30
+ str << "<mergeCells count='#{size}'>"
31
+ each { |merged_cell| str << "<mergeCell ref='#{merged_cell}'></mergeCell>" }
32
+ str << '</mergeCells>'
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ module Axlsx
2
+
3
+ # A self serializing collection of ranges that should be protected in
4
+ # the worksheet
5
+ class ProtectedRanges < SimpleTypedList
6
+
7
+ attr_reader :worksheet
8
+
9
+ def initialize(worksheet)
10
+ raise ArgumentError, 'You must provide a worksheet' unless worksheet.is_a?(Worksheet)
11
+ super ProtectedRange
12
+ @worksheet = worksheet
13
+ end
14
+
15
+ # Adds a protected range
16
+ # @param [Array|String] cells A string range reference or array of cells that will be protected
17
+ def add_range(cells)
18
+ sqref = if cells.is_a?(String)
19
+ cells
20
+ elsif cells.is_a?(SimpleTypedList) || cells.is_a?(Array)
21
+ Axlsx::cell_range(cells, false)
22
+ end
23
+ @list << ProtectedRange.new(:sqref => sqref, :name => "Range#{size}")
24
+ last
25
+ end
26
+
27
+ def to_xml_string(str = '')
28
+ return if empty?
29
+ str << '<protectedRanges>'
30
+ each { |range| range.to_xml_string(str) }
31
+ str << '</protectedRanges>'
32
+ end
33
+ end
34
+ end