axlsx 1.1.8 → 1.2.0

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