write_xlsx 1.12.3 → 1.15.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +22 -0
  3. data/Changes +23 -0
  4. data/README.md +1 -1
  5. data/lib/write_xlsx/chart/area.rb +2 -2
  6. data/lib/write_xlsx/chart/axis.rb +55 -32
  7. data/lib/write_xlsx/chart/axis_writer.rb +528 -0
  8. data/lib/write_xlsx/chart/bar.rb +2 -2
  9. data/lib/write_xlsx/chart/caption.rb +16 -9
  10. data/lib/write_xlsx/chart/chart_area.rb +121 -0
  11. data/lib/write_xlsx/chart/column.rb +2 -2
  12. data/lib/write_xlsx/chart/d_pt_point_writer.rb +14 -0
  13. data/lib/write_xlsx/chart/doughnut.rb +0 -3
  14. data/lib/write_xlsx/chart/formatting_writer.rb +652 -0
  15. data/lib/write_xlsx/chart/initialization.rb +100 -0
  16. data/lib/write_xlsx/chart/line.rb +4 -3
  17. data/lib/write_xlsx/chart/pie.rb +6 -2
  18. data/lib/write_xlsx/chart/radar.rb +2 -2
  19. data/lib/write_xlsx/chart/scatter.rb +4 -3
  20. data/lib/write_xlsx/chart/series.rb +35 -15
  21. data/lib/write_xlsx/chart/series_data.rb +132 -0
  22. data/lib/write_xlsx/chart/series_writer.rb +318 -0
  23. data/lib/write_xlsx/chart/settings.rb +226 -0
  24. data/lib/write_xlsx/chart/stock.rb +2 -2
  25. data/lib/write_xlsx/chart/table.rb +50 -0
  26. data/lib/write_xlsx/chart/xml_writer.rb +305 -0
  27. data/lib/write_xlsx/chart.rb +286 -2477
  28. data/lib/write_xlsx/chartsheet.rb +35 -83
  29. data/lib/write_xlsx/constants.rb +11 -0
  30. data/lib/write_xlsx/drawing.rb +5 -3
  31. data/lib/write_xlsx/format/alignment_state.rb +39 -0
  32. data/lib/write_xlsx/format/alignment_style.rb +92 -0
  33. data/lib/write_xlsx/format/border_state.rb +47 -0
  34. data/lib/write_xlsx/format/border_style.rb +116 -0
  35. data/lib/write_xlsx/format/fill_state.rb +26 -0
  36. data/lib/write_xlsx/format/fill_style.rb +52 -0
  37. data/lib/write_xlsx/format/font_state.rb +74 -0
  38. data/lib/write_xlsx/format/font_style.rb +172 -0
  39. data/lib/write_xlsx/format/format_state.rb +65 -0
  40. data/lib/write_xlsx/format/number_format_state.rb +20 -0
  41. data/lib/write_xlsx/format/number_format_style.rb +28 -0
  42. data/lib/write_xlsx/format/protection_state.rb +20 -0
  43. data/lib/write_xlsx/format/protection_style.rb +28 -0
  44. data/lib/write_xlsx/format.rb +1093 -426
  45. data/lib/write_xlsx/formats.rb +0 -2
  46. data/lib/write_xlsx/image_property.rb +4 -1
  47. data/lib/write_xlsx/inserted_chart.rb +1 -1
  48. data/lib/write_xlsx/object_positioning.rb +203 -0
  49. data/lib/write_xlsx/package/app.rb +3 -3
  50. data/lib/write_xlsx/package/button.rb +6 -2
  51. data/lib/write_xlsx/package/comments.rb +11 -3
  52. data/lib/write_xlsx/package/conditional_format.rb +7 -3
  53. data/lib/write_xlsx/package/content_types.rb +2 -2
  54. data/lib/write_xlsx/package/core.rb +2 -2
  55. data/lib/write_xlsx/package/custom.rb +3 -2
  56. data/lib/write_xlsx/package/metadata.rb +2 -2
  57. data/lib/write_xlsx/package/packager.rb +0 -3
  58. data/lib/write_xlsx/package/relationships.rb +2 -2
  59. data/lib/write_xlsx/package/rich_value.rb +4 -2
  60. data/lib/write_xlsx/package/rich_value_rel.rb +2 -2
  61. data/lib/write_xlsx/package/rich_value_structure.rb +2 -2
  62. data/lib/write_xlsx/package/rich_value_types.rb +3 -3
  63. data/lib/write_xlsx/package/shared_strings.rb +2 -2
  64. data/lib/write_xlsx/package/styles.rb +13 -9
  65. data/lib/write_xlsx/package/table.rb +8 -2
  66. data/lib/write_xlsx/package/theme.rb +0 -3
  67. data/lib/write_xlsx/package/vml.rb +2 -2
  68. data/lib/write_xlsx/page_setup.rb +192 -0
  69. data/lib/write_xlsx/shape.rb +97 -100
  70. data/lib/write_xlsx/sheets.rb +9 -4
  71. data/lib/write_xlsx/sparkline.rb +2 -2
  72. data/lib/write_xlsx/utility/cell_reference.rb +124 -0
  73. data/lib/write_xlsx/utility/chart_formatting.rb +262 -0
  74. data/lib/write_xlsx/utility/common.rb +44 -0
  75. data/lib/write_xlsx/utility/date_time.rb +113 -0
  76. data/lib/write_xlsx/utility/dimensions.rb +40 -0
  77. data/lib/write_xlsx/utility/drawing.rb +136 -0
  78. data/lib/write_xlsx/utility/rich_text.rb +184 -0
  79. data/lib/write_xlsx/utility/sheetname_quoting.rb +73 -0
  80. data/lib/write_xlsx/utility/string_width.rb +45 -0
  81. data/lib/write_xlsx/utility/url.rb +27 -0
  82. data/lib/write_xlsx/utility/xml_primitives.rb +32 -0
  83. data/lib/write_xlsx/version.rb +1 -1
  84. data/lib/write_xlsx/workbook/chart_data.rb +188 -0
  85. data/lib/write_xlsx/workbook/format_preparation.rb +199 -0
  86. data/lib/write_xlsx/workbook/initialization.rb +223 -0
  87. data/lib/write_xlsx/workbook/package_preparation.rb +231 -0
  88. data/lib/write_xlsx/workbook/workbook_writer.rb +164 -0
  89. data/lib/write_xlsx/workbook.rb +143 -981
  90. data/lib/write_xlsx/worksheet/asset_manager.rb +60 -0
  91. data/lib/write_xlsx/worksheet/autofilter.rb +390 -0
  92. data/lib/write_xlsx/worksheet/cell_data.rb +13 -6
  93. data/lib/write_xlsx/worksheet/cell_data_manager.rb +47 -0
  94. data/lib/write_xlsx/worksheet/cell_data_store.rb +61 -0
  95. data/lib/write_xlsx/worksheet/columns.rb +204 -0
  96. data/lib/write_xlsx/worksheet/comments_support.rb +61 -0
  97. data/lib/write_xlsx/worksheet/conditional_formats.rb +30 -0
  98. data/lib/write_xlsx/worksheet/data_validation.rb +9 -1
  99. data/lib/write_xlsx/worksheet/data_writing.rb +1017 -0
  100. data/lib/write_xlsx/worksheet/drawing_methods.rb +308 -0
  101. data/lib/write_xlsx/worksheet/drawing_preparation.rb +290 -0
  102. data/lib/write_xlsx/worksheet/drawing_relations.rb +76 -0
  103. data/lib/write_xlsx/worksheet/drawing_xml_writer.rb +50 -0
  104. data/lib/write_xlsx/worksheet/formatting.rb +418 -0
  105. data/lib/write_xlsx/worksheet/hyperlink.rb +9 -1
  106. data/lib/write_xlsx/worksheet/initialization.rb +146 -0
  107. data/lib/write_xlsx/worksheet/panes.rb +64 -0
  108. data/lib/write_xlsx/worksheet/print_options.rb +72 -0
  109. data/lib/write_xlsx/worksheet/protection.rb +65 -0
  110. data/lib/write_xlsx/worksheet/rich_text_helpers.rb +78 -0
  111. data/lib/write_xlsx/worksheet/row_col_sizing.rb +69 -0
  112. data/lib/write_xlsx/worksheet/rows.rb +84 -0
  113. data/lib/write_xlsx/worksheet/selection.rb +41 -0
  114. data/lib/write_xlsx/worksheet/xml_writer.rb +1246 -0
  115. data/lib/write_xlsx/worksheet.rb +376 -4530
  116. metadata +66 -4
  117. data/lib/write_xlsx/utility.rb +0 -986
  118. data/lib/write_xlsx/worksheet/page_setup.rb +0 -192
@@ -1,986 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # frozen_string_literal: true
3
-
4
- require 'write_xlsx/col_name'
5
-
6
- module Writexlsx
7
- module Utility
8
- ROW_MAX = 1048576 # :nodoc:
9
- COL_MAX = 16384 # :nodoc:
10
- STR_MAX = 32767 # :nodoc:
11
- SHEETNAME_MAX = 31 # :nodoc:
12
- CHAR_WIDTHS = {
13
- ' ' => 3, '!' => 5, '"' => 6, '#' => 7, '$' => 7, '%' => 11,
14
- '&' => 10, "'" => 3, '(' => 5, ')' => 5, '*' => 7, '+' => 7,
15
- ',' => 4, '-' => 5, '.' => 4, '/' => 6, '0' => 7, '1' => 7,
16
- '2' => 7, '3' => 7, '4' => 7, '5' => 7, '6' => 7, '7' => 7,
17
- '8' => 7, '9' => 7, ':' => 4, ';' => 4, '<' => 7, '=' => 7,
18
- '>' => 7, '?' => 7, '@' => 13, 'A' => 9, 'B' => 8, 'C' => 8,
19
- 'D' => 9, 'E' => 7, 'F' => 7, 'G' => 9, 'H' => 9, 'I' => 4,
20
- 'J' => 5, 'K' => 8, 'L' => 6, 'M' => 12, 'N' => 10, 'O' => 10,
21
- 'P' => 8, 'Q' => 10, 'R' => 8, 'S' => 7, 'T' => 7, 'U' => 9,
22
- 'V' => 9, 'W' => 13, 'X' => 8, 'Y' => 7, 'Z' => 7, '[' => 5,
23
- '\\' => 6, ']' => 5, '^' => 7, '_' => 7, '`' => 4, 'a' => 7,
24
- 'b' => 8, 'c' => 6, 'd' => 8, 'e' => 8, 'f' => 5, 'g' => 7,
25
- 'h' => 8, 'i' => 4, 'j' => 4, 'k' => 7, 'l' => 4, 'm' => 12,
26
- 'n' => 8, 'o' => 8, 'p' => 8, 'q' => 8, 'r' => 5, 's' => 6,
27
- 't' => 5, 'u' => 8, 'v' => 7, 'w' => 11, 'x' => 7, 'y' => 7,
28
- 'z' => 6, '{' => 5, '|' => 7, '}' => 5, '~' => 7
29
- }.freeze
30
- MAX_DIGIT_WIDTH = 7 # For Calabri 11. # :nodoc:
31
- PADDING = 5 # :nodoc:
32
- DEFAULT_COL_PIXELS = 64
33
-
34
- #
35
- # xl_rowcol_to_cell($row, col, row_absolute, col_absolute)
36
- #
37
- def xl_rowcol_to_cell(row_or_name, col, row_absolute = false, col_absolute = false)
38
- if row_or_name.is_a?(Integer)
39
- row_or_name += 1 # Change from 0-indexed to 1 indexed.
40
- end
41
- col_str = xl_col_to_name(col, col_absolute)
42
- "#{col_str}#{absolute_char(row_absolute)}#{row_or_name}"
43
- end
44
-
45
- #
46
- # Returns: [row, col, row_absolute, col_absolute]
47
- #
48
- # The row_absolute and col_absolute parameters aren't documented because they
49
- # mainly used internally and aren't very useful to the user.
50
- #
51
- def xl_cell_to_rowcol(cell)
52
- cell =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/
53
-
54
- col_abs = ::Regexp.last_match(1) != ""
55
- col = ::Regexp.last_match(2)
56
- row_abs = ::Regexp.last_match(3) != ""
57
- row = ::Regexp.last_match(4).to_i
58
-
59
- # Convert base26 column string to number
60
- # All your Base are belong to us.
61
- chars = col.split("")
62
- expn = 0
63
- col = 0
64
-
65
- chars.reverse.each do |char|
66
- col += (char.ord - 'A'.ord + 1) * (26**expn)
67
- expn += 1
68
- end
69
-
70
- # Convert 1-index to zero-index
71
- row -= 1
72
- col -= 1
73
-
74
- [row, col, row_abs, col_abs]
75
- end
76
-
77
- def xl_col_to_name(col, col_absolute)
78
- col_str = ColName.instance.col_str(col)
79
- if col_absolute
80
- "#{absolute_char(col_absolute)}#{col_str}"
81
- else
82
- # Do not allocate new string
83
- col_str
84
- end
85
- end
86
-
87
- def xl_range(row_1, row_2, col_1, col_2,
88
- row_abs_1 = false, row_abs_2 = false, col_abs_1 = false, col_abs_2 = false)
89
- range1 = xl_rowcol_to_cell(row_1, col_1, row_abs_1, col_abs_1)
90
- range2 = xl_rowcol_to_cell(row_2, col_2, row_abs_2, col_abs_2)
91
-
92
- if range1 == range2
93
- range1
94
- else
95
- "#{range1}:#{range2}"
96
- end
97
- end
98
-
99
- def xl_range_formula(sheetname, row_1, row_2, col_1, col_2)
100
- # Use Excel's conventions and quote the sheet name if it contains any
101
- # non-word character or if it isn't already quoted.
102
- sheetname = "'#{sheetname}'" if sheetname =~ /\W/ && !(sheetname =~ /^'/)
103
-
104
- range1 = xl_rowcol_to_cell(row_1, col_1, 1, 1)
105
- range2 = xl_rowcol_to_cell(row_2, col_2, 1, 1)
106
-
107
- "=#{sheetname}!#{range1}:#{range2}"
108
- end
109
-
110
- #
111
- # xl_string_pixel_width($string)
112
- #
113
- # Get the pixel width of a string based on individual character widths taken
114
- # from Excel. UTF8 characters are given a default width of 8.
115
- #
116
- # Note, Excel adds an additional 7 pixels padding to a cell.
117
- #
118
- def xl_string_pixel_width(string)
119
- length = 0
120
- string.to_s.split("").each { |char| length += CHAR_WIDTHS[char] || 8 }
121
-
122
- length
123
- end
124
-
125
- #
126
- # Sheetnames used in references should be quoted if they contain any spaces,
127
- # special characters or if the look like something that isn't a sheet name.
128
- # TODO. We need to handle more special cases.
129
- #
130
- def quote_sheetname(sheetname) # :nodoc:
131
- # Use Excel's conventions and quote the sheet name if it comtains any
132
- # non-word character or if it isn't already quoted.
133
- name = sheetname.dup
134
- if name =~ /\W/ && !(name =~ /^'/)
135
- # Double quote and single quoted strings.
136
- name = name.gsub("'", "''")
137
- name = "'#{name}'"
138
- end
139
- name
140
- end
141
-
142
- def check_dimensions(row, col)
143
- raise WriteXLSXDimensionError if !row || row >= ROW_MAX || !col || col >= COL_MAX
144
-
145
- 0
146
- end
147
-
148
- #
149
- # convert_date_time(date_time_string)
150
- #
151
- # The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
152
- # and converts it to a decimal number representing a valid Excel date.
153
- #
154
- def convert_date_time(date_time_string) # :nodoc:
155
- date_time = date_time_string.to_s.sub(/^\s+/, '').sub(/\s+$/, '').sub(/Z$/, '')
156
-
157
- # Check for invalid date char.
158
- return nil if date_time =~ /[^0-9T:\-.Z]/
159
-
160
- # Check for "T" after date or before time.
161
- return nil unless date_time =~ /\dT|T\d/
162
-
163
- days = 0 # Number of days since epoch
164
- seconds = 0 # Time expressed as fraction of 24h hours in seconds
165
-
166
- # Split into date and time.
167
- date, time = date_time.split("T")
168
-
169
- # We allow the time portion of the input DateTime to be optional.
170
- if time
171
- # Match hh:mm:ss.sss+ where the seconds are optional
172
- if time =~ /^(\d\d):(\d\d)(:(\d\d(\.\d+)?))?/
173
- hour = ::Regexp.last_match(1).to_i
174
- min = ::Regexp.last_match(2).to_i
175
- sec = ::Regexp.last_match(4).to_f
176
- else
177
- return nil # Not a valid time format.
178
- end
179
-
180
- # Some boundary checks
181
- return nil if hour >= 24
182
- return nil if min >= 60
183
- return nil if sec >= 60
184
-
185
- # Excel expresses seconds as a fraction of the number in 24 hours.
186
- seconds = ((hour * 60 * 60) + (min * 60) + sec) / (24.0 * 60 * 60)
187
- end
188
-
189
- # We allow the date portion of the input DateTime to be optional.
190
- return seconds if date == ''
191
-
192
- # Match date as yyyy-mm-dd.
193
- if date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/
194
- year = ::Regexp.last_match(1).to_i
195
- month = ::Regexp.last_match(2).to_i
196
- day = ::Regexp.last_match(3).to_i
197
- else
198
- return nil # Not a valid date format.
199
- end
200
-
201
- # Set the epoch as 1900 or 1904. Defaults to 1900.
202
- # Special cases for Excel.
203
- unless date_1904?
204
- return seconds if date == '1899-12-31' # Excel 1900 epoch
205
- return seconds if date == '1900-01-00' # Excel 1900 epoch
206
- return 60 + seconds if date == '1900-02-29' # Excel false leapday
207
- end
208
-
209
- # We calculate the date by calculating the number of days since the epoch
210
- # and adjust for the number of leap days. We calculate the number of leap
211
- # days by normalising the year in relation to the epoch. Thus the year 2000
212
- # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
213
- #
214
- epoch = date_1904? ? 1904 : 1900
215
- offset = date_1904? ? 4 : 0
216
- norm = 300
217
- range = year - epoch
218
-
219
- # Set month days and check for leap year.
220
- mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
221
- leap = 0
222
- leap = 1 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
223
- mdays[1] = 29 if leap != 0
224
-
225
- # Some boundary checks
226
- return nil if year < epoch or year > 9999
227
- return nil if month < 1 or month > 12
228
- return nil if day < 1 or day > mdays[month - 1]
229
-
230
- # Accumulate the number of days since the epoch.
231
- days = day # Add days for current month
232
- (0..(month - 2)).each do |m|
233
- days += mdays[m] # Add days for past months
234
- end
235
- days += range * 365 # Add days for past years
236
- days += (range / 4) # Add leapdays
237
- days -= ((range + offset) / 100) # Subtract 100 year leapdays
238
- days += ((range + offset + norm) / 400) # Add 400 year leapdays
239
- days -= leap # Already counted above
240
-
241
- # Adjust for Excel erroneously treating 1900 as a leap year.
242
- days += 1 if !date_1904? and days > 59
243
-
244
- date_time = sprintf("%0.10f", days + seconds)
245
- date_time = date_time.sub(/\.?0+$/, '') if date_time =~ /\./
246
- if date_time =~ /\./
247
- date_time.to_f
248
- else
249
- date_time.to_i
250
- end
251
- end
252
-
253
- def escape_url(url)
254
- unless url =~ /%[0-9a-fA-F]{2}/
255
- # Escape the URL escape symbol.
256
- url = url.gsub("%", "%25")
257
-
258
- # Escape whitespae in URL.
259
- url = url.gsub(/[\s\x00]/, '%20')
260
-
261
- # Escape other special characters in URL.
262
- re = /(["<>\[\]`^{}])/
263
- while re =~ url
264
- match = $~[1]
265
- url = url.sub(re, sprintf("%%%x", match.ord))
266
- end
267
- end
268
-
269
- url
270
- end
271
-
272
- def absolute_char(absolute)
273
- absolute ? '$' : ''
274
- end
275
-
276
- def xml_str
277
- @writer.string
278
- end
279
-
280
- def self.delete_files(path)
281
- if FileTest.file?(path)
282
- File.delete(path)
283
- elsif FileTest.directory?(path)
284
- Dir.foreach(path) do |file|
285
- next if file =~ /^\.\.?$/ # '.' or '..'
286
-
287
- delete_files(path.sub(%r{/+$}, "") + '/' + file)
288
- end
289
- Dir.rmdir(path)
290
- end
291
- end
292
-
293
- def put_deprecate_message(method)
294
- warn("Warning: calling deprecated method #{method}. This method will be removed in a future release.")
295
- end
296
-
297
- # Check for a cell reference in A1 notation and substitute row and column
298
- def row_col_notation(row_or_a1) # :nodoc:
299
- substitute_cellref(row_or_a1) if row_or_a1.respond_to?(:match) && row_or_a1.to_s =~ /^\D/
300
- end
301
-
302
- #
303
- # Substitute an Excel cell reference in A1 notation for zero based row and
304
- # column values in an argument list.
305
- #
306
- # Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
307
- #
308
- def substitute_cellref(cell, *args) # :nodoc:
309
- # return [*args] if cell.respond_to?(:coerce) # Numeric
310
-
311
- normalized_cell = cell.upcase
312
-
313
- case normalized_cell
314
- # Convert a column range: 'A:A' or 'B:G'.
315
- # A range such as A:A is equivalent to A1:65536, so add rows as required
316
- when /\$?([A-Z]{1,3}):\$?([A-Z]{1,3})/
317
- row1, col1 = xl_cell_to_rowcol(::Regexp.last_match(1) + '1')
318
- row2, col2 = xl_cell_to_rowcol(::Regexp.last_match(2) + ROW_MAX.to_s)
319
- [row1, col1, row2, col2, *args]
320
- # Convert a cell range: 'A1:B7'
321
- when /\$?([A-Z]{1,3}\$?\d+):\$?([A-Z]{1,3}\$?\d+)/
322
- row1, col1 = xl_cell_to_rowcol(::Regexp.last_match(1))
323
- row2, col2 = xl_cell_to_rowcol(::Regexp.last_match(2))
324
- [row1, col1, row2, col2, *args]
325
- # Convert a cell reference: 'A1' or 'AD2000'
326
- when /\$?([A-Z]{1,3}\$?\d+)/
327
- row1, col1 = xl_cell_to_rowcol(::Regexp.last_match(1))
328
- [row1, col1, *args]
329
- else
330
- raise("Unknown cell reference #{normalized_cell}")
331
- end
332
- end
333
-
334
- def underline_attributes(underline)
335
- if underline == 2
336
- [%w[val double]]
337
- elsif underline == 33
338
- [%w[val singleAccounting]]
339
- elsif underline == 34
340
- [%w[val doubleAccounting]]
341
- else
342
- [] # Default to single underline.
343
- end
344
- end
345
-
346
- #
347
- # Write the <color> element.
348
- #
349
- def write_color(name, value, writer = @writer) # :nodoc:
350
- attributes = [[name, value]]
351
-
352
- writer.empty_tag('color', attributes)
353
- end
354
-
355
- PERL_TRUE_VALUES = [false, nil, 0, "0", "", [], {}].freeze
356
- #
357
- # return perl's boolean result
358
- #
359
- def ptrue?(value)
360
- if PERL_TRUE_VALUES.include?(value)
361
- false
362
- else
363
- true
364
- end
365
- end
366
-
367
- def check_parameter(params, valid_keys, method)
368
- invalids = params.keys - valid_keys
369
- unless invalids.empty?
370
- raise WriteXLSXOptionParameterError,
371
- "Unknown parameter '#{invalids.join(", ")}' in #{method}."
372
- end
373
- true
374
- end
375
-
376
- #
377
- # Check that row and col are valid and store max and min values for use in
378
- # other methods/elements.
379
- #
380
- def check_dimensions_and_update_max_min_values(row, col, ignore_row = 0, ignore_col = 0) # :nodoc:
381
- check_dimensions(row, col)
382
- store_row_max_min_values(row) if ignore_row == 0
383
- store_col_max_min_values(col) if ignore_col == 0
384
-
385
- 0
386
- end
387
-
388
- def store_row_max_min_values(row)
389
- @dim_rowmin = row if !@dim_rowmin || (row < @dim_rowmin)
390
- @dim_rowmax = row if !@dim_rowmax || (row > @dim_rowmax)
391
- end
392
-
393
- def store_col_max_min_values(col)
394
- @dim_colmin = col if !@dim_colmin || (col < @dim_colmin)
395
- @dim_colmax = col if !@dim_colmax || (col > @dim_colmax)
396
- end
397
-
398
- def float_to_str(float)
399
- return '' unless float
400
-
401
- if float == float.to_i
402
- float.to_i.to_s
403
- else
404
- float.to_s
405
- end
406
- end
407
-
408
- #
409
- # Convert user defined legend properties to the structure required internally.
410
- #
411
- def legend_properties(params)
412
- legend = Writexlsx::Chart::Legend.new
413
-
414
- legend.position = params[:position] || 'right'
415
- legend.delete_series = params[:delete_series]
416
- legend.font = convert_font_args(params[:font])
417
-
418
- # Set the legend layout.
419
- legend.layout = layout_properties(params[:layout])
420
-
421
- # Turn off the legend.
422
- legend.position = 'none' if params[:none]
423
-
424
- # Set the line properties for the legend.
425
- line = line_properties(params[:line])
426
-
427
- # Allow 'border' as a synonym for 'line'.
428
- line = line_properties(params[:border]) if params[:border]
429
-
430
- # Set the fill properties for the legend.
431
- fill = fill_properties(params[:fill])
432
-
433
- # Set the pattern properties for the legend.
434
- pattern = pattern_properties(params[:pattern])
435
-
436
- # Set the gradient fill properties for the legend.
437
- gradient = gradient_properties(params[:gradient])
438
-
439
- # Pattern fill overrides solid fill.
440
- fill = nil if pattern
441
-
442
- # Gradient fill overrides solid and pattern fills.
443
- if gradient
444
- pattern = nil
445
- fill = nil
446
- end
447
-
448
- # Set the legend layout.
449
- layout = layout_properties(params[:layout])
450
-
451
- legend.line = line
452
- legend.fill = fill
453
- legend.pattern = pattern
454
- legend.gradient = gradient
455
- legend.layout = layout
456
-
457
- @legend = legend
458
- end
459
-
460
- #
461
- # Convert user defined layout properties to the format required internally.
462
- #
463
- def layout_properties(args, is_text = false)
464
- return unless ptrue?(args)
465
-
466
- properties = is_text ? %i[x y] : %i[x y width height]
467
-
468
- # Check for valid properties.
469
- args.keys.each do |key|
470
- raise "Property '#{key}' not allowed in layout options\n" unless properties.include?(key.to_sym)
471
- end
472
-
473
- # Set the layout properties
474
- layout = {}
475
- properties.each do |property|
476
- value = args[property]
477
- # Convert to the format used by Excel for easier testing.
478
- layout[property] = sprintf("%.17g", value)
479
- end
480
-
481
- layout
482
- end
483
-
484
- #
485
- # Convert vertices from pixels to points.
486
- #
487
- def pixels_to_points(vertices)
488
- _col_start, _row_start, _x1, _y1,
489
- _col_end, _row_end, _x2, _y2,
490
- left, top, width, height = vertices.flatten
491
-
492
- left *= 0.75
493
- top *= 0.75
494
- width *= 0.75
495
- height *= 0.75
496
-
497
- [left, top, width, height]
498
- end
499
-
500
- def v_shape_attributes_base(id)
501
- [
502
- ['id', "_x0000_s#{id}"],
503
- ['type', type]
504
- ]
505
- end
506
-
507
- def v_shape_style_base(z_index, vertices)
508
- left, top, width, height = pixels_to_points(vertices)
509
-
510
- left_str = float_to_str(left)
511
- top_str = float_to_str(top)
512
- width_str = float_to_str(width)
513
- height_str = float_to_str(height)
514
- z_index_str = float_to_str(z_index)
515
-
516
- shape_style_base(left_str, top_str, width_str, height_str, z_index_str)
517
- end
518
-
519
- def shape_style_base(left_str, top_str, width_str, height_str, z_index_str)
520
- [
521
- 'position:absolute;',
522
- 'margin-left:',
523
- left_str, 'pt;',
524
- 'margin-top:',
525
- top_str, 'pt;',
526
- 'width:',
527
- width_str, 'pt;',
528
- 'height:',
529
- height_str, 'pt;',
530
- 'z-index:',
531
- z_index_str, ';'
532
- ]
533
- end
534
-
535
- #
536
- # Write the <v:fill> element.
537
- #
538
- def write_fill
539
- @writer.empty_tag('v:fill', fill_attributes)
540
- end
541
-
542
- #
543
- # Write the <v:path> element.
544
- #
545
- def write_comment_path(gradientshapeok, connecttype)
546
- attributes = []
547
-
548
- attributes << %w[gradientshapeok t] if gradientshapeok
549
- attributes << ['o:connecttype', connecttype]
550
-
551
- @writer.empty_tag('v:path', attributes)
552
- end
553
-
554
- #
555
- # Write the <x:Anchor> element.
556
- #
557
- def write_anchor
558
- col_start, row_start, x1, y1, col_end, row_end, x2, y2 = @vertices
559
- data = [col_start, x1, row_start, y1, col_end, x2, row_end, y2].join(', ')
560
-
561
- @writer.data_element('x:Anchor', data)
562
- end
563
-
564
- #
565
- # Write the <x:AutoFill> element.
566
- #
567
- def write_auto_fill
568
- @writer.data_element('x:AutoFill', 'False')
569
- end
570
-
571
- #
572
- # Write the <div> element.
573
- #
574
- def write_div(align, font = nil)
575
- style = "text-align:#{align}"
576
- attributes = [['style', style]]
577
-
578
- @writer.tag_elements('div', attributes) do
579
- if font
580
- # Write the font element.
581
- write_font(font)
582
- end
583
- end
584
- end
585
-
586
- #
587
- # Write the <font> element.
588
- #
589
- def write_font(font)
590
- caption = font[:_caption]
591
- face = 'Calibri'
592
- size = 220
593
- color = '#000000'
594
-
595
- attributes = [
596
- ['face', face],
597
- ['size', size],
598
- ['color', color]
599
- ]
600
- @writer.data_element('font', caption, attributes)
601
- end
602
-
603
- #
604
- # Write the <v:stroke> element.
605
- #
606
- def write_stroke
607
- attributes = [%w[joinstyle miter]]
608
-
609
- @writer.empty_tag('v:stroke', attributes)
610
- end
611
-
612
- def r_id_attributes(id)
613
- ['r:id', "rId#{id}"]
614
- end
615
-
616
- def write_xml_declaration
617
- @writer.xml_decl
618
- yield
619
- @writer.crlf
620
- @writer.close
621
- end
622
-
623
- #
624
- # Convert user defined line properties to the structure required internally.
625
- #
626
- def line_properties(line) # :nodoc:
627
- line_fill_properties(line) do
628
- value_or_raise(dash_types, line[:dash_type], 'dash type')
629
- end
630
- end
631
-
632
- #
633
- # Convert user defined fill properties to the structure required internally.
634
- #
635
- def fill_properties(fill) # :nodoc:
636
- line_fill_properties(fill)
637
- end
638
-
639
- #
640
- # Convert user defined pattern properties to the structure required internally.
641
- #
642
- def pattern_properties(args) # :nodoc:
643
- pattern = {}
644
-
645
- return nil unless args
646
-
647
- # Check the pattern type is present.
648
- return nil unless args.has_key?(:pattern)
649
-
650
- # Check the foreground color is present.
651
- retuen nil unless args.has_key?(:fg_color)
652
-
653
- types = {
654
- 'percent_5' => 'pct5',
655
- 'percent_10' => 'pct10',
656
- 'percent_20' => 'pct20',
657
- 'percent_25' => 'pct25',
658
- 'percent_30' => 'pct30',
659
- 'percent_40' => 'pct40',
660
-
661
- 'percent_50' => 'pct50',
662
- 'percent_60' => 'pct60',
663
- 'percent_70' => 'pct70',
664
- 'percent_75' => 'pct75',
665
- 'percent_80' => 'pct80',
666
- 'percent_90' => 'pct90',
667
-
668
- 'light_downward_diagonal' => 'ltDnDiag',
669
- 'light_upward_diagonal' => 'ltUpDiag',
670
- 'dark_downward_diagonal' => 'dkDnDiag',
671
- 'dark_upward_diagonal' => 'dkUpDiag',
672
- 'wide_downward_diagonal' => 'wdDnDiag',
673
- 'wide_upward_diagonal' => 'wdUpDiag',
674
-
675
- 'light_vertical' => 'ltVert',
676
- 'light_horizontal' => 'ltHorz',
677
- 'narrow_vertical' => 'narVert',
678
- 'narrow_horizontal' => 'narHorz',
679
- 'dark_vertical' => 'dkVert',
680
- 'dark_horizontal' => 'dkHorz',
681
-
682
- 'dashed_downward_diagonal' => 'dashDnDiag',
683
- 'dashed_upward_diagonal' => 'dashUpDiag',
684
- 'dashed_horizontal' => 'dashHorz',
685
- 'dashed_vertical' => 'dashVert',
686
- 'small_confetti' => 'smConfetti',
687
- 'large_confetti' => 'lgConfetti',
688
-
689
- 'zigzag' => 'zigZag',
690
- 'wave' => 'wave',
691
- 'diagonal_brick' => 'diagBrick',
692
- 'horizontal_brick' => 'horzBrick',
693
- 'weave' => 'weave',
694
- 'plaid' => 'plaid',
695
-
696
- 'divot' => 'divot',
697
- 'dotted_grid' => 'dotGrid',
698
- 'dotted_diamond' => 'dotDmnd',
699
- 'shingle' => 'shingle',
700
- 'trellis' => 'trellis',
701
- 'sphere' => 'sphere',
702
-
703
- 'small_grid' => 'smGrid',
704
- 'large_grid' => 'lgGrid',
705
- 'small_check' => 'smCheck',
706
- 'large_check' => 'lgCheck',
707
- 'outlined_diamond' => 'openDmnd',
708
- 'solid_diamond' => 'solidDmnd'
709
- }
710
-
711
- # Check for valid types.
712
- if types[args[:pattern]]
713
- pattern[:pattern] = types[args[:pattern]]
714
- else
715
- raise "Unknown pattern type '#{args[:pattern]}'"
716
- end
717
-
718
- pattern[:bg_color] = args[:bg_color] || '#FFFFFF'
719
- pattern[:fg_color] = args[:fg_color]
720
-
721
- pattern
722
- end
723
-
724
- def line_fill_properties(params)
725
- return { _defined: 0 } unless params
726
-
727
- ret = params.dup
728
- ret[:dash_type] = yield if block_given? && ret[:dash_type]
729
- ret[:_defined] = 1
730
- ret
731
- end
732
-
733
- def dash_types
734
- {
735
- solid: 'solid',
736
- round_dot: 'sysDot',
737
- square_dot: 'sysDash',
738
- dash: 'dash',
739
- dash_dot: 'dashDot',
740
- long_dash: 'lgDash',
741
- long_dash_dot: 'lgDashDot',
742
- long_dash_dot_dot: 'lgDashDotDot',
743
- dot: 'dot',
744
- system_dash_dot: 'sysDashDot',
745
- system_dash_dot_dot: 'sysDashDotDot'
746
- }
747
- end
748
-
749
- def value_or_raise(hash, key, msg)
750
- raise "Unknown #{msg} '#{key}'" if hash[key.to_sym].nil?
751
-
752
- hash[key.to_sym]
753
- end
754
-
755
- def palette_color_from_index(index)
756
- # Adjust the colour index.
757
- idx = index - 8
758
-
759
- r, g, b = @palette[idx]
760
- sprintf("%02X%02X%02X", r, g, b)
761
- end
762
-
763
- #
764
- # Convert user defined font values into private hash values.
765
- #
766
- def convert_font_args(params)
767
- return unless params
768
-
769
- font = params_to_font(params)
770
-
771
- # Convert font size units.
772
- font[:_size] *= 100 if font[:_size] && font[:_size] != 0
773
-
774
- # Convert rotation into 60,000ths of a degree.
775
- font[:_rotation] = 60_000 * font[:_rotation].to_i if ptrue?(font[:_rotation])
776
-
777
- font
778
- end
779
-
780
- def params_to_font(params)
781
- {
782
- _name: params[:name],
783
- _color: params[:color],
784
- _size: params[:size],
785
- _bold: params[:bold],
786
- _italic: params[:italic],
787
- _underline: params[:underline],
788
- _pitch_family: params[:pitch_family],
789
- _charset: params[:charset],
790
- _baseline: params[:baseline] || 0,
791
- _rotation: params[:rotation]
792
- }
793
- end
794
-
795
- #
796
- # Write the <c:txPr> element.
797
- #
798
- def write_tx_pr(font, is_y_axis = nil) # :nodoc:
799
- rotation = nil
800
- rotation = font[:_rotation] if font && font.respond_to?(:[]) && font[:_rotation]
801
- @writer.tag_elements('c:txPr') do
802
- # Write the a:bodyPr element.
803
- write_a_body_pr(rotation, is_y_axis)
804
- # Write the a:lstStyle element.
805
- write_a_lst_style
806
- # Write the a:p element.
807
- write_a_p_formula(font)
808
- end
809
- end
810
-
811
- #
812
- # Write the <a:bodyPr> element.
813
- #
814
- def write_a_body_pr(rot, is_y_axis = nil) # :nodoc:
815
- rot = -5400000 if !rot && ptrue?(is_y_axis)
816
- attributes = []
817
- if rot
818
- if rot == 16_200_000
819
- # 270 deg/stacked angle.
820
- attributes << ['rot', 0]
821
- attributes << %w[vert wordArtVert]
822
- elsif rot == 16_260_000
823
- # 271 deg/stacked angle.
824
- attributes << ['rot', 0]
825
- attributes << %w[vert eaVert]
826
- else
827
- attributes << ['rot', rot]
828
- attributes << %w[vert horz]
829
- end
830
- end
831
-
832
- @writer.empty_tag('a:bodyPr', attributes)
833
- end
834
-
835
- #
836
- # Write the <a:lstStyle> element.
837
- #
838
- def write_a_lst_style # :nodoc:
839
- @writer.empty_tag('a:lstStyle')
840
- end
841
-
842
- #
843
- # Write the <a:p> element for formula titles.
844
- #
845
- def write_a_p_formula(font = nil) # :nodoc:
846
- @writer.tag_elements('a:p') do
847
- # Write the a:pPr element.
848
- write_a_p_pr_formula(font)
849
- # Write the a:endParaRPr element.
850
- write_a_end_para_rpr
851
- end
852
- end
853
-
854
- #
855
- # Write the <a:pPr> element for formula titles.
856
- #
857
- def write_a_p_pr_formula(font) # :nodoc:
858
- @writer.tag_elements('a:pPr') { write_a_def_rpr(font) }
859
- end
860
-
861
- #
862
- # Write the <a:defRPr> element.
863
- #
864
- def write_a_def_rpr(font = nil) # :nodoc:
865
- write_def_rpr_r_pr_common(
866
- font,
867
- get_font_style_attributes(font),
868
- 'a:defRPr'
869
- )
870
- end
871
-
872
- def write_def_rpr_r_pr_common(font, style_attributes, tag) # :nodoc:
873
- latin_attributes = get_font_latin_attributes(font)
874
- has_color = ptrue?(font) && ptrue?(font[:_color])
875
-
876
- if !latin_attributes.empty? || has_color
877
- @writer.tag_elements(tag, style_attributes) do
878
- write_a_solid_fill(color: font[:_color]) if has_color
879
- write_a_latin(latin_attributes) unless latin_attributes.empty?
880
- end
881
- else
882
- @writer.empty_tag(tag, style_attributes)
883
- end
884
- end
885
-
886
- #
887
- # Get the font latin attributes from a font hash.
888
- #
889
- def get_font_latin_attributes(font)
890
- return [] unless font
891
- return [] unless font.respond_to?(:[])
892
-
893
- attributes = []
894
- attributes << ['typeface', font[:_name]] if ptrue?(font[:_name])
895
- attributes << ['pitchFamily', font[:_pitch_family]] if font[:_pitch_family]
896
- attributes << ['charset', font[:_charset]] if font[:_charset]
897
-
898
- attributes
899
- end
900
-
901
- #
902
- # Write the <a:solidFill> element.
903
- #
904
- def write_a_solid_fill(fill) # :nodoc:
905
- @writer.tag_elements('a:solidFill') do
906
- if fill[:color]
907
- # Write the a:srgbClr element.
908
- write_a_srgb_clr(color(fill[:color]), fill[:transparency])
909
- end
910
- end
911
- end
912
-
913
- #
914
- # Write the <a:srgbClr> element.
915
- #
916
- def write_a_srgb_clr(color, transparency = nil) # :nodoc:
917
- tag = 'a:srgbClr'
918
- attributes = [['val', color]]
919
-
920
- if ptrue?(transparency)
921
- @writer.tag_elements(tag, attributes) do
922
- write_a_alpha(transparency)
923
- end
924
- else
925
- @writer.empty_tag(tag, attributes)
926
- end
927
- end
928
-
929
- #
930
- # Convert the user specified colour index or string to a rgb colour.
931
- #
932
- def color(color_code) # :nodoc:
933
- if color_code and color_code =~ /^#[0-9a-fA-F]{6}$/
934
- # Convert a HTML style #RRGGBB color.
935
- color_code.sub(/^#/, '').upcase
936
- else
937
- index = Format.color(color_code)
938
- raise "Unknown color '#{color_code}' used in chart formatting." unless index
939
-
940
- palette_color_from_index(index)
941
- end
942
- end
943
-
944
- #
945
- # Get the font style attributes from a font hash.
946
- #
947
- def get_font_style_attributes(font)
948
- return [] unless font
949
- return [] unless font.respond_to?(:[])
950
-
951
- attributes = []
952
- attributes << ['sz', font[:_size]] if ptrue?(font[:_size])
953
- attributes << ['b', font[:_bold]] if font[:_bold]
954
- attributes << ['i', font[:_italic]] if font[:_italic]
955
- attributes << %w[u sng] if font[:_underline]
956
-
957
- # Turn off baseline when testing fonts that don't have it.
958
- attributes << ['baseline', font[:_baseline]] if font[:_baseline] != -1
959
- attributes
960
- end
961
-
962
- #
963
- # Write the <a:endParaRPr> element.
964
- #
965
- def write_a_end_para_rpr # :nodoc:
966
- @writer.empty_tag('a:endParaRPr', [%w[lang en-US]])
967
- end
968
- end
969
-
970
- module WriteDPtPoint
971
- #
972
- # Write an individual <c:dPt> element. Override the parent method to add
973
- # markers.
974
- #
975
- def write_d_pt_point(index, point)
976
- @writer.tag_elements('c:dPt') do
977
- # Write the c:idx element.
978
- write_idx(index)
979
- @writer.tag_elements('c:marker') do
980
- # Write the c:spPr element.
981
- write_sp_pr(point)
982
- end
983
- end
984
- end
985
- end
986
- end