xlsxrb 0.1.16 → 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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -0
  3. data/README.md +2 -2
  4. data/lib/xlsxrb/chart_builder.rb +41 -2
  5. data/lib/xlsxrb/dsl_helpers.rb +522 -6
  6. data/lib/xlsxrb/elements/cell.rb +34 -9
  7. data/lib/xlsxrb/elements/column.rb +2 -2
  8. data/lib/xlsxrb/elements/types.rb +1 -0
  9. data/lib/xlsxrb/elements/workbook.rb +48 -0
  10. data/lib/xlsxrb/elements/worksheet.rb +158 -11
  11. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +22 -1
  12. data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +187 -24
  13. data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +108 -1
  14. data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +16 -11
  15. data/lib/xlsxrb/ooxml/reader.rb +88 -0
  16. data/lib/xlsxrb/ooxml/styles_parser.rb +19 -11
  17. data/lib/xlsxrb/ooxml/workbook_parser.rb +18 -3
  18. data/lib/xlsxrb/ooxml/workbook_writer.rb +266 -46
  19. data/lib/xlsxrb/ooxml/worksheet_parser.rb +18 -4
  20. data/lib/xlsxrb/ooxml/worksheet_writer.rb +289 -88
  21. data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +579 -441
  22. data/lib/xlsxrb/ooxml/writer/features_xml.rb +26 -9
  23. data/lib/xlsxrb/ooxml/writer/styles_xml.rb +23 -9
  24. data/lib/xlsxrb/ooxml/writer.rb +268 -31
  25. data/lib/xlsxrb/ooxml/zip_writer.rb +15 -5
  26. data/lib/xlsxrb/stream_sheet.rb +157 -3
  27. data/lib/xlsxrb/stream_writer.rb +946 -74
  28. data/lib/xlsxrb/style_builder.rb +59 -6
  29. data/lib/xlsxrb/utils.rb +65 -0
  30. data/lib/xlsxrb/version.rb +1 -1
  31. data/lib/xlsxrb/workbook_builder.rb +197 -14
  32. data/lib/xlsxrb/worksheet_builder.rb +1137 -44
  33. data/lib/xlsxrb.rb +302 -47
  34. data/sig/generated/xlsxrb/chart_builder.rbs +31 -3
  35. data/sig/generated/xlsxrb/dsl_helpers.rbs +119 -4
  36. data/sig/generated/xlsxrb/elements/cell.rbs +20 -3
  37. data/sig/generated/xlsxrb/elements/column.rbs +3 -3
  38. data/sig/generated/xlsxrb/elements/types.rbs +2 -0
  39. data/sig/generated/xlsxrb/elements/workbook.rbs +37 -0
  40. data/sig/generated/xlsxrb/elements/worksheet.rbs +82 -2
  41. data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +2 -0
  42. data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +74 -2
  43. data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +36 -0
  44. data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +3 -0
  45. data/sig/generated/xlsxrb/ooxml/reader.rbs +17 -0
  46. data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +3 -0
  47. data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +8 -0
  48. data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +8 -4
  49. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +1 -1
  50. data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +12 -1
  51. data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +16 -0
  52. data/sig/generated/xlsxrb/ooxml/writer.rbs +42 -8
  53. data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +8 -2
  54. data/sig/generated/xlsxrb/stream_sheet.rbs +101 -1
  55. data/sig/generated/xlsxrb/stream_writer.rbs +592 -23
  56. data/sig/generated/xlsxrb/style_builder.rbs +24 -6
  57. data/sig/generated/xlsxrb/utils.rbs +20 -0
  58. data/sig/generated/xlsxrb/workbook_builder.rbs +87 -5
  59. data/sig/generated/xlsxrb/worksheet_builder.rbs +554 -10
  60. data/sig/generated/xlsxrb.rbs +77 -16
  61. metadata +1 -1
@@ -323,6 +323,18 @@ module Xlsxrb
323
323
  parse_worksheet_properties(worksheet_xml)
324
324
  end
325
325
 
326
+ # Returns outline and grouping properties (<outlinePr>) for the given sheet.
327
+ def outline_properties(sheet: nil)
328
+ props = sheet_properties(sheet: sheet)
329
+ props[:outline_properties] || {
330
+ apply_styles: props[:apply_styles],
331
+ summary_below: props[:summary_below],
332
+ summary_right: props[:summary_right],
333
+ show_outline_symbols: props[:show_outline_symbols]
334
+ }.compact
335
+ end
336
+ alias outline_pr outline_properties
337
+
326
338
  # Returns phonetic properties for the given sheet, or nil if not present.
327
339
  def phonetic_properties(sheet: nil)
328
340
  worksheet_xml = load_worksheet_xml(sheet)
@@ -709,6 +721,7 @@ module Xlsxrb
709
721
  cp.listen(cl)
710
722
  cp.parse
711
723
  chart[:chart_type] = cl.chart_type
724
+ chart[:type] = cl.chart_type
712
725
  chart[:title] = cl.title
713
726
  chart[:title_overlay] = cl.title_overlay unless cl.title_overlay.nil?
714
727
  chart[:title_font] = cl.title_font if cl.title_font
@@ -855,6 +868,30 @@ module Xlsxrb
855
868
  chart[:protection] = cl.protection if cl.protection
856
869
  chart[:print_settings] = cl.print_settings if cl.print_settings
857
870
  chart[:chart_font] = cl.chart_font if cl.chart_font
871
+ chart[:sec_val_axis_title] = cl.sec_val_axis_title if cl.sec_val_axis_title
872
+ chart[:sec_val_axis_major_gridlines] = cl.sec_val_axis_major_gridlines if cl.sec_val_axis_major_gridlines
873
+ chart[:sec_val_axis_minor_gridlines] = cl.sec_val_axis_minor_gridlines if cl.sec_val_axis_minor_gridlines
874
+ chart[:sec_val_axis_num_fmt] = cl.sec_val_axis_num_fmt if cl.sec_val_axis_num_fmt
875
+ chart[:sec_val_axis_pos] = cl.sec_val_axis_pos if cl.sec_val_axis_pos
876
+ chart[:sec_val_axis_scaling_max] = cl.sec_val_axis_scaling_max if cl.sec_val_axis_scaling_max
877
+ chart[:sec_val_axis_scaling_min] = cl.sec_val_axis_scaling_min if cl.sec_val_axis_scaling_min
878
+ chart[:sec_val_axis_major_unit] = cl.sec_val_axis_major_unit if cl.sec_val_axis_major_unit
879
+ chart[:sec_val_axis_minor_unit] = cl.sec_val_axis_minor_unit if cl.sec_val_axis_minor_unit
880
+ next unless cl.sec_val_axis_title || cl.sec_val_axis_major_gridlines || cl.sec_val_axis_scaling_max ||
881
+ cl.sec_val_axis_scaling_min || cl.sec_val_axis_num_fmt || cl.sec_val_axis_pos
882
+
883
+ sva = {
884
+ title: cl.sec_val_axis_title,
885
+ major_gridlines: cl.sec_val_axis_major_gridlines,
886
+ minor_gridlines: cl.sec_val_axis_minor_gridlines,
887
+ num_fmt: cl.sec_val_axis_num_fmt,
888
+ pos: cl.sec_val_axis_pos,
889
+ max: cl.sec_val_axis_scaling_max,
890
+ min: cl.sec_val_axis_scaling_min,
891
+ major_unit: cl.sec_val_axis_major_unit,
892
+ minor_unit: cl.sec_val_axis_minor_unit
893
+ }.compact
894
+ chart[:secondary_value_axis] = sva unless sva.empty?
858
895
  end
859
896
  listener.charts
860
897
  end
@@ -889,6 +926,57 @@ module Xlsxrb
889
926
  listener.comments
890
927
  end
891
928
 
929
+ # Returns all persons registered in xl/persons/person.xml.
930
+ # Each hash: { id:, display_name:, user_id:, provider_id: }
931
+ # : () -> Array[Hash[Symbol, untyped]]
932
+ def persons
933
+ @persons ||= begin
934
+ xml = extract_zip_entry("xl/persons/person.xml")
935
+ if xml.nil? || xml.empty?
936
+ wb_rels_xml = extract_zip_entry("xl/_rels/workbook.xml.rels")
937
+ if wb_rels_xml && !wb_rels_xml.empty?
938
+ rels = parse_rels_with_types(wb_rels_xml)
939
+ p_rel = rels.find { |r| r[:type]&.end_with?("person") }
940
+ xml = extract_zip_entry(normalize_xl_path(p_rel[:target], "xl")) if p_rel
941
+ end
942
+ end
943
+ if xml && !xml.empty?
944
+ parser = REXML::Parsers::SAX2Parser.new(xml)
945
+ listener = PersonListListener.new
946
+ parser.listen(listener)
947
+ parser.parse
948
+ listener.persons
949
+ else
950
+ []
951
+ end
952
+ end
953
+ end
954
+ alias person_list persons
955
+
956
+ # Returns threaded comments for the given sheet as an array of hashes.
957
+ # Each hash: { ref:, text:, author:, person_id:, id:, parent_id:, dt:, done: }
958
+ # : (?sheet: untyped?) -> Array[Hash[Symbol, untyped]]
959
+ def threaded_comments(sheet: nil)
960
+ sheet_index = resolve_sheet_index(sheet)
961
+ tc_path = find_sheet_rel_target(sheet_index, "threadedComment")
962
+ return [] unless tc_path
963
+
964
+ xml = extract_zip_entry(tc_path)
965
+ return [] if xml.nil? || xml.empty?
966
+
967
+ parser = REXML::Parsers::SAX2Parser.new(xml)
968
+ listener = ThreadedCommentsListener.new
969
+ parser.listen(listener)
970
+ parser.parse
971
+
972
+ persons_map = persons.to_h { |p| [p[:id], p[:display_name]] }
973
+ listener.threaded_comments.map do |tc|
974
+ tc_author = persons_map[tc[:person_id]]
975
+ tc[:author] = tc_author if tc_author
976
+ tc
977
+ end
978
+ end
979
+
892
980
  # Returns pivot tables for the given sheet as an array of hashes.
893
981
  # Each hash: { name:, ref:, cache_id:, fields:, row_fields:, col_fields:, data_fields:, cache: }
894
982
  def pivot_tables(sheet: nil)
@@ -80,6 +80,8 @@ module Xlsxrb
80
80
  @current_font[:extend] = truthy_val?(attrs) if @current_font
81
81
  when "u"
82
82
  @current_font[:underline] = attrs["val"] || "single" if @current_font
83
+ when "vertAlign"
84
+ @current_font[:vert_align] = attrs["val"] if @current_font && attrs["val"]
83
85
  when "sz"
84
86
  @current_font[:sz] = attrs["val"]&.to_f if @current_font
85
87
  when "color"
@@ -95,8 +97,8 @@ module Xlsxrb
95
97
  when "border"
96
98
  if parent_context?("borders") || (@current_dxf && parent_context?("dxf"))
97
99
  @current_border = {}
98
- @current_border[:diagonal_up] = true if attrs["diagonalUp"] == "1"
99
- @current_border[:diagonal_down] = true if attrs["diagonalDown"] == "1"
100
+ @current_border[:diagonal_up] = true if boolean_val(attrs["diagonalUp"])
101
+ @current_border[:diagonal_down] = true if boolean_val(attrs["diagonalDown"])
100
102
  end
101
103
  when "left", "right", "top", "bottom", "diagonal"
102
104
  handle_border_side(localname, attrs)
@@ -206,14 +208,20 @@ module Xlsxrb
206
208
  fill_id: attrs["fillId"]&.to_i,
207
209
  border_id: attrs["borderId"]&.to_i,
208
210
  xf_id: attrs["xfId"]&.to_i,
209
- apply_number_format: attrs["applyNumberFormat"] == "1",
210
- apply_font: attrs["applyFont"] == "1",
211
- apply_fill: attrs["applyFill"] == "1",
212
- apply_border: attrs["applyBorder"] == "1",
213
- apply_alignment: attrs["applyAlignment"] == "1"
211
+ apply_number_format: boolean_val(attrs["applyNumberFormat"]),
212
+ apply_font: boolean_val(attrs["applyFont"]),
213
+ apply_fill: boolean_val(attrs["applyFill"]),
214
+ apply_border: boolean_val(attrs["applyBorder"]),
215
+ apply_alignment: boolean_val(attrs["applyAlignment"]),
216
+ apply_protection: boolean_val(attrs["applyProtection"])
214
217
  }
215
218
  end
216
219
 
220
+ #: (untyped val) -> bool
221
+ def boolean_val(val)
222
+ val == "1" || (val&.casecmp?("true") || false)
223
+ end
224
+
217
225
  def truthy_val?(attrs)
218
226
  val = attrs["val"]
219
227
  val.nil? || (val != "0" && val.downcase != "false")
@@ -226,10 +234,10 @@ module Xlsxrb
226
234
  alignment = {}
227
235
  alignment[:horizontal] = attrs["horizontal"] if attrs["horizontal"]
228
236
  alignment[:vertical] = attrs["vertical"] if attrs["vertical"]
229
- alignment[:wrap_text] = true if attrs["wrapText"] == "1"
237
+ alignment[:wrap_text] = true if boolean_val(attrs["wrapText"])
230
238
  alignment[:text_rotation] = attrs["textRotation"]&.to_i if attrs["textRotation"]
231
239
  alignment[:indent] = attrs["indent"]&.to_i if attrs["indent"]
232
- alignment[:shrink_to_fit] = true if attrs["shrinkToFit"] == "1"
240
+ alignment[:shrink_to_fit] = true if boolean_val(attrs["shrinkToFit"])
233
241
  target[:alignment] = alignment unless alignment.empty?
234
242
  end
235
243
 
@@ -238,8 +246,8 @@ module Xlsxrb
238
246
  return unless target
239
247
 
240
248
  protection = {}
241
- protection[:locked] = attrs["locked"] == "1" unless attrs["locked"].nil?
242
- protection[:hidden] = attrs["hidden"] == "1" unless attrs["hidden"].nil?
249
+ protection[:locked] = !%w[0 false].include?(attrs["locked"]&.downcase) unless attrs["locked"].nil?
250
+ protection[:hidden] = boolean_val(attrs["hidden"]) unless attrs["hidden"].nil?
243
251
  target[:protection] = protection unless protection.empty?
244
252
  end
245
253
 
@@ -14,7 +14,7 @@ module Xlsxrb
14
14
  end
15
15
 
16
16
  def self.parse_with_properties(xml_string)
17
- return { sheets: [], date1904: false, defined_names: [], active_tab: nil, full_calc_on_load: nil } if xml_string.nil? || xml_string.empty?
17
+ return { sheets: [], date1904: false, defined_names: [], active_tab: nil, full_calc_on_load: nil, first_sheet: nil, show_sheet_tabs: nil, tab_ratio: nil, code_name: nil } if xml_string.nil? || xml_string.empty?
18
18
 
19
19
  listener = Listener.new
20
20
  XmlParser.parse(xml_string, listener)
@@ -23,7 +23,11 @@ module Xlsxrb
23
23
  date1904: listener.date1904,
24
24
  defined_names: listener.defined_names,
25
25
  active_tab: listener.active_tab,
26
- full_calc_on_load: listener.full_calc_on_load
26
+ full_calc_on_load: listener.full_calc_on_load,
27
+ first_sheet: listener.first_sheet,
28
+ show_sheet_tabs: listener.show_sheet_tabs,
29
+ tab_ratio: listener.tab_ratio,
30
+ code_name: listener.code_name
27
31
  }
28
32
  end
29
33
 
@@ -31,7 +35,7 @@ module Xlsxrb
31
35
  class Listener
32
36
  include REXML::SAX2Listener
33
37
 
34
- attr_reader :sheets, :date1904, :defined_names, :active_tab, :full_calc_on_load
38
+ attr_reader :sheets, :date1904, :defined_names, :active_tab, :full_calc_on_load, :first_sheet, :show_sheet_tabs, :tab_ratio, :code_name
35
39
 
36
40
  def initialize
37
41
  @sheets = []
@@ -39,6 +43,10 @@ module Xlsxrb
39
43
  @defined_names = []
40
44
  @active_tab = nil
41
45
  @full_calc_on_load = nil
46
+ @first_sheet = nil
47
+ @show_sheet_tabs = nil
48
+ @tab_ratio = nil
49
+ @code_name = nil
42
50
  @in_defined_name = false
43
51
  @current_dn = nil
44
52
  @dn_text = +""
@@ -65,9 +73,16 @@ module Xlsxrb
65
73
  when "workbookPr"
66
74
  d1904 = attrs["date1904"]
67
75
  @date1904 = %w[1 true].include?(d1904) unless d1904.nil?
76
+ @code_name = attrs["codeName"] if attrs["codeName"]
68
77
  when "workbookView"
69
78
  at = attrs["activeTab"]
70
79
  @active_tab = at.to_i if at
80
+ fs = attrs["firstSheet"]
81
+ @first_sheet = fs.to_i if fs
82
+ sst = attrs["showSheetTabs"]
83
+ @show_sheet_tabs = %w[1 true].include?(sst) unless sst.nil?
84
+ tr = attrs["tabRatio"]
85
+ @tab_ratio = tr.to_i if tr
71
86
  when "calcPr"
72
87
  fcol = attrs["fullCalcOnLoad"]
73
88
  @full_calc_on_load = %w[1 true].include?(fcol) unless fcol.nil?