axlsx 1.3.4 → 1.3.5

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 (46) hide show
  1. data/README.md +27 -5
  2. data/examples/example.rb +31 -9
  3. data/examples/ios_preview.rb +14 -0
  4. data/examples/pivot_table.rb +39 -0
  5. data/examples/styles.rb +4 -0
  6. data/lib/axlsx.rb +28 -0
  7. data/lib/axlsx/drawing/bar_3D_chart.rb +15 -10
  8. data/lib/axlsx/drawing/chart.rb +13 -1
  9. data/lib/axlsx/drawing/drawing.rb +12 -11
  10. data/lib/axlsx/drawing/graphic_frame.rb +6 -1
  11. data/lib/axlsx/drawing/hyperlink.rb +5 -0
  12. data/lib/axlsx/drawing/line_3D_chart.rb +3 -2
  13. data/lib/axlsx/drawing/pic.rb +6 -0
  14. data/lib/axlsx/drawing/pie_3D_chart.rb +2 -1
  15. data/lib/axlsx/drawing/scatter_chart.rb +2 -1
  16. data/lib/axlsx/package.rb +13 -0
  17. data/lib/axlsx/rels/relationship.rb +1 -0
  18. data/lib/axlsx/stylesheet/styles.rb +22 -18
  19. data/lib/axlsx/util/accessors.rb +15 -0
  20. data/lib/axlsx/util/constants.rb +25 -5
  21. data/lib/axlsx/util/validators.rb +10 -8
  22. data/lib/axlsx/version.rb +1 -1
  23. data/lib/axlsx/workbook/shared_strings_table.rb +1 -1
  24. data/lib/axlsx/workbook/workbook.rb +27 -3
  25. data/lib/axlsx/workbook/worksheet/cell.rb +26 -64
  26. data/lib/axlsx/workbook/worksheet/cell_serializer.rb +144 -0
  27. data/lib/axlsx/workbook/worksheet/col.rb +9 -2
  28. data/lib/axlsx/workbook/worksheet/pivot_table.rb +259 -0
  29. data/lib/axlsx/workbook/worksheet/pivot_table_cache_definition.rb +65 -0
  30. data/lib/axlsx/workbook/worksheet/pivot_tables.rb +24 -0
  31. data/lib/axlsx/workbook/worksheet/row.rb +10 -1
  32. data/lib/axlsx/workbook/worksheet/sheet_format_pr.rb +60 -0
  33. data/lib/axlsx/workbook/worksheet/worksheet.rb +55 -6
  34. data/test/benchmark.rb +1 -0
  35. data/test/drawing/tc_chart.rb +7 -0
  36. data/test/drawing/tc_graphic_frame.rb +5 -0
  37. data/test/example.xlsx +0 -0
  38. data/test/profile.rb +1 -0
  39. data/test/tc_axlsx.rb +15 -0
  40. data/test/tc_package.rb +13 -5
  41. data/test/workbook/worksheet/tc_cell.rb +5 -1
  42. data/test/workbook/worksheet/tc_pivot_table.rb +102 -0
  43. data/test/workbook/worksheet/tc_pivot_table_cache_definition.rb +46 -0
  44. data/test/workbook/worksheet/tc_sheet_format_pr.rb +88 -0
  45. data/test/workbook/worksheet/tc_worksheet.rb +45 -1
  46. metadata +23 -9
@@ -0,0 +1,144 @@
1
+ module Axlsx
2
+
3
+ # The Cell Serializer class contains the logic for serializing cells based on their type.
4
+ class CellSerializer
5
+ class << self
6
+
7
+
8
+ # Calls the proper serialization method based on type.
9
+ # @param [Integer] row_index The index of the cell's row
10
+ # @param [Integer] column_index The index of the cell's column
11
+ # @param [String] str The string to apend serialization to.
12
+ # @return [String]
13
+ def to_xml_string(row_index, column_index, cell, str='')
14
+ str << '<c r="' << Axlsx::cell_r(column_index, row_index) << '" s="' << cell.style.to_s << '" '
15
+ return str << '/>' if cell.value.nil?
16
+ method = (cell.type.to_s << '_type_serialization').to_sym
17
+ self.send(method, cell, str)
18
+ str << '</c>'
19
+ end
20
+
21
+
22
+ # builds an xml text run based on this cells attributes.
23
+ # @param [String] str The string instance this run will be concated to.
24
+ # @return [String]
25
+ def run_xml_string(cell, str = '')
26
+ if cell.is_text_run?
27
+ data = cell.instance_values.reject{|key, value| value == nil || key == 'value' || key == 'type' }
28
+ keys = data.keys & Cell::INLINE_STYLES
29
+ str << "<r><rPr>"
30
+ keys.each do |key|
31
+ case key
32
+ when 'font_name'
33
+ str << "<rFont val='"<< cell.font_name << "'/>"
34
+ when 'color'
35
+ str << data[key].to_xml_string
36
+ else
37
+ str << "<" << key.to_s << " val='" << data[key].to_s << "'/>"
38
+ end
39
+ end
40
+ str << "</rPr>" << "<t>" << cell.value.to_s << "</t></r>"
41
+ else
42
+ str << "<t>" << cell.value.to_s << "</t>"
43
+ end
44
+ str
45
+ end
46
+
47
+ # serializes cells that are type iso_8601
48
+ # @param [Cell] cell The cell that is being serialized
49
+ # @param [String] str The string the serialized content will be appended to.
50
+ # @return [String]
51
+ def iso_8601_type_serialization(cell, str='')
52
+ value_serialization 'd', cell.value, str
53
+ end
54
+
55
+
56
+ # serializes cells that are type date
57
+ # @param [Cell] cell The cell that is being serialized
58
+ # @param [String] str The string the serialized content will be appended to.
59
+ # @return [String]
60
+ def date_type_serialization(cell, str='')
61
+ value_serialization false, DateTimeConverter::date_to_serial(cell.value).to_s, str
62
+ end
63
+
64
+ # Serializes cells that are type time
65
+ # @param [Cell] cell The cell that is being serialized
66
+ # @param [String] str The string the serialized content will be appended to.
67
+ # @return [String]
68
+ def time_type_serialization(cell, str='')
69
+ value_serialization false, DateTimeConverter::time_to_serial(cell.value).to_s, str
70
+ end
71
+
72
+ # Serializes cells that are type boolean
73
+ # @param [Cell] cell The cell that is being serialized
74
+ # @param [String] str The string the serialized content will be appended to.
75
+ # @return [String]
76
+ def boolean_type_serialization(cell, str='')
77
+ value_serialization 'b', cell.value.to_s, str
78
+ end
79
+
80
+ # Serializes cells that are type float
81
+ # @param [Cell] cell The cell that is being serialized
82
+ # @param [String] str The string the serialized content will be appended to.
83
+ # @return [String]
84
+ def float_type_serialization(cell, str='')
85
+ numeric_type_serialization cell, str
86
+ end
87
+
88
+ # Serializes cells that are type integer
89
+ # @param [Cell] cell The cell that is being serialized
90
+ # @param [String] str The string the serialized content will be appended to.
91
+ # @return [String]
92
+ def integer_type_serialization(cell, str = '')
93
+ numeric_type_serialization cell, str
94
+ end
95
+
96
+
97
+ # Serializes cells that are type formula
98
+ # @param [Cell] cell The cell that is being serialized
99
+ # @param [String] str The string the serialized content will be appended to.
100
+ # @return [String]
101
+ def formula_serialization(cell, str='')
102
+ str << 't="str">' << '<f>' << cell.value.to_s.sub('=', '') << '</f>'
103
+ str << '<v>' << cell.formula_value.to_s << '</v>' unless cell.formula_value.nil?
104
+ end
105
+
106
+ # Serializes cells that are type inline_string
107
+ # @param [Cell] cell The cell that is being serialized
108
+ # @param [String] str The string the serialized content will be appended to.
109
+ # @return [String]
110
+ def inline_string_serialization(cell, str = '')
111
+ str << 't="inlineStr">' << '<is>'
112
+ run_xml_string cell, str
113
+ str << '</is>'
114
+ end
115
+
116
+ # Serializes cells that are type string
117
+ # @param [Cell] cell The cell that is being serialized
118
+ # @param [String] str The string the serialized content will be appended to.
119
+ # @return [String]
120
+ def string_type_serialization(cell, str='')
121
+ if cell.is_formula?
122
+ formula_serialization cell, str
123
+ elsif !cell.ssti.nil?
124
+ value_serialization 's', cell.ssti.to_s, str
125
+ else
126
+ inline_string_serialization cell, str
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def numeric_type_serialization(cell, str = '')
133
+ value_serialization 'n', cell.value.to_s, str
134
+ end
135
+
136
+ def value_serialization(serialization_type, serialization_value, str = '')
137
+ str << 't="' << serialization_type << '"' if serialization_type
138
+ str << '><v>' << serialization_value << '</v>'
139
+ end
140
+
141
+
142
+ end
143
+ end
144
+ end
@@ -104,7 +104,12 @@ module Axlsx
104
104
 
105
105
  # @see Col#width
106
106
  def width=(v)
107
- Axlsx.validate_unsigned_numeric(v) unless v == nil
107
+ # Removing this validation make a 10% difference in performance
108
+ # as it is called EVERY TIME A CELL IS ADDED - the proper solution
109
+ # is to only set this if a calculated value is greated than the
110
+ # current @width value.
111
+ # TODO!!!
112
+ #Axlsx.validate_unsigned_numeric(v) unless v == nil
108
113
  @custom_width = @best_fit = v != nil
109
114
  @width = v
110
115
  end
@@ -120,7 +125,9 @@ module Axlsx
120
125
  if fixed_width.is_a? Numeric
121
126
  self.width = fixed_width
122
127
  elsif use_autowidth
123
- self.width = [width || 0, cell.autowidth || 0].max
128
+ cell_width = cell.autowidth
129
+ self.width = cell_width unless (width || 0) > (cell_width || 0)
130
+ #self.width = [width || 0, cell.autowidth || 0].max
124
131
  end
125
132
  end
126
133
 
@@ -0,0 +1,259 @@
1
+ # encoding: UTF-8
2
+ module Axlsx
3
+ # Table
4
+ # @note Worksheet#add_pivot_table is the recommended way to create tables for your worksheets.
5
+ # @see README for examples
6
+ class PivotTable
7
+
8
+ include Axlsx::OptionsParser
9
+
10
+ # Creates a new PivotTable object
11
+ # @param [String] ref The reference to where the pivot table lives like 'G4:L17'.
12
+ # @param [String] range The reference to the pivot table data like 'A1:D31'.
13
+ # @param [Worksheet] sheet The sheet containing the table data.
14
+ # @option options [Cell, String] name
15
+ # @option options [TableStyle] style
16
+ def initialize(ref, range, sheet, options={})
17
+ @ref = ref
18
+ self.range = range
19
+ @sheet = sheet
20
+ @sheet.workbook.pivot_tables << self
21
+ @name = "PivotTable#{index+1}"
22
+ @rows = []
23
+ @columns = []
24
+ @data = []
25
+ @pages = []
26
+ parse_options options
27
+ yield self if block_given?
28
+ end
29
+
30
+ # The reference to the table data
31
+ # @return [String]
32
+ attr_reader :ref
33
+
34
+ # The name of the table.
35
+ # @return [String]
36
+ attr_reader :name
37
+
38
+ # The name of the sheet.
39
+ # @return [String]
40
+ attr_reader :sheet
41
+
42
+ # The range where the data for this pivot table lives.
43
+ # @return [String]
44
+ attr_reader :range
45
+
46
+ # (see #range)
47
+ def range=(v)
48
+ DataTypeValidator.validate "#{self.class}.range", [String], v
49
+ if v.is_a?(String)
50
+ @range = v
51
+ end
52
+ end
53
+
54
+ # The rows
55
+ # @return [Array]
56
+ attr_reader :rows
57
+
58
+
59
+ # (see #rows)
60
+ def rows=(v)
61
+ DataTypeValidator.validate "#{self.class}.rows", [Array], v
62
+ v.each do |ref|
63
+ DataTypeValidator.validate "#{self.class}.rows[]", [String], ref
64
+ end
65
+ @rows = v
66
+ end
67
+
68
+ # The columns
69
+ # @return [Array]
70
+ attr_reader :columns
71
+
72
+ # (see #columns)
73
+ def columns=(v)
74
+ DataTypeValidator.validate "#{self.class}.columns", [Array], v
75
+ v.each do |ref|
76
+ DataTypeValidator.validate "#{self.class}.columns[]", [String], ref
77
+ end
78
+ @columns = v
79
+ end
80
+
81
+ # The data
82
+ # @return [Array]
83
+ attr_reader :data
84
+
85
+ # (see #data)
86
+ def data=(v)
87
+ DataTypeValidator.validate "#{self.class}.data", [Array], v
88
+ v.each do |ref|
89
+ DataTypeValidator.validate "#{self.class}.data[]", [String], ref
90
+ end
91
+ @data = v
92
+ end
93
+
94
+ # The pages
95
+ # @return [String]
96
+ attr_reader :pages
97
+
98
+ # (see #pages)
99
+ def pages=(v)
100
+ DataTypeValidator.validate "#{self.class}.pages", [Array], v
101
+ v.each do |ref|
102
+ DataTypeValidator.validate "#{self.class}.pages[]", [String], ref
103
+ end
104
+ @pages = v
105
+ end
106
+
107
+ # The index of this chart in the workbooks charts collection
108
+ # @return [Integer]
109
+ def index
110
+ @sheet.workbook.pivot_tables.index(self)
111
+ end
112
+
113
+ # The part name for this table
114
+ # @return [String]
115
+ def pn
116
+ "#{PIVOT_TABLE_PN % (index+1)}"
117
+ end
118
+
119
+ # The relationship part name of this pivot table
120
+ # @return [String]
121
+ def rels_pn
122
+ "#{PIVOT_TABLE_RELS_PN % (index+1)}"
123
+ end
124
+
125
+ # The cache_definition for this pivot table
126
+ # @return [PivotTableCacheDefinition]
127
+ def cache_definition
128
+ @cache_definition ||= PivotTableCacheDefinition.new(self)
129
+ end
130
+
131
+ # The worksheet relationships. This is managed automatically by the worksheet
132
+ # @return [Relationships]
133
+ def relationships
134
+ r = Relationships.new
135
+ r << Relationship.new(PIVOT_TABLE_CACHE_DEFINITION_R, "../#{cache_definition.pn}")
136
+ r
137
+ end
138
+
139
+ # The relation reference id for this table
140
+ # @return [String]
141
+ def rId
142
+ "rId#{index+1}"
143
+ end
144
+
145
+ # Serializes the object
146
+ # @param [String] str
147
+ # @return [String]
148
+ def to_xml_string(str = '')
149
+ str << '<?xml version="1.0" encoding="UTF-8"?>'
150
+ str << '<pivotTableDefinition xmlns="' << XML_NS << '" name="' << name << '" cacheId="' << cache_definition.cache_id.to_s << '" dataOnRows="1" applyNumberFormats="0" applyBorderFormats="0" applyFontFormats="0" applyPatternFormats="0" applyAlignmentFormats="0" applyWidthHeightFormats="1" dataCaption="Data" showMultipleLabel="0" showMemberPropertyTips="0" useAutoFormatting="1" indent="0" compact="0" compactData="0" gridDropZones="1" multipleFieldFilters="0">'
151
+ str << '<location firstDataCol="1" firstDataRow="1" firstHeaderRow="1" ref="' << ref << '"/>'
152
+ str << '<pivotFields count="' << header_cells_count.to_s << '">'
153
+ header_cell_values.each do |cell_value|
154
+ str << pivot_field_for(cell_value)
155
+ end
156
+ str << '</pivotFields>'
157
+ if rows.empty?
158
+ str << '<rowFields count="1"><field x="-2"/></rowFields>'
159
+ str << '<rowItems count="2"><i><x/></i> <i i="1"><x v="1"/></i></rowItems>'
160
+ else
161
+ str << '<rowFields count="' << rows.size.to_s << '">'
162
+ rows.each do |row_value|
163
+ str << '<field x="' << header_index_of(row_value).to_s << '"/>'
164
+ end
165
+ str << '</rowFields>'
166
+ str << '<rowItems count="' << rows.size.to_s << '">'
167
+ rows.size.times do |i|
168
+ str << '<i/>'
169
+ end
170
+ str << '</rowItems>'
171
+ end
172
+ if columns.empty?
173
+ str << '<colItems count="1"><i/></colItems>'
174
+ else
175
+ str << '<colFields count="' << columns.size.to_s << '">'
176
+ columns.each do |column_value|
177
+ str << '<field x="' << header_index_of(column_value).to_s << '"/>'
178
+ end
179
+ str << '</colFields>'
180
+ end
181
+ unless pages.empty?
182
+ str << '<pageFields count="' << pages.size.to_s << '">'
183
+ pages.each do |page_value|
184
+ str << '<pageField fld="' << header_index_of(page_value).to_s << '"/>'
185
+ end
186
+ str << '</pageFields>'
187
+ end
188
+ unless data.empty?
189
+ str << '<dataFields count="' << data.size.to_s << '">'
190
+ data.each do |datum_value|
191
+ str << '<dataField name="Sum of ' << datum_value << '" ' <<
192
+ 'fld="' << header_index_of(datum_value).to_s << '" ' <<
193
+ 'baseField="0" baseItem="0"/>'
194
+ end
195
+ str << '</dataFields>'
196
+ end
197
+ str << '</pivotTableDefinition>'
198
+ end
199
+
200
+ # References for header cells
201
+ # @return [Array]
202
+ def header_cell_refs
203
+ Axlsx::range_to_a(header_range).first
204
+ end
205
+
206
+ # The header cells for the pivot table
207
+ # @return [Array]
208
+ def header_cells
209
+ @sheet[header_range]
210
+ end
211
+
212
+ # The values in the header cells collection
213
+ # @return [Array]
214
+ def header_cell_values
215
+ header_cells.map(&:value)
216
+ end
217
+
218
+ # The number of cells in the header_cells collection
219
+ # @return [Integer]
220
+ def header_cells_count
221
+ header_cells.count
222
+ end
223
+
224
+ # The index of a given value in the header cells
225
+ # @return [Integer]
226
+ def header_index_of(value)
227
+ header_cell_values.index(value)
228
+ end
229
+
230
+ private
231
+
232
+ def pivot_field_for(cell_ref)
233
+ if rows.include? cell_ref
234
+ '<pivotField axis="axisRow" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1">' <<
235
+ '<items count="1"><item t="default"/></items>' <<
236
+ '</pivotField>'
237
+ elsif columns.include? cell_ref
238
+ '<pivotField axis="axisCol" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1">' <<
239
+ '<items count="1"><item t="default"/></items>' <<
240
+ '</pivotField>'
241
+ elsif pages.include? cell_ref
242
+ '<pivotField axis="axisCol" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1">' <<
243
+ '<items count="1"><item t="default"/></items>' <<
244
+ '</pivotField>'
245
+ elsif data.include? cell_ref
246
+ '<pivotField dataField="1" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1">' <<
247
+ '</pivotField>'
248
+ else
249
+ '<pivotField compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1">' <<
250
+ '</pivotField>'
251
+ end
252
+ end
253
+
254
+ def header_range
255
+ range.gsub(/^(\w+?)(\d+)\:(\w+?)\d+$/, '\1\2:\3\2')
256
+ end
257
+
258
+ end
259
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: UTF-8
2
+ module Axlsx
3
+ # Table
4
+ # @note Worksheet#add_pivot_table is the recommended way to create tables for your worksheets.
5
+ # @see README for examples
6
+ class PivotTableCacheDefinition
7
+
8
+ include Axlsx::OptionsParser
9
+
10
+ # Creates a new PivotTable object
11
+ # @param [String] pivot_table The pivot table this cache definition is in
12
+ def initialize(pivot_table)
13
+ @pivot_table = pivot_table
14
+ end
15
+
16
+ # # The reference to the pivot table data
17
+ # # @return [PivotTable]
18
+ attr_reader :pivot_table
19
+
20
+ # The index of this chart in the workbooks charts collection
21
+ # @return [Integer]
22
+ def index
23
+ pivot_table.sheet.workbook.pivot_tables.index(pivot_table)
24
+ end
25
+
26
+ # The part name for this table
27
+ # @return [String]
28
+ def pn
29
+ "#{PIVOT_TABLE_CACHE_DEFINITION_PN % (index+1)}"
30
+ end
31
+
32
+ # The identifier for this cache
33
+ # @return [Integer]
34
+ def cache_id
35
+ index + 1
36
+ end
37
+
38
+ # The relation reference id for this table
39
+ # @return [String]
40
+ def rId
41
+ "rId#{index + 1}"
42
+ end
43
+
44
+ # Serializes the object
45
+ # @param [String] str
46
+ # @return [String]
47
+ def to_xml_string(str = '')
48
+ str << '<?xml version="1.0" encoding="UTF-8"?>'
49
+ str << '<pivotCacheDefinition xmlns="' << XML_NS << '" xmlns:r="' << XML_NS_R << '" invalid="1" refreshOnLoad="1" recordCount="0">'
50
+ str << '<cacheSource type="worksheet">'
51
+ str << '<worksheetSource ref="' << pivot_table.range << '" sheet="Data Sheet"/>'
52
+ str << '</cacheSource>'
53
+ str << '<cacheFields count="' << pivot_table.header_cells_count.to_s << '">'
54
+ pivot_table.header_cells.each do |cell|
55
+ str << '<cacheField name="' << cell.value << '" numFmtId="0">'
56
+ str << '<sharedItems count="0">'
57
+ str << '</sharedItems>'
58
+ str << '</cacheField>'
59
+ end
60
+ str << '</cacheFields>'
61
+ str << '</pivotCacheDefinition>'
62
+ end
63
+
64
+ end
65
+ end