xlsxrb 0.1.11 → 0.1.13

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -34
  3. data/README.md +26 -21
  4. data/lib/xlsxrb/chart_builder.rb +18 -18
  5. data/lib/xlsxrb/dsl_helpers.rb +175 -0
  6. data/lib/xlsxrb/elements/cell.rb +56 -22
  7. data/lib/xlsxrb/elements/column.rb +27 -3
  8. data/lib/xlsxrb/elements/row.rb +27 -8
  9. data/lib/xlsxrb/elements/workbook.rb +27 -14
  10. data/lib/xlsxrb/elements/worksheet.rb +17 -1
  11. data/lib/xlsxrb/ooxml/cfb.rb +9 -6
  12. data/lib/xlsxrb/ooxml/crypto/agile.rb +5 -5
  13. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +784 -0
  14. data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +3478 -0
  15. data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +1796 -0
  16. data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +428 -0
  17. data/lib/xlsxrb/ooxml/reader/listeners.rb +4 -6446
  18. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +3 -5
  19. data/lib/xlsxrb/ooxml/utils.rb +5 -6
  20. data/lib/xlsxrb/ooxml/workbook_writer.rb +5 -7
  21. data/lib/xlsxrb/ooxml/worksheet_parser.rb +172 -9
  22. data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +1602 -0
  23. data/lib/xlsxrb/ooxml/writer/features_xml.rb +731 -0
  24. data/lib/xlsxrb/ooxml/writer/styles_xml.rb +348 -0
  25. data/lib/xlsxrb/ooxml/writer.rb +9 -2646
  26. data/lib/xlsxrb/ooxml/xml_builder.rb +31 -2
  27. data/lib/xlsxrb/ooxml/zip_generator.rb +26 -5
  28. data/lib/xlsxrb/ooxml/zip_reader.rb +364 -78
  29. data/lib/xlsxrb/ooxml/zip_writer.rb +32 -5
  30. data/lib/xlsxrb/stream_sheet.rb +94 -6
  31. data/lib/xlsxrb/stream_writer.rb +48 -115
  32. data/lib/xlsxrb/style_builder.rb +10 -1
  33. data/lib/xlsxrb/version.rb +1 -1
  34. data/lib/xlsxrb/workbook_builder.rb +1 -1
  35. data/lib/xlsxrb/worksheet_builder.rb +19 -81
  36. data/lib/xlsxrb.rb +449 -171
  37. data/sig/generated/xlsxrb/chart_builder.rbs +36 -36
  38. data/sig/generated/xlsxrb/dsl_helpers.rbs +92 -0
  39. data/sig/generated/xlsxrb/elements/cell.rbs +29 -0
  40. data/sig/generated/xlsxrb/elements/column.rbs +32 -21
  41. data/sig/generated/xlsxrb/elements/row.rbs +94 -14
  42. data/sig/generated/xlsxrb/elements/workbook.rbs +102 -10
  43. data/sig/generated/xlsxrb/elements/worksheet.rbs +9 -0
  44. data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +173 -0
  45. data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +400 -0
  46. data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +442 -0
  47. data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +80 -0
  48. data/sig/generated/xlsxrb/ooxml/reader/listeners.rbs +0 -1072
  49. data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +0 -2
  50. data/sig/generated/xlsxrb/ooxml/utils.rbs +2 -0
  51. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +5 -4
  52. data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +79 -0
  53. data/sig/generated/xlsxrb/ooxml/writer/features_xml.rbs +83 -0
  54. data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +53 -0
  55. data/sig/generated/xlsxrb/ooxml/writer.rbs +6 -185
  56. data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +18 -0
  57. data/sig/generated/xlsxrb/ooxml/zip_generator.rbs +15 -0
  58. data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +96 -8
  59. data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +5 -1
  60. data/sig/generated/xlsxrb/stream_sheet.rbs +43 -3
  61. data/sig/generated/xlsxrb/style_builder.rbs +7 -0
  62. data/sig/generated/xlsxrb.rbs +18 -11
  63. metadata +22 -144
  64. data/.devcontainer/Dockerfile +0 -65
  65. data/.devcontainer/devcontainer.json +0 -17
  66. data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +0 -9
  67. data/.gem_rbs_collection/ast/2.4/ast.rbs +0 -73
  68. data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +0 -9
  69. data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +0 -4
  70. data/.gem_rbs_collection/concurrent-ruby/1.1/atomic_reference.rbs +0 -16
  71. data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +0 -96
  72. data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +0 -4
  73. data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +0 -68
  74. data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +0 -249
  75. data/.gem_rbs_collection/concurrent-ruby/1.1/set.rbs +0 -4
  76. data/.gem_rbs_collection/concurrent-ruby/1.1/timer_task.rbs +0 -47
  77. data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +0 -5
  78. data/.gem_rbs_collection/csv/3.3/.rbs_meta.yaml +0 -9
  79. data/.gem_rbs_collection/csv/3.3/csv.rbs +0 -3871
  80. data/.gem_rbs_collection/csv/3.3/manifest.yaml +0 -3
  81. data/.gem_rbs_collection/lint_roller/1.1/.rbs_meta.yaml +0 -9
  82. data/.gem_rbs_collection/lint_roller/1.1/lint_roller.rbs +0 -48
  83. data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +0 -9
  84. data/.gem_rbs_collection/listen/3.9/listen.rbs +0 -25
  85. data/.gem_rbs_collection/listen/3.9/listener.rbs +0 -24
  86. data/.gem_rbs_collection/logger/1.7/.rbs_meta.yaml +0 -9
  87. data/.gem_rbs_collection/logger/1.7/formatter.rbs +0 -45
  88. data/.gem_rbs_collection/logger/1.7/log_device.rbs +0 -100
  89. data/.gem_rbs_collection/logger/1.7/logger.rbs +0 -796
  90. data/.gem_rbs_collection/logger/1.7/manifest.yaml +0 -2
  91. data/.gem_rbs_collection/logger/1.7/period.rbs +0 -17
  92. data/.gem_rbs_collection/logger/1.7/severity.rbs +0 -34
  93. data/.gem_rbs_collection/nokogiri/1.11/.rbs_meta.yaml +0 -9
  94. data/.gem_rbs_collection/nokogiri/1.11/nokogiri.rbs +0 -2332
  95. data/.gem_rbs_collection/nokogiri/1.11/patch.rbs +0 -4
  96. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +0 -9
  97. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +0 -86
  98. data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +0 -9
  99. data/.gem_rbs_collection/parser/3.2/manifest.yaml +0 -7
  100. data/.gem_rbs_collection/parser/3.2/parser.rbs +0 -194
  101. data/.gem_rbs_collection/parser/3.2/polyfill.rbs +0 -4
  102. data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +0 -9
  103. data/.gem_rbs_collection/rainbow/3.0/global.rbs +0 -7
  104. data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +0 -209
  105. data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +0 -5
  106. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +0 -9
  107. data/.gem_rbs_collection/rake/13.0/manifest.yaml +0 -2
  108. data/.gem_rbs_collection/rake/13.0/rake.rbs +0 -39
  109. data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +0 -9
  110. data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +0 -17
  111. data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +0 -9
  112. data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +0 -208
  113. data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +0 -9
  114. data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +0 -903
  115. data/.gem_rbs_collection/rubyzip/3.2/.rbs_meta.yaml +0 -9
  116. data/.gem_rbs_collection/rubyzip/3.2/manifest.yaml +0 -8
  117. data/.gem_rbs_collection/rubyzip/3.2/zip/central_directory.rbs +0 -42
  118. data/.gem_rbs_collection/rubyzip/3.2/zip/compressor.rbs +0 -5
  119. data/.gem_rbs_collection/rubyzip/3.2/zip/constants.rbs +0 -47
  120. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/aes_encryption.rbs +0 -30
  121. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/decrypted_io.rbs +0 -9
  122. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/encryption.rbs +0 -7
  123. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/null_encryption.rbs +0 -19
  124. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/traditional_encryption.rbs +0 -31
  125. data/.gem_rbs_collection/rubyzip/3.2/zip/decompressor.rbs +0 -18
  126. data/.gem_rbs_collection/rubyzip/3.2/zip/deflater.rbs +0 -12
  127. data/.gem_rbs_collection/rubyzip/3.2/zip/dirtyable.rbs +0 -11
  128. data/.gem_rbs_collection/rubyzip/3.2/zip/dos_time.rbs +0 -13
  129. data/.gem_rbs_collection/rubyzip/3.2/zip/entry.rbs +0 -95
  130. data/.gem_rbs_collection/rubyzip/3.2/zip/entry_set.rbs +0 -31
  131. data/.gem_rbs_collection/rubyzip/3.2/zip/errors.rbs +0 -58
  132. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/aes.rbs +0 -24
  133. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/generic.rbs +0 -17
  134. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/ntfs.rbs +0 -23
  135. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/old_unix.rbs +0 -22
  136. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/universal_time.rbs +0 -30
  137. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unix.rbs +0 -20
  138. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unknown.rbs +0 -15
  139. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/zip64.rbs +0 -26
  140. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field.rbs +0 -21
  141. data/.gem_rbs_collection/rubyzip/3.2/zip/file.rbs +0 -131
  142. data/.gem_rbs_collection/rubyzip/3.2/zip/file_split.rbs +0 -14
  143. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/dir.rbs +0 -33
  144. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/directory_iterator.rbs +0 -21
  145. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file.rbs +0 -63
  146. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file_stat.rbs +0 -55
  147. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/zip_file_name_mapper.rbs +0 -35
  148. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem.rbs +0 -7
  149. data/.gem_rbs_collection/rubyzip/3.2/zip/inflater.rbs +0 -10
  150. data/.gem_rbs_collection/rubyzip/3.2/zip/input_stream.rbs +0 -22
  151. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_input_stream.rbs +0 -29
  152. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_output_stream.rbs +0 -17
  153. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras.rbs +0 -13
  154. data/.gem_rbs_collection/rubyzip/3.2/zip/null_compressor.rbs +0 -10
  155. data/.gem_rbs_collection/rubyzip/3.2/zip/null_decompressor.rbs +0 -8
  156. data/.gem_rbs_collection/rubyzip/3.2/zip/null_input_stream.rbs +0 -6
  157. data/.gem_rbs_collection/rubyzip/3.2/zip/output_stream.rbs +0 -30
  158. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_compressor.rbs +0 -10
  159. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_decompressor.rbs +0 -10
  160. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_directory.rbs +0 -5
  161. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_stream.rbs +0 -15
  162. data/.gem_rbs_collection/rubyzip/3.2/zip/version.rbs +0 -3
  163. data/.gem_rbs_collection/rubyzip/3.2/zip.rbs +0 -40
  164. data/Rakefile +0 -567
  165. data/Steepfile +0 -18
  166. data/benchmark.rb +0 -406
  167. data/docs/ARCHITECTURE.md +0 -514
  168. data/docs/DEVELOPMENT.md +0 -133
  169. data/docs/PEER_LIBRARIES.md +0 -121
  170. data/docs/QUALITY_ASSURANCE.md +0 -25
  171. data/docs/SPEC_SOURCES.md +0 -48
  172. data/docs/assets/benchmark_results.svg +0 -144
  173. data/docs/assets/lsp_autocompletion.png +0 -0
  174. data/docs/assets/playground_preview.png +0 -0
  175. data/docs/coi-serviceworker.js +0 -82
  176. data/docs/office_thread.js +0 -77
  177. data/docs/preview.html +0 -719
  178. data/docs/visual/VisualGallery.md +0 -4672
  179. data/docs/wasm/ruby.wasm +0 -0
  180. data/docs/wasm/wasm_doc_helper.css +0 -351
  181. data/docs/wasm/wasm_doc_helper.js +0 -439
  182. data/docs/zeta.js +0 -1107
  183. data/rbs_collection.lock.yaml +0 -252
  184. data/rbs_collection.yaml +0 -19
  185. data/vendor/sdk_runner/Program.cs +0 -93
  186. data/vendor/sdk_runner/sdk_runner.csproj +0 -13
@@ -0,0 +1,428 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ require "rexml/parsers/sax2parser"
6
+ require "rexml/sax2listener"
7
+
8
+ module Xlsxrb
9
+ module Ooxml
10
+ class Reader
11
+ # SAX2 listener for parsing styles.xml (numFmts + cellXfs).
12
+ class StylesListener
13
+ include REXML::SAX2Listener
14
+
15
+ attr_reader :num_fmts, :cell_xfs, :cell_style_xfs, :cell_styles, :fonts, :fills, :borders, :dxfs,
16
+ :indexed_colors, :mru_colors, :table_styles
17
+
18
+ def initialize
19
+ @num_fmts = {} # { numFmtId => formatCode }
20
+ @cell_xfs = [] # Array of { num_fmt_id:, font_id:, fill_id:, border_id: }
21
+ @cell_style_xfs = [] # Array of { num_fmt_id:, font_id:, fill_id:, border_id: }
22
+ @cell_styles = [] # Array of { name:, xf_id:, builtin_id: }
23
+ @fonts = []
24
+ @fills = []
25
+ @borders = []
26
+ @dxfs = []
27
+ @indexed_colors = []
28
+ @mru_colors = []
29
+ @table_styles = {}
30
+ @inside_cell_xfs = false
31
+ @inside_cell_style_xfs = false
32
+ @inside_cell_styles = false
33
+ @inside_fonts = false
34
+ @inside_fills = false
35
+ @inside_borders = false
36
+ @inside_dxfs = false
37
+ @inside_indexed_colors = false
38
+ @inside_mru_colors = false
39
+ @inside_table_styles = false
40
+ @current_table_style = nil
41
+ @current_font = nil
42
+ @current_fill = nil
43
+ @current_border = nil
44
+ @current_border_side = nil
45
+ @current_dxf = nil
46
+ @current_xf = nil
47
+ @current_gradient_stop = nil
48
+ end
49
+
50
+ def start_element(_uri, local_name, qname, attributes)
51
+ name = element_name(local_name, qname)
52
+
53
+ case name
54
+ when "numFmt"
55
+ id = attributes["numFmtId"]&.to_i
56
+ code = attributes["formatCode"]
57
+ if @inside_dxfs && @current_dxf
58
+ @current_dxf[:num_fmt] = { num_fmt_id: id, format_code: code } if id && code
59
+ elsif id && code
60
+ @num_fmts[id] = code
61
+ end
62
+ when "cellXfs"
63
+ @inside_cell_xfs = true
64
+ when "cellStyleXfs"
65
+ @inside_cell_style_xfs = true
66
+ when "cellStyles"
67
+ @inside_cell_styles = true
68
+ when "cellStyle"
69
+ if @inside_cell_styles
70
+ entry = { name: attributes["name"] }
71
+ entry[:xf_id] = attributes["xfId"].to_i if attributes["xfId"]
72
+ entry[:builtin_id] = attributes["builtinId"].to_i if attributes["builtinId"]
73
+ il = attributes["iLevel"]
74
+ entry[:i_level] = il.to_i if il
75
+ entry[:hidden] = true if %w[1 true].include?(attributes["hidden"])
76
+ entry[:custom_builtin] = true if %w[1 true].include?(attributes["customBuiltin"])
77
+ @cell_styles << entry
78
+ end
79
+ when "xf"
80
+ xf_entry = {
81
+ num_fmt_id: attributes["numFmtId"]&.to_i,
82
+ font_id: attributes["fontId"]&.to_i,
83
+ fill_id: attributes["fillId"]&.to_i,
84
+ border_id: attributes["borderId"]&.to_i
85
+ }
86
+ %w[applyNumberFormat applyFont applyFill applyBorder applyAlignment applyProtection].each do |attr|
87
+ key = attr.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }.to_sym
88
+ xf_entry[key] = attributes[attr] == "1" unless attributes[attr].nil?
89
+ end
90
+ if @inside_cell_xfs
91
+ xf_entry[:xf_id] = attributes["xfId"]&.to_i
92
+ xf_entry[:quote_prefix] = true if attributes["quotePrefix"] == "1"
93
+ xf_entry[:pivot_button] = true if attributes["pivotButton"] == "1"
94
+ @cell_xfs << xf_entry
95
+ @current_xf = xf_entry
96
+ elsif @inside_cell_style_xfs
97
+ @cell_style_xfs << xf_entry
98
+ @current_xf = xf_entry
99
+ else
100
+ @current_xf = nil
101
+ end
102
+ when "alignment"
103
+ alignment = {}
104
+ alignment[:horizontal] = attributes["horizontal"] if attributes["horizontal"]
105
+ alignment[:vertical] = attributes["vertical"] if attributes["vertical"]
106
+ alignment[:wrap_text] = true if attributes["wrapText"] == "1"
107
+ alignment[:text_rotation] = attributes["textRotation"].to_i if attributes["textRotation"]
108
+ alignment[:indent] = attributes["indent"].to_i if attributes["indent"]
109
+ ri = attributes["relativeIndent"]
110
+ alignment[:relative_indent] = ri.to_i if ri
111
+ alignment[:shrink_to_fit] = true if attributes["shrinkToFit"] == "1"
112
+ ro = attributes["readingOrder"]
113
+ alignment[:reading_order] = ro.to_i if ro
114
+ alignment[:justify_last_line] = true if attributes["justifyLastLine"] == "1"
115
+ unless alignment.empty?
116
+ if @inside_dxfs && @current_dxf
117
+ @current_dxf[:alignment] = alignment
118
+ elsif @current_xf
119
+ @current_xf[:alignment] = alignment
120
+ end
121
+ end
122
+ when "protection"
123
+ protection = {}
124
+ protection[:locked] = attributes["locked"] != "0" if attributes.key?("locked")
125
+ protection[:hidden] = attributes["hidden"] == "1" if attributes.key?("hidden")
126
+ unless protection.empty?
127
+ if @inside_dxfs && @current_dxf
128
+ @current_dxf[:protection] = protection
129
+ elsif @current_xf
130
+ @current_xf[:protection] = protection
131
+ end
132
+ end
133
+ when "fonts"
134
+ @inside_fonts = true
135
+ when "font"
136
+ @current_font = {} if @inside_fonts || @inside_dxfs
137
+ when "b"
138
+ @current_font[:bold] = true if @current_font
139
+ when "i"
140
+ @current_font[:italic] = true if @current_font
141
+ when "strike"
142
+ @current_font[:strike] = true if @current_font
143
+ when "shadow"
144
+ @current_font[:shadow] = true if @current_font
145
+ when "outline"
146
+ @current_font[:outline] = true if @current_font
147
+ when "condense"
148
+ @current_font[:condense] = true if @current_font
149
+ when "extend"
150
+ @current_font[:extend] = true if @current_font
151
+ when "u"
152
+ if @current_font
153
+ val = attributes["val"]
154
+ @current_font[:underline] = val || true
155
+ end
156
+ when "vertAlign"
157
+ @current_font[:vert_align] = attributes["val"] if @current_font && attributes["val"]
158
+ when "scheme"
159
+ @current_font[:scheme] = attributes["val"] if @current_font && attributes["val"]
160
+ when "family"
161
+ @current_font[:family] = attributes["val"].to_i if @current_font && attributes["val"]
162
+ when "charset"
163
+ @current_font[:charset] = attributes["val"].to_i if @current_font && attributes["val"]
164
+ when "sz"
165
+ @current_font[:sz] = attributes["val"]&.to_f if @current_font
166
+ when "color"
167
+ parse_color(attributes)
168
+ when "fills"
169
+ @inside_fills = true
170
+ when "fill"
171
+ @current_fill = {} if @inside_fills || @inside_dxfs
172
+ when "patternFill"
173
+ @current_fill[:pattern] = attributes["patternType"] if @current_fill
174
+ when "gradientFill"
175
+ if @current_fill
176
+ gradient = {}
177
+ gradient[:type] = attributes["type"] if attributes["type"]
178
+ gradient[:degree] = attributes["degree"].to_f if attributes["degree"]
179
+ gradient[:left] = attributes["left"].to_f if attributes["left"]
180
+ gradient[:right] = attributes["right"].to_f if attributes["right"]
181
+ gradient[:top] = attributes["top"].to_f if attributes["top"]
182
+ gradient[:bottom] = attributes["bottom"].to_f if attributes["bottom"]
183
+ gradient[:stops] = []
184
+ @current_fill[:gradient] = gradient
185
+ end
186
+ when "stop"
187
+ @current_gradient_stop = { position: attributes["position"].to_f } if @current_fill&.dig(:gradient)
188
+ when "fgColor"
189
+ parse_fill_color(:fg_color, attributes) if @current_fill
190
+ when "bgColor"
191
+ parse_fill_color(:bg_color, attributes) if @current_fill
192
+ when "borders"
193
+ @inside_borders = true
194
+ when "border"
195
+ if @inside_borders || @inside_dxfs
196
+ @current_border = {}
197
+ @current_border[:diagonal_up] = true if attributes["diagonalUp"] == "1"
198
+ @current_border[:diagonal_down] = true if attributes["diagonalDown"] == "1"
199
+ ol = attributes["outline"]
200
+ @current_border[:outline] = %w[1 true].include?(ol) unless ol.nil?
201
+ end
202
+ when "left", "right", "top", "bottom", "diagonal", "vertical", "horizontal", "start", "end"
203
+ if @current_border
204
+ style = attributes["style"]
205
+ @current_border_side = name.to_sym
206
+ @current_border[@current_border_side] = { style: style } if style
207
+ end
208
+ when "dxfs"
209
+ @inside_dxfs = true
210
+ when "dxf"
211
+ @current_dxf = {}
212
+ when "indexedColors"
213
+ @inside_indexed_colors = true
214
+ when "mruColors"
215
+ @inside_mru_colors = true
216
+ when "rgbColor"
217
+ @indexed_colors << attributes["rgb"] if @inside_indexed_colors && attributes["rgb"]
218
+ when "tableStyles"
219
+ @inside_table_styles = true
220
+ @table_styles[:default_table_style] = attributes["defaultTableStyle"] if attributes["defaultTableStyle"]
221
+ @table_styles[:default_pivot_style] = attributes["defaultPivotStyle"] if attributes["defaultPivotStyle"]
222
+ @table_styles[:styles] = []
223
+ when "tableStyle"
224
+ if @inside_table_styles
225
+ ts = { name: attributes["name"], elements: [] }
226
+ ts[:pivot] = %w[1 true].include?(attributes["pivot"]) if attributes.key?("pivot")
227
+ ts[:table] = %w[1 true].include?(attributes["table"]) if attributes.key?("table")
228
+ @current_table_style = ts
229
+ end
230
+ when "tableStyleElement"
231
+ if @current_table_style
232
+ el = { type: attributes["type"] }
233
+ el[:size] = attributes["size"].to_i if attributes["size"]
234
+ el[:dxf_id] = attributes["dxfId"].to_i if attributes["dxfId"]
235
+ @current_table_style[:elements] << el
236
+ end
237
+ end
238
+
239
+ parse_font_name(name, attributes)
240
+ end
241
+
242
+ def end_element(_uri, local_name, qname)
243
+ name = element_name(local_name, qname)
244
+ case name
245
+ when "cellXfs"
246
+ @inside_cell_xfs = false
247
+ when "cellStyleXfs"
248
+ @inside_cell_style_xfs = false
249
+ when "xf"
250
+ @current_xf = nil
251
+ when "cellStyles"
252
+ @inside_cell_styles = false
253
+ when "fonts"
254
+ @inside_fonts = false
255
+ when "font"
256
+ if @inside_dxfs && @current_dxf
257
+ @current_dxf[:font] = @current_font
258
+ elsif @inside_fonts
259
+ @fonts << @current_font
260
+ end
261
+ @current_font = nil
262
+ when "fills"
263
+ @inside_fills = false
264
+ when "fill"
265
+ if @inside_dxfs && @current_dxf
266
+ @current_dxf[:fill] = @current_fill
267
+ elsif @inside_fills
268
+ @fills << @current_fill
269
+ end
270
+ @current_fill = nil
271
+ when "stop"
272
+ @current_fill[:gradient][:stops] << @current_gradient_stop if @current_gradient_stop && @current_fill&.dig(:gradient)
273
+ @current_gradient_stop = nil
274
+ when "borders"
275
+ @inside_borders = false
276
+ when "border"
277
+ if @inside_dxfs && @current_dxf
278
+ @current_dxf[:border] = @current_border
279
+ elsif @inside_borders
280
+ @borders << @current_border
281
+ end
282
+ @current_border = nil
283
+ when "left", "right", "top", "bottom", "diagonal", "vertical", "horizontal", "start", "end"
284
+ @current_border_side = nil
285
+ when "dxfs"
286
+ @inside_dxfs = false
287
+ when "dxf"
288
+ @dxfs << @current_dxf if @current_dxf
289
+ @current_dxf = nil
290
+ when "indexedColors"
291
+ @inside_indexed_colors = false
292
+ when "mruColors"
293
+ @inside_mru_colors = false
294
+ when "tableStyles"
295
+ @inside_table_styles = false
296
+ when "tableStyle"
297
+ if @inside_table_styles && @current_table_style
298
+ @table_styles[:styles] << @current_table_style
299
+ @current_table_style = nil
300
+ end
301
+ end
302
+ end
303
+
304
+ private
305
+
306
+ def parse_color(attributes)
307
+ if @inside_mru_colors
308
+ c = {}
309
+ c[:auto] = true if %w[1 true].include?(attributes["auto"])
310
+ c[:rgb] = attributes["rgb"] if attributes["rgb"]
311
+ c[:theme] = attributes["theme"].to_i if attributes["theme"]
312
+ c[:tint] = attributes["tint"].to_f if attributes["tint"]
313
+ c[:indexed] = attributes["indexed"].to_i if attributes["indexed"]
314
+ @mru_colors << c unless c.empty?
315
+ elsif @current_gradient_stop
316
+ @current_gradient_stop[:auto] = true if %w[1 true].include?(attributes["auto"])
317
+ @current_gradient_stop[:color] = attributes["rgb"] if attributes["rgb"]
318
+ @current_gradient_stop[:theme] = attributes["theme"].to_i if attributes["theme"]
319
+ @current_gradient_stop[:tint] = attributes["tint"].to_f if attributes["tint"]
320
+ @current_gradient_stop[:indexed] = attributes["indexed"].to_i if attributes["indexed"]
321
+ elsif @current_border_side && @current_border
322
+ side_data = @current_border[@current_border_side]
323
+ if side_data.is_a?(Hash)
324
+ side_data[:auto] = true if %w[1 true].include?(attributes["auto"])
325
+ side_data[:color] = attributes["rgb"] if attributes["rgb"]
326
+ side_data[:theme] = attributes["theme"].to_i if attributes["theme"]
327
+ side_data[:tint] = attributes["tint"].to_f if attributes["tint"]
328
+ side_data[:indexed] = attributes["indexed"].to_i if attributes["indexed"]
329
+ end
330
+ elsif @current_font
331
+ @current_font[:auto] = true if %w[1 true].include?(attributes["auto"])
332
+ @current_font[:color] = attributes["rgb"] if attributes["rgb"]
333
+ @current_font[:theme] = attributes["theme"].to_i if attributes["theme"]
334
+ @current_font[:tint] = attributes["tint"].to_f if attributes["tint"]
335
+ @current_font[:indexed] = attributes["indexed"].to_i if attributes["indexed"]
336
+ end
337
+ end
338
+
339
+ def parse_fill_color(key, attributes)
340
+ if %w[1 true].include?(attributes["auto"])
341
+ @current_fill[:"#{key}_auto"] = true
342
+ elsif attributes["rgb"]
343
+ @current_fill[key] = attributes["rgb"]
344
+ elsif attributes["theme"]
345
+ @current_fill[:"#{key}_theme"] = attributes["theme"].to_i
346
+ @current_fill[:"#{key}_tint"] = attributes["tint"].to_f if attributes["tint"]
347
+ elsif attributes["indexed"]
348
+ @current_fill[:"#{key}_indexed"] = attributes["indexed"].to_i
349
+ end
350
+ end
351
+
352
+ def parse_font_name(name, attributes)
353
+ @current_font[:name] = attributes["val"] if name == "name" && @current_font && attributes["val"]
354
+ end
355
+
356
+ def element_name(local_name, qname)
357
+ if local_name.nil? || local_name.empty?
358
+ qname.to_s.split(":").last
359
+ else
360
+ local_name
361
+ end
362
+ end
363
+ end
364
+
365
+ # SAX2 listener that captures cell style index (s attribute) from worksheet.
366
+ class CellStyleListener
367
+ include REXML::SAX2Listener
368
+
369
+ attr_reader :cell_style_indices
370
+
371
+ def initialize
372
+ @cell_style_indices = {}
373
+ end
374
+
375
+ def start_element(_uri, local_name, qname, attributes)
376
+ name = element_name(local_name, qname)
377
+ return unless name == "c"
378
+
379
+ ref = attributes["r"]
380
+ s = attributes["s"]
381
+ @cell_style_indices[ref] = s.to_i if ref && s
382
+ end
383
+
384
+ private
385
+
386
+ def element_name(local_name, qname)
387
+ if local_name.nil? || local_name.empty?
388
+ qname.to_s.split(":").last
389
+ else
390
+ local_name
391
+ end
392
+ end
393
+ end
394
+
395
+ # SAX2 listener for parsing <phoneticPr> element.
396
+ class PhoneticPrListener
397
+ include REXML::SAX2Listener
398
+
399
+ attr_reader :phonetic_pr
400
+
401
+ def initialize
402
+ @phonetic_pr = nil
403
+ end
404
+
405
+ def start_element(_uri, local_name, qname, attributes)
406
+ name = element_name(local_name, qname)
407
+ return unless name == "phoneticPr"
408
+
409
+ pr = {}
410
+ pr[:font_id] = attributes["fontId"].to_i if attributes["fontId"]
411
+ pr[:type] = attributes["type"] if attributes["type"]
412
+ pr[:alignment] = attributes["alignment"] if attributes["alignment"]
413
+ @phonetic_pr = pr
414
+ end
415
+
416
+ private
417
+
418
+ def element_name(local_name, qname)
419
+ if local_name.nil? || local_name.empty?
420
+ qname.to_s.split(":").last
421
+ else
422
+ local_name
423
+ end
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end