xlsxrb 0.1.15 → 0.1.17
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +125 -0
- data/README.md +24 -5
- data/benchmark/micro_ips.rb +70 -0
- data/lib/xlsxrb/chart_builder.rb +41 -2
- data/lib/xlsxrb/dsl_helpers.rb +522 -6
- data/lib/xlsxrb/elements/cell.rb +34 -9
- data/lib/xlsxrb/elements/column.rb +2 -2
- data/lib/xlsxrb/elements/styles.rb +427 -15
- data/lib/xlsxrb/elements/types.rb +1 -0
- data/lib/xlsxrb/elements/workbook.rb +79 -4
- data/lib/xlsxrb/elements/worksheet.rb +173 -11
- data/lib/xlsxrb/ooxml/cfb.rb +10 -0
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +77 -38
- data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +187 -24
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +115 -2
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +28 -18
- data/lib/xlsxrb/ooxml/reader.rb +253 -136
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +83 -28
- data/lib/xlsxrb/ooxml/styles_parser.rb +76 -25
- data/lib/xlsxrb/ooxml/utils.rb +59 -2
- data/lib/xlsxrb/ooxml/workbook_parser.rb +57 -3
- data/lib/xlsxrb/ooxml/workbook_writer.rb +471 -86
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +210 -127
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +515 -184
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +595 -453
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +26 -9
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +87 -24
- data/lib/xlsxrb/ooxml/writer.rb +299 -59
- data/lib/xlsxrb/ooxml/xml_builder.rb +12 -4
- data/lib/xlsxrb/ooxml/xml_parser.rb +45 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +48 -29
- data/lib/xlsxrb/ooxml/zip_writer.rb +15 -5
- data/lib/xlsxrb/stream_row.rb +4 -3
- data/lib/xlsxrb/stream_sheet.rb +182 -8
- data/lib/xlsxrb/stream_writer.rb +977 -84
- data/lib/xlsxrb/style_builder.rb +59 -6
- data/lib/xlsxrb/utils.rb +80 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +307 -24
- data/lib/xlsxrb/worksheet_builder.rb +1150 -49
- data/lib/xlsxrb.rb +705 -142
- data/sig/generated/xlsxrb/chart_builder.rbs +31 -3
- data/sig/generated/xlsxrb/dsl_helpers.rbs +119 -4
- data/sig/generated/xlsxrb/elements/cell.rbs +20 -3
- data/sig/generated/xlsxrb/elements/column.rbs +3 -3
- data/sig/generated/xlsxrb/elements/styles.rbs +271 -5
- data/sig/generated/xlsxrb/elements/types.rbs +2 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +63 -3
- data/sig/generated/xlsxrb/elements/worksheet.rbs +92 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +74 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +36 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +5 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +29 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +5 -0
- data/sig/generated/xlsxrb/ooxml/utils.rbs +22 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +15 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +20 -4
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +19 -3
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +15 -2
- data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +16 -0
- data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/writer.rbs +42 -8
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +4 -1
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +6 -6
- data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +8 -2
- data/sig/generated/xlsxrb/stream_row.rbs +3 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +122 -6
- data/sig/generated/xlsxrb/stream_writer.rbs +606 -30
- data/sig/generated/xlsxrb/style_builder.rbs +24 -6
- data/sig/generated/xlsxrb/utils.rbs +33 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +87 -5
- data/sig/generated/xlsxrb/worksheet_builder.rbs +557 -12
- data/sig/generated/xlsxrb.rbs +118 -20
- metadata +2 -1
|
@@ -17,9 +17,13 @@ module Xlsxrb
|
|
|
17
17
|
class Worksheet
|
|
18
18
|
[Enumerable, CoordinateAccess].each { |m| include m }
|
|
19
19
|
|
|
20
|
-
attr_reader :name, :rows, :columns, :charts, :conditional_formatting, :data_validations, :unmapped_data, :errors, :state, :hyperlinks, :comments, :images, :styles, :date1904, :print_area, :print_titles, :sparkline_groups, :merged_cells, :auto_filter, :tables, :shapes, :freeze_pane, :page_setup, :page_margins, :header_footer, :print_options, :sheet_protection, :sheet_view, :sheet_properties, :sheet_format, :row_breaks, :col_breaks, :protected_ranges, :cell_watches
|
|
20
|
+
attr_reader :name, :rows, :columns, :charts, :conditional_formatting, :data_validations, :unmapped_data, :errors, :state, :hyperlinks, :comments, :images, :styles, :date1904, :print_area, :print_titles, :sparkline_groups, :merged_cells, :auto_filter, :tables, :shapes, :freeze_pane, :page_setup, :page_margins, :header_footer, :print_options, :sheet_protection, :sheet_view, :sheet_properties, :sheet_format, :row_breaks, :col_breaks, :protected_ranges, :cell_watches, :ignored_errors, :phonetic_properties, :selection, :sheet_calc_pr, :background_image, :threaded_comments
|
|
21
21
|
attr_writer :dimension
|
|
22
22
|
alias conditional_formats conditional_formatting
|
|
23
|
+
alias phonetic_pr phonetic_properties
|
|
24
|
+
alias calc_pr sheet_calc_pr
|
|
25
|
+
alias background_picture background_image
|
|
26
|
+
alias picture background_image
|
|
23
27
|
|
|
24
28
|
# @param name [String] The worksheet name (max 31 characters).
|
|
25
29
|
# @param rows [Array<Elements::Row>] Rows in the sheet.
|
|
@@ -59,8 +63,14 @@ module Xlsxrb
|
|
|
59
63
|
# @param col_breaks [Array<Integer | Hash>] Explicit page column breaks.
|
|
60
64
|
# @param protected_ranges [Array<Hash>] Protected range definitions.
|
|
61
65
|
# @param cell_watches [Array<String>] Watched cell references.
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
# @param ignored_errors [Array<Hash>] Ignored error rules.
|
|
67
|
+
# @param phonetic_properties [Hash, nil] Phonetic text properties.
|
|
68
|
+
# @param selection [Hash, Array<Hash>, nil] Active cell and selection settings.
|
|
69
|
+
# @param sheet_calc_pr [Hash, nil] Worksheet calculation properties.
|
|
70
|
+
# @param background_image [Hash, nil] Worksheet background image specification.
|
|
71
|
+
# @param threaded_comments [Array<Hash>] Threaded comments in the sheet.
|
|
72
|
+
#: (name: String?, ?rows: Array[Elements::Row], ?columns: Array[Elements::Column], ?charts: Array[Hash[Symbol, untyped]], ?conditional_formatting: Array[Hash[Symbol, untyped]]?, ?data_validations: Array[Hash[Symbol, untyped]], ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?, ?conditional_formats: Array[Hash[Symbol, untyped]]?, ?state: Symbol, ?hyperlinks: Hash[String, untyped], ?comments: Array[Hash[Symbol, untyped]], ?images: Array[Elements::Image], ?styles: (Elements::Styles | Hash[untyped, untyped])?, ?date1904: bool, ?dimension: String?, ?trim_empty_rows: bool, ?pad_empty_rows: bool, ?pad_empty_cells: bool, ?print_area: String?, ?print_titles: Hash[Symbol, untyped]?, ?sparkline_groups: Array[Hash[Symbol, untyped]], ?merged_cells: Array[String], ?auto_filter: String?, ?tables: Array[Hash[Symbol, untyped]], ?shapes: Array[Hash[Symbol, untyped]], ?freeze_pane: Hash[Symbol, untyped]?, ?page_setup: Hash[Symbol, untyped], ?page_margins: Hash[Symbol, untyped]?, ?header_footer: Hash[Symbol, untyped], ?print_options: Hash[Symbol, untyped], ?sheet_protection: Hash[Symbol, untyped]?, ?sheet_view: Hash[Symbol, untyped], ?sheet_properties: Hash[Symbol, untyped], ?sheet_format: Hash[Symbol, untyped], ?row_breaks: Array[untyped], ?col_breaks: Array[untyped], ?protected_ranges: Array[Hash[Symbol, untyped]], ?cell_watches: Array[String], ?ignored_errors: Array[Hash[Symbol, untyped]], ?phonetic_properties: Hash[Symbol, untyped]?, ?selection: (Hash[Symbol, untyped] | Array[Hash[Symbol, untyped]])?, ?sheet_calc_pr: Hash[Symbol, untyped]?, ?background_image: Hash[Symbol, untyped]?, ?threaded_comments: Array[Hash[Symbol, untyped]]) -> void
|
|
73
|
+
def initialize(name:, rows: [], columns: [], charts: [], conditional_formatting: nil, data_validations: [], unmapped_data: {}, errors: nil, conditional_formats: nil, state: :visible, hyperlinks: {}, comments: [], images: [], styles: nil, date1904: false, dimension: nil, trim_empty_rows: false, pad_empty_rows: false, pad_empty_cells: false, print_area: nil, print_titles: nil, sparkline_groups: [], merged_cells: [], auto_filter: nil, tables: [], shapes: [], freeze_pane: nil, page_setup: {}, page_margins: nil, header_footer: {}, print_options: {}, sheet_protection: nil, sheet_view: {}, sheet_properties: {}, sheet_format: {}, row_breaks: [], col_breaks: [], protected_ranges: [], cell_watches: [], ignored_errors: [], phonetic_properties: nil, selection: nil, sheet_calc_pr: nil, background_image: nil, threaded_comments: [])
|
|
64
74
|
@name = name
|
|
65
75
|
@rows = (rows || []).freeze
|
|
66
76
|
@columns = (columns || []).freeze
|
|
@@ -84,6 +94,7 @@ module Xlsxrb
|
|
|
84
94
|
@trim_empty_rows = trim_empty_rows ? true : false
|
|
85
95
|
@pad_empty_rows = pad_empty_rows ? true : false
|
|
86
96
|
@pad_empty_cells = pad_empty_cells ? true : false
|
|
97
|
+
@threaded_comments = (threaded_comments || []).freeze
|
|
87
98
|
@merged_cells = (merged_cells || []).freeze
|
|
88
99
|
@auto_filter = auto_filter
|
|
89
100
|
@tables = (tables || []).freeze
|
|
@@ -101,6 +112,11 @@ module Xlsxrb
|
|
|
101
112
|
@col_breaks = (col_breaks || []).freeze
|
|
102
113
|
@protected_ranges = (protected_ranges || unmapped_data[:protected_ranges] || unmapped_data.dig(:facade, :protected_ranges) || []).freeze
|
|
103
114
|
@cell_watches = (cell_watches || unmapped_data[:cell_watches] || unmapped_data.dig(:facade, :cell_watches) || []).freeze
|
|
115
|
+
@ignored_errors = (ignored_errors || unmapped_data[:ignored_errors] || unmapped_data.dig(:facade, :ignored_errors) || []).freeze
|
|
116
|
+
@phonetic_properties = (phonetic_properties || unmapped_data[:phonetic_properties] || unmapped_data.dig(:facade, :phonetic_properties))&.freeze
|
|
117
|
+
@selection = selection&.freeze
|
|
118
|
+
@sheet_calc_pr = (sheet_calc_pr || unmapped_data[:sheet_calc_pr] || unmapped_data.dig(:facade, :sheet_calc_pr) || (@sheet_properties[:full_calc_on_load] ? { full_calc_on_load: true } : nil))&.freeze
|
|
119
|
+
@background_image = (background_image || unmapped_data[:background_image] || unmapped_data.dig(:facade, :background_image))&.freeze
|
|
104
120
|
end
|
|
105
121
|
|
|
106
122
|
# Returns whether trailing empty rows are omitted during iteration.
|
|
@@ -152,6 +168,68 @@ module Xlsxrb
|
|
|
152
168
|
@dimension ||= compute_dimension
|
|
153
169
|
end
|
|
154
170
|
|
|
171
|
+
# Returns the worksheet tab color specification, or nil if not set.
|
|
172
|
+
#
|
|
173
|
+
# @return [String, Hash, nil] RGB hex string (e.g. "FFFF0000") or color hash (e.g. { theme: 3, tint: -0.5 }).
|
|
174
|
+
# @api public
|
|
175
|
+
#: () -> (String | Hash[Symbol, untyped])?
|
|
176
|
+
def tab_color
|
|
177
|
+
props = sheet_properties
|
|
178
|
+
return props[:tab_color] if props[:tab_color]
|
|
179
|
+
return { theme: props[:tab_color_theme], tint: props[:tab_color_tint] } if props[:tab_color_theme]
|
|
180
|
+
return { indexed: props[:tab_color_indexed] } if props[:tab_color_indexed]
|
|
181
|
+
return { auto: true } if props[:tab_color_auto]
|
|
182
|
+
|
|
183
|
+
nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Returns the internal VBA or application code name of the worksheet, or nil if not specified.
|
|
187
|
+
#
|
|
188
|
+
# @return [String, nil]
|
|
189
|
+
# @api public
|
|
190
|
+
#: () -> String?
|
|
191
|
+
def code_name
|
|
192
|
+
sheet_properties[:code_name]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Returns whether fit-to-page is enabled for page setup on this worksheet.
|
|
196
|
+
#
|
|
197
|
+
# @return [Boolean]
|
|
198
|
+
# @api public
|
|
199
|
+
#: () -> bool
|
|
200
|
+
def fit_to_page?
|
|
201
|
+
sheet_properties[:fit_to_page] ? true : false
|
|
202
|
+
end
|
|
203
|
+
alias fit_to_page fit_to_page?
|
|
204
|
+
|
|
205
|
+
# Returns whether full calculation on load is enabled on this worksheet.
|
|
206
|
+
#
|
|
207
|
+
# @return [Boolean]
|
|
208
|
+
# @api public
|
|
209
|
+
#: () -> bool
|
|
210
|
+
def full_calc_on_load?
|
|
211
|
+
(@sheet_calc_pr && @sheet_calc_pr[:full_calc_on_load]) || sheet_properties[:full_calc_on_load] ? true : false
|
|
212
|
+
end
|
|
213
|
+
alias full_calc_on_load full_calc_on_load?
|
|
214
|
+
|
|
215
|
+
# Returns the outline and grouping properties of the worksheet, or an empty hash if none configured.
|
|
216
|
+
#
|
|
217
|
+
# @return [Hash]
|
|
218
|
+
# @api public
|
|
219
|
+
#: () -> Hash[Symbol, untyped]
|
|
220
|
+
def outline_properties
|
|
221
|
+
props = sheet_properties
|
|
222
|
+
return props[:outline_properties] if props[:outline_properties]
|
|
223
|
+
|
|
224
|
+
res = {}
|
|
225
|
+
res[:summary_below] = props[:summary_below] unless props[:summary_below].nil?
|
|
226
|
+
res[:summary_right] = props[:summary_right] unless props[:summary_right].nil?
|
|
227
|
+
res[:show_outline_symbols] = props[:show_outline_symbols] unless props[:show_outline_symbols].nil?
|
|
228
|
+
res[:apply_styles] = props[:apply_styles] unless props[:apply_styles].nil?
|
|
229
|
+
res
|
|
230
|
+
end
|
|
231
|
+
alias outline_pr outline_properties
|
|
232
|
+
|
|
155
233
|
# Iterate over rows in the worksheet.
|
|
156
234
|
#
|
|
157
235
|
# @example
|
|
@@ -362,6 +440,15 @@ module Xlsxrb
|
|
|
362
440
|
@state == :visible
|
|
363
441
|
end
|
|
364
442
|
|
|
443
|
+
# Returns whether the worksheet is very hidden (:very_hidden).
|
|
444
|
+
#
|
|
445
|
+
# @return [Boolean]
|
|
446
|
+
# @api public
|
|
447
|
+
#: () -> bool
|
|
448
|
+
def very_hidden?
|
|
449
|
+
@state == :very_hidden
|
|
450
|
+
end
|
|
451
|
+
|
|
365
452
|
# Returns hyperlink metadata for the given cell reference, or nil.
|
|
366
453
|
#
|
|
367
454
|
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
@@ -406,6 +493,34 @@ module Xlsxrb
|
|
|
406
493
|
end
|
|
407
494
|
end
|
|
408
495
|
|
|
496
|
+
# Returns threaded comments for the given cell reference, or empty array.
|
|
497
|
+
#
|
|
498
|
+
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
499
|
+
# @param col [Integer, nil] Optional 0-based column index.
|
|
500
|
+
# @return [Array<Hash[Symbol, untyped]>]
|
|
501
|
+
# @api public
|
|
502
|
+
#: (String | Symbol | Integer ref_or_row, ?Integer? col) -> Array[Hash[Symbol, untyped]]
|
|
503
|
+
def threaded_comment(ref_or_row, col = nil)
|
|
504
|
+
ref = if col
|
|
505
|
+
"#{Cell.column_letter(col)}#{ref_or_row.to_i + 1}"
|
|
506
|
+
else
|
|
507
|
+
ref_or_row.to_s.upcase
|
|
508
|
+
end
|
|
509
|
+
threaded_comments_by_ref[ref] || []
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# Returns threaded comments indexed by cell reference.
|
|
513
|
+
#
|
|
514
|
+
# @return [Hash<String, Array<Hash[Symbol, untyped]>>]
|
|
515
|
+
# @api public
|
|
516
|
+
#: () -> Hash[String, Array[Hash[Symbol, untyped]]]
|
|
517
|
+
def threaded_comments_by_ref
|
|
518
|
+
@threaded_comments_by_ref ||= @threaded_comments.each_with_object({}) do |c, acc|
|
|
519
|
+
r = c[:ref] || c[:cell]
|
|
520
|
+
(acc[r.to_s.upcase] ||= []) << c if r
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
409
524
|
# Access a cell by reference or 0-based coordinates.
|
|
410
525
|
#
|
|
411
526
|
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
@@ -539,6 +654,26 @@ module Xlsxrb
|
|
|
539
654
|
new_col_breaks = changes.key?(:col_breaks) ? changes[:col_breaks] : col_breaks
|
|
540
655
|
new_protected_ranges = changes.key?(:protected_ranges) ? changes[:protected_ranges] : protected_ranges
|
|
541
656
|
new_cell_watches = changes.key?(:cell_watches) ? changes[:cell_watches] : cell_watches
|
|
657
|
+
new_ignored_errors = changes.key?(:ignored_errors) ? changes[:ignored_errors] : ignored_errors
|
|
658
|
+
new_phonetic_properties = if changes.key?(:phonetic_properties)
|
|
659
|
+
changes[:phonetic_properties]
|
|
660
|
+
elsif changes.key?(:phonetic_pr)
|
|
661
|
+
changes[:phonetic_pr]
|
|
662
|
+
else
|
|
663
|
+
phonetic_properties
|
|
664
|
+
end
|
|
665
|
+
new_selection = changes.key?(:selection) ? changes[:selection] : selection
|
|
666
|
+
new_sheet_calc_pr = changes.key?(:sheet_calc_pr) ? changes[:sheet_calc_pr] : sheet_calc_pr
|
|
667
|
+
new_background_image = if changes.key?(:background_image)
|
|
668
|
+
changes[:background_image]
|
|
669
|
+
elsif changes.key?(:background_picture)
|
|
670
|
+
changes[:background_picture]
|
|
671
|
+
elsif changes.key?(:picture)
|
|
672
|
+
changes[:picture]
|
|
673
|
+
else
|
|
674
|
+
background_image
|
|
675
|
+
end
|
|
676
|
+
new_threaded_comments = changes.key?(:threaded_comments) ? changes[:threaded_comments] : threaded_comments
|
|
542
677
|
|
|
543
678
|
self.class.new(
|
|
544
679
|
name: new_name,
|
|
@@ -575,7 +710,13 @@ module Xlsxrb
|
|
|
575
710
|
row_breaks: new_row_breaks,
|
|
576
711
|
col_breaks: new_col_breaks,
|
|
577
712
|
protected_ranges: new_protected_ranges,
|
|
578
|
-
cell_watches: new_cell_watches
|
|
713
|
+
cell_watches: new_cell_watches,
|
|
714
|
+
ignored_errors: new_ignored_errors,
|
|
715
|
+
phonetic_properties: new_phonetic_properties,
|
|
716
|
+
selection: new_selection,
|
|
717
|
+
sheet_calc_pr: new_sheet_calc_pr,
|
|
718
|
+
background_image: new_background_image,
|
|
719
|
+
threaded_comments: new_threaded_comments
|
|
579
720
|
)
|
|
580
721
|
end
|
|
581
722
|
|
|
@@ -595,6 +736,7 @@ module Xlsxrb
|
|
|
595
736
|
state: state,
|
|
596
737
|
hyperlinks: hyperlinks,
|
|
597
738
|
comments: comments,
|
|
739
|
+
threaded_comments: threaded_comments,
|
|
598
740
|
styles: styles,
|
|
599
741
|
date1904: date1904,
|
|
600
742
|
dimension: dimension,
|
|
@@ -616,7 +758,19 @@ module Xlsxrb
|
|
|
616
758
|
sheet_properties: sheet_properties,
|
|
617
759
|
sheet_format: sheet_format,
|
|
618
760
|
row_breaks: row_breaks,
|
|
619
|
-
col_breaks: col_breaks
|
|
761
|
+
col_breaks: col_breaks,
|
|
762
|
+
protected_ranges: protected_ranges,
|
|
763
|
+
cell_watches: cell_watches,
|
|
764
|
+
ignored_errors: ignored_errors,
|
|
765
|
+
phonetic_properties: phonetic_properties,
|
|
766
|
+
phonetic_pr: phonetic_properties,
|
|
767
|
+
selection: selection,
|
|
768
|
+
sheet_calc_pr: sheet_calc_pr,
|
|
769
|
+
calc_pr: sheet_calc_pr,
|
|
770
|
+
code_name: code_name,
|
|
771
|
+
background_image: background_image,
|
|
772
|
+
background_picture: background_image,
|
|
773
|
+
picture: background_image
|
|
620
774
|
}
|
|
621
775
|
end
|
|
622
776
|
|
|
@@ -628,13 +782,18 @@ module Xlsxrb
|
|
|
628
782
|
name == other.name && rows == other.rows && columns == other.columns && charts == other.charts &&
|
|
629
783
|
conditional_formatting == other.conditional_formatting && data_validations == other.data_validations &&
|
|
630
784
|
state == other.state && hyperlinks == other.hyperlinks && comments == other.comments &&
|
|
785
|
+
threaded_comments == other.threaded_comments &&
|
|
631
786
|
merged_cells == other.merged_cells && auto_filter == other.auto_filter &&
|
|
632
787
|
tables == other.tables && shapes == other.shapes && freeze_pane == other.freeze_pane &&
|
|
633
788
|
page_setup == other.page_setup && page_margins == other.page_margins &&
|
|
634
789
|
header_footer == other.header_footer && print_options == other.print_options &&
|
|
635
790
|
sheet_protection == other.sheet_protection && sheet_view == other.sheet_view &&
|
|
636
791
|
sheet_properties == other.sheet_properties && sheet_format == other.sheet_format &&
|
|
637
|
-
row_breaks == other.row_breaks && col_breaks == other.col_breaks
|
|
792
|
+
row_breaks == other.row_breaks && col_breaks == other.col_breaks &&
|
|
793
|
+
protected_ranges == other.protected_ranges && cell_watches == other.cell_watches &&
|
|
794
|
+
ignored_errors == other.ignored_errors && phonetic_properties == other.phonetic_properties &&
|
|
795
|
+
selection == other.selection && sheet_calc_pr == other.sheet_calc_pr &&
|
|
796
|
+
background_image == other.background_image
|
|
638
797
|
end
|
|
639
798
|
alias eql? ==
|
|
640
799
|
|
|
@@ -642,9 +801,10 @@ module Xlsxrb
|
|
|
642
801
|
def hash
|
|
643
802
|
[
|
|
644
803
|
self.class, name, rows, columns, charts, conditional_formatting, data_validations,
|
|
645
|
-
state, hyperlinks, comments, merged_cells, auto_filter, tables, shapes, freeze_pane,
|
|
804
|
+
state, hyperlinks, comments, threaded_comments, merged_cells, auto_filter, tables, shapes, freeze_pane,
|
|
646
805
|
page_setup, page_margins, header_footer, print_options, sheet_protection, sheet_view,
|
|
647
|
-
sheet_properties, sheet_format, row_breaks, col_breaks
|
|
806
|
+
sheet_properties, sheet_format, row_breaks, col_breaks, protected_ranges,
|
|
807
|
+
cell_watches, ignored_errors, phonetic_properties, selection, sheet_calc_pr, background_image
|
|
648
808
|
].hash
|
|
649
809
|
end
|
|
650
810
|
|
|
@@ -658,7 +818,7 @@ module Xlsxrb
|
|
|
658
818
|
end
|
|
659
819
|
alias to_worksheet load
|
|
660
820
|
|
|
661
|
-
FORBIDDEN_NAME_CHARS = %r{[\\/?*\[\]]}
|
|
821
|
+
FORBIDDEN_NAME_CHARS = %r{[\\/?*\[\]:]|[\x00-\x08\x0B\x0C\x0E-\x1F]}
|
|
662
822
|
|
|
663
823
|
# Returns whether the worksheet name is valid according to OOXML specifications (1..31 chars, no forbidden chars).
|
|
664
824
|
#
|
|
@@ -668,7 +828,7 @@ module Xlsxrb
|
|
|
668
828
|
def self.valid_name?(name)
|
|
669
829
|
case name
|
|
670
830
|
when String
|
|
671
|
-
!name.empty? && name.size <= 31 && !name.match?(FORBIDDEN_NAME_CHARS)
|
|
831
|
+
!name.empty? && name.size <= 31 && !name.match?(FORBIDDEN_NAME_CHARS) && !name.start_with?("'") && !name.end_with?("'") && !name.casecmp?("History")
|
|
672
832
|
else
|
|
673
833
|
false
|
|
674
834
|
end
|
|
@@ -686,7 +846,9 @@ module Xlsxrb
|
|
|
686
846
|
errs << "worksheet name must be a non-empty String (got #{name.inspect})"
|
|
687
847
|
else
|
|
688
848
|
errs << "worksheet name cannot exceed 31 characters (got #{name.size})" if name.size > 31
|
|
689
|
-
errs << "worksheet name cannot contain \\, /, ?, *, [, or
|
|
849
|
+
errs << "worksheet name cannot contain \\, /, ?, *, [, ], or :" if name.match?(FORBIDDEN_NAME_CHARS)
|
|
850
|
+
errs << "worksheet name cannot start or end with an apostrophe" if name.start_with?("'") || name.end_with?("'")
|
|
851
|
+
errs << "worksheet name 'History' is reserved by Excel" if name.casecmp?("History")
|
|
690
852
|
end
|
|
691
853
|
errs << "rows must be an Array (got #{rows.class})" unless rows.is_a?(Array)
|
|
692
854
|
if rows.is_a?(Array)
|
data/lib/xlsxrb/ooxml/cfb.rb
CHANGED
|
@@ -100,9 +100,16 @@ module Xlsxrb
|
|
|
100
100
|
raise Xlsxrb::Error, "CFB file is too small" if @data.bytesize < 512
|
|
101
101
|
|
|
102
102
|
_, major_ver = @data[0x18, 4].unpack("v2")
|
|
103
|
+
raise Xlsxrb::Error, "Unsupported CFB major version: #{major_ver}" unless [3, 4].include?(major_ver)
|
|
104
|
+
|
|
103
105
|
@major_version = major_ver
|
|
104
106
|
@sector_shift = @data[0x1E, 2].unpack1("v")
|
|
105
107
|
@mini_sector_shift = @data[0x20, 2].unpack1("v")
|
|
108
|
+
|
|
109
|
+
expected_shift = @major_version == 4 ? 12 : 9
|
|
110
|
+
raise Xlsxrb::Error, "Invalid CFB sector shift in header: #{@sector_shift}" unless @sector_shift == expected_shift
|
|
111
|
+
raise Xlsxrb::Error, "Invalid CFB mini sector shift in header: #{@mini_sector_shift}" unless @mini_sector_shift == 6
|
|
112
|
+
|
|
106
113
|
@sector_size = 1 << @sector_shift
|
|
107
114
|
@mini_sector_size = 1 << @mini_sector_shift
|
|
108
115
|
|
|
@@ -123,6 +130,9 @@ module Xlsxrb
|
|
|
123
130
|
end
|
|
124
131
|
|
|
125
132
|
def read_sector(sector_id)
|
|
133
|
+
max_sectors = (@data.bytesize - 512) / @sector_size
|
|
134
|
+
return "".b if sector_id.negative? || sector_id >= max_sectors
|
|
135
|
+
|
|
126
136
|
offset = sector_offset(sector_id)
|
|
127
137
|
@data[offset, @sector_size] || "".b
|
|
128
138
|
end
|
|
@@ -24,12 +24,15 @@ module Xlsxrb
|
|
|
24
24
|
@runs = []
|
|
25
25
|
@current_font = {}
|
|
26
26
|
@has_runs = false
|
|
27
|
+
@inside_rph = false
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def start_element(_uri, local_name, qname, attributes)
|
|
30
31
|
name = element_name(local_name, qname)
|
|
31
32
|
|
|
32
33
|
case name
|
|
34
|
+
when "rPh"
|
|
35
|
+
@inside_rph = true
|
|
33
36
|
when "si"
|
|
34
37
|
@inside_si = true
|
|
35
38
|
@text_buffer = +""
|
|
@@ -74,7 +77,7 @@ module Xlsxrb
|
|
|
74
77
|
when "scheme"
|
|
75
78
|
@current_font[:scheme] = attributes["val"] if @inside_rpr
|
|
76
79
|
when "t"
|
|
77
|
-
@inside_t = @inside_si
|
|
80
|
+
@inside_t = @inside_si && !@inside_rph
|
|
78
81
|
@text_buffer = +"" if @inside_r
|
|
79
82
|
end
|
|
80
83
|
end
|
|
@@ -87,12 +90,16 @@ module Xlsxrb
|
|
|
87
90
|
name = element_name(local_name, qname)
|
|
88
91
|
|
|
89
92
|
case name
|
|
93
|
+
when "rPh"
|
|
94
|
+
@inside_rph = false
|
|
90
95
|
when "t"
|
|
91
96
|
@inside_t = false
|
|
92
97
|
when "rPr"
|
|
93
98
|
@inside_rpr = false
|
|
94
99
|
when "r"
|
|
95
|
-
|
|
100
|
+
decoded_text = XmlBuilder.unescape(@text_buffer)
|
|
101
|
+
decoded_text = decoded_text.gsub("\r\n", "\n").tr("\r", "\n") if decoded_text.include?("\r")
|
|
102
|
+
run = { text: decoded_text }
|
|
96
103
|
run[:font] = @current_font.dup unless @current_font.empty?
|
|
97
104
|
@runs << run
|
|
98
105
|
@inside_r = false
|
|
@@ -100,7 +107,9 @@ module Xlsxrb
|
|
|
100
107
|
@strings << if @has_runs
|
|
101
108
|
Xlsxrb::Elements::RichText.new(runs: @runs)
|
|
102
109
|
else
|
|
103
|
-
@text_buffer
|
|
110
|
+
decoded_text = XmlBuilder.unescape(@text_buffer)
|
|
111
|
+
decoded_text = decoded_text.gsub("\r\n", "\n").tr("\r", "\n") if decoded_text.include?("\r")
|
|
112
|
+
decoded_text
|
|
104
113
|
end
|
|
105
114
|
@inside_si = false
|
|
106
115
|
@text_buffer = +""
|
|
@@ -141,22 +150,28 @@ module Xlsxrb
|
|
|
141
150
|
@value_buffer = +""
|
|
142
151
|
@inline_text_buffer = +""
|
|
143
152
|
@formula_buffer = +""
|
|
153
|
+
@shared_formula_masters = {}
|
|
144
154
|
@is_runs = []
|
|
145
155
|
@is_has_runs = false
|
|
146
156
|
@is_current_font = {}
|
|
147
157
|
@is_run_text = +""
|
|
158
|
+
@inside_rph = false
|
|
159
|
+
@has_inline_text = false
|
|
148
160
|
end
|
|
149
161
|
|
|
150
162
|
def start_element(_uri, local_name, qname, attributes)
|
|
151
163
|
name = element_name(local_name, qname)
|
|
152
164
|
|
|
153
165
|
case name
|
|
166
|
+
when "rPh"
|
|
167
|
+
@inside_rph = true
|
|
154
168
|
when "row"
|
|
155
169
|
parse_row_attributes(attributes)
|
|
156
170
|
when "c"
|
|
157
171
|
@current_cell_ref = attributes["r"]
|
|
158
172
|
@current_cell_type = attributes["t"]
|
|
159
173
|
@current_cell_ph = %w[1 true].include?(attributes["ph"])
|
|
174
|
+
@has_inline_text = false
|
|
160
175
|
@value_buffer = +""
|
|
161
176
|
@inline_text_buffer = +""
|
|
162
177
|
@formula_buffer = +""
|
|
@@ -188,6 +203,7 @@ module Xlsxrb
|
|
|
188
203
|
@formula_r1 = attributes["r1"] if attributes["r1"]
|
|
189
204
|
@formula_r2 = attributes["r2"] if attributes["r2"]
|
|
190
205
|
when "is"
|
|
206
|
+
@has_inline_text = true
|
|
191
207
|
@inside_is = true if @current_cell_type == "inlineStr"
|
|
192
208
|
when "r"
|
|
193
209
|
if @inside_is
|
|
@@ -231,11 +247,15 @@ module Xlsxrb
|
|
|
231
247
|
when "scheme"
|
|
232
248
|
@is_current_font[:scheme] = attributes["val"] if @inside_is_rpr
|
|
233
249
|
when "t"
|
|
234
|
-
|
|
235
|
-
@
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
250
|
+
unless @inside_rph
|
|
251
|
+
if @inside_is_r
|
|
252
|
+
@is_run_text = +""
|
|
253
|
+
@inside_inline_text = true
|
|
254
|
+
@has_inline_text = true
|
|
255
|
+
elsif @inside_is || (@current_cell_type == "inlineStr" && !@current_cell_ref.nil?)
|
|
256
|
+
@inside_inline_text = true
|
|
257
|
+
@has_inline_text = true
|
|
258
|
+
end
|
|
239
259
|
end
|
|
240
260
|
end
|
|
241
261
|
end
|
|
@@ -256,6 +276,8 @@ module Xlsxrb
|
|
|
256
276
|
name = element_name(local_name, qname)
|
|
257
277
|
|
|
258
278
|
case name
|
|
279
|
+
when "rPh"
|
|
280
|
+
@inside_rph = false
|
|
259
281
|
when "v"
|
|
260
282
|
@inside_value = false
|
|
261
283
|
when "f"
|
|
@@ -281,6 +303,8 @@ module Xlsxrb
|
|
|
281
303
|
@value_buffer = +""
|
|
282
304
|
@inline_text_buffer = +""
|
|
283
305
|
@formula_buffer = +""
|
|
306
|
+
when "worksheet"
|
|
307
|
+
expand_shared_formulas!
|
|
284
308
|
end
|
|
285
309
|
end
|
|
286
310
|
|
|
@@ -309,10 +333,25 @@ module Xlsxrb
|
|
|
309
333
|
return if @current_cell_ref.nil?
|
|
310
334
|
|
|
311
335
|
unless @formula_buffer.empty? && @formula_type.nil?
|
|
312
|
-
cached = @value_buffer.empty?
|
|
336
|
+
cached = if @value_buffer.empty?
|
|
337
|
+
nil
|
|
338
|
+
elsif @current_cell_type == "b"
|
|
339
|
+
v = @value_buffer.strip
|
|
340
|
+
v == "1" || v.casecmp?("true")
|
|
341
|
+
else
|
|
342
|
+
@value_buffer.dup
|
|
343
|
+
end
|
|
313
344
|
f_type = { "shared" => :shared, "array" => :array, "dataTable" => :data_table }[@formula_type]
|
|
345
|
+
expr = @formula_buffer.dup
|
|
346
|
+
if f_type == :shared && @formula_si
|
|
347
|
+
if !expr.empty?
|
|
348
|
+
@shared_formula_masters[@formula_si] = { cell: @current_cell_ref, expression: expr }
|
|
349
|
+
elsif (master = @shared_formula_masters[@formula_si])
|
|
350
|
+
expr = Xlsxrb::Utils.expand_formula(master[:expression], from_ref: master[:cell], to_ref: @current_cell_ref)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
314
353
|
@cells[@current_cell_ref] = Xlsxrb::Elements::Formula.new(
|
|
315
|
-
expression:
|
|
354
|
+
expression: expr,
|
|
316
355
|
cached_value: cached,
|
|
317
356
|
type: f_type,
|
|
318
357
|
ref: @formula_ref,
|
|
@@ -328,39 +367,39 @@ module Xlsxrb
|
|
|
328
367
|
return
|
|
329
368
|
end
|
|
330
369
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
else
|
|
336
|
-
@inline_text_buffer.dup
|
|
337
|
-
end
|
|
338
|
-
when "s"
|
|
339
|
-
index = @value_buffer.to_i
|
|
340
|
-
@cells[@current_cell_ref] = @shared_strings[index] || ""
|
|
341
|
-
when "e"
|
|
342
|
-
code = @value_buffer.dup
|
|
343
|
-
@cells[@current_cell_ref] = if Xlsxrb::Elements::VALID_ERROR_CODES.include?(code)
|
|
344
|
-
Xlsxrb::Elements::CellError.new(code:)
|
|
345
|
-
else
|
|
346
|
-
code
|
|
347
|
-
end
|
|
348
|
-
when "b"
|
|
349
|
-
@cells[@current_cell_ref] = @value_buffer.strip == "1"
|
|
350
|
-
when nil, "", "n"
|
|
351
|
-
return if @value_buffer.empty?
|
|
370
|
+
if @current_cell_type == "inlineStr" && @is_has_runs
|
|
371
|
+
@cells[@current_cell_ref] = Xlsxrb::Elements::RichText.new(runs: @is_runs.map(&:dup))
|
|
372
|
+
return
|
|
373
|
+
end
|
|
352
374
|
|
|
353
|
-
|
|
354
|
-
|
|
375
|
+
v = if @current_cell_type == "inlineStr"
|
|
376
|
+
@has_inline_text ? @inline_text_buffer : nil
|
|
377
|
+
else
|
|
378
|
+
@value_buffer.empty? ? nil : @value_buffer
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
val = Xlsxrb::Ooxml::WorksheetParser.resolve_cell_value(@current_cell_type, v, nil, @shared_strings)
|
|
382
|
+
return if val.nil? && (@current_cell_type.nil? || @current_cell_type == "" || @current_cell_type == "n" || @current_cell_type == "s" || @current_cell_type == "inlineStr")
|
|
383
|
+
|
|
384
|
+
@cells[@current_cell_ref] = if @current_cell_type == "e" && val && Xlsxrb::Elements::VALID_ERROR_CODES.include?(val)
|
|
385
|
+
Xlsxrb::Elements::CellError.new(code: val)
|
|
386
|
+
else
|
|
387
|
+
val
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def expand_shared_formulas!
|
|
392
|
+
@cells.each do |ref, val|
|
|
393
|
+
next unless val.is_a?(Xlsxrb::Elements::Formula) && val.type == :shared && (val.expression.nil? || val.expression.empty?)
|
|
394
|
+
next unless val.shared_index && (master = @shared_formula_masters[val.shared_index])
|
|
395
|
+
|
|
396
|
+
expanded = Xlsxrb::Utils.expand_formula(master[:expression], from_ref: master[:cell], to_ref: ref)
|
|
397
|
+
@cells[ref] = val.with(expression: expanded)
|
|
355
398
|
end
|
|
356
399
|
end
|
|
357
400
|
|
|
358
401
|
def numeric_value(raw)
|
|
359
|
-
|
|
360
|
-
raw.to_f
|
|
361
|
-
else
|
|
362
|
-
raw.to_i
|
|
363
|
-
end
|
|
402
|
+
Xlsxrb::Ooxml::WorksheetParser.numeric_cell_value(raw)
|
|
364
403
|
end
|
|
365
404
|
|
|
366
405
|
def element_name(local_name, qname)
|