axlsx 1.3.4 → 1.3.5

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