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
@@ -1075,9 +1075,22 @@ module Xlsxrb
1075
1075
  class ChartTypeListener
1076
1076
  include REXML::SAX2Listener
1077
1077
 
1078
- attr_reader :chart_type, :title, :title_overlay, :title_font,
1078
+ attr_reader :title, :title_overlay, :title_font,
1079
1079
  :title_fill_color, :title_no_fill, :title_line_color, :title_line_width, :title_line_dash,
1080
1080
  :series, :legend, :data_labels, :cat_axis_title, :val_axis_title,
1081
+ :secondary_value_axis, :sec_val_axis_title, :chart_types,
1082
+ :sec_val_axis_major_gridlines, :sec_val_axis_minor_gridlines,
1083
+ :sec_val_axis_delete, :sec_val_axis_orientation, :sec_val_axis_num_fmt,
1084
+ :sec_val_axis_major_tick_mark, :sec_val_axis_minor_tick_mark, :sec_val_axis_crosses,
1085
+ :sec_val_axis_crosses_at, :sec_val_axis_cross_between, :sec_val_axis_major_unit,
1086
+ :sec_val_axis_minor_unit, :sec_val_axis_disp_units, :sec_val_axis_scaling_max,
1087
+ :sec_val_axis_scaling_min, :sec_val_axis_log_base, :sec_val_axis_pos,
1088
+ :sec_val_axis_label_rotation, :sec_val_axis_font, :sec_val_axis_fill,
1089
+ :sec_val_axis_no_fill, :sec_val_axis_line_color, :sec_val_axis_line_width,
1090
+ :sec_val_axis_line_dash, :sec_val_axis_tick_lbl_pos,
1091
+ :sec_val_axis_title_font, :sec_val_axis_title_fill, :sec_val_axis_title_no_fill,
1092
+ :sec_val_axis_title_line_color, :sec_val_axis_title_line_width, :sec_val_axis_title_line_dash,
1093
+ :sec_val_axis_title_layout, :sec_val_axis_title_rotation,
1081
1094
  :grouping, :bar_dir, :vary_colors, :plot_vis_only, :disp_blanks_as, :style, :auto_title_deleted,
1082
1095
  :rounded_corners, :cat_axis_tick_lbl_pos, :val_axis_tick_lbl_pos,
1083
1096
  :cat_axis_major_gridlines, :val_axis_major_gridlines,
@@ -1134,8 +1147,54 @@ module Xlsxrb
1134
1147
  bar3DChart line3DChart pie3DChart area3DChart surfaceChart surface3DChart stockChart bubbleChart
1135
1148
  ofPieChart].freeze
1136
1149
 
1150
+ def chart_type
1151
+ return "comboChart" if @chart_types && @chart_types.uniq.size > 1
1152
+
1153
+ @chart_types&.first || @chart_type
1154
+ end
1155
+
1137
1156
  def initialize
1138
1157
  @chart_type = nil
1158
+ @chart_types = []
1159
+ @current_chart_type = nil
1160
+ @current_chart_series = []
1161
+ @val_ax_count = 0
1162
+ @is_secondary_val_ax = false
1163
+ @secondary_value_axis = nil
1164
+ @sec_val_axis_title = nil
1165
+ @sec_val_axis_major_gridlines = false
1166
+ @sec_val_axis_minor_gridlines = false
1167
+ @sec_val_axis_delete = nil
1168
+ @sec_val_axis_orientation = nil
1169
+ @sec_val_axis_num_fmt = nil
1170
+ @sec_val_axis_major_tick_mark = nil
1171
+ @sec_val_axis_minor_tick_mark = nil
1172
+ @sec_val_axis_crosses = nil
1173
+ @sec_val_axis_crosses_at = nil
1174
+ @sec_val_axis_cross_between = nil
1175
+ @sec_val_axis_major_unit = nil
1176
+ @sec_val_axis_minor_unit = nil
1177
+ @sec_val_axis_disp_units = nil
1178
+ @sec_val_axis_scaling_max = nil
1179
+ @sec_val_axis_scaling_min = nil
1180
+ @sec_val_axis_log_base = nil
1181
+ @sec_val_axis_pos = nil
1182
+ @sec_val_axis_label_rotation = nil
1183
+ @sec_val_axis_font = nil
1184
+ @sec_val_axis_fill = nil
1185
+ @sec_val_axis_no_fill = nil
1186
+ @sec_val_axis_line_color = nil
1187
+ @sec_val_axis_line_width = nil
1188
+ @sec_val_axis_line_dash = nil
1189
+ @sec_val_axis_tick_lbl_pos = nil
1190
+ @sec_val_axis_title_font = nil
1191
+ @sec_val_axis_title_fill = nil
1192
+ @sec_val_axis_title_no_fill = nil
1193
+ @sec_val_axis_title_line_color = nil
1194
+ @sec_val_axis_title_line_width = nil
1195
+ @sec_val_axis_title_line_dash = nil
1196
+ @sec_val_axis_title_layout = nil
1197
+ @sec_val_axis_title_rotation = nil
1139
1198
  @title = nil
1140
1199
  @title_overlay = nil
1141
1200
  @title_font = nil
@@ -1441,13 +1500,25 @@ module Xlsxrb
1441
1500
 
1442
1501
  def start_element(_uri, local_name, qname, attributes)
1443
1502
  name = element_name(local_name, qname)
1444
- @chart_type = name if CHART_TYPES.include?(name)
1503
+ if CHART_TYPES.include?(name)
1504
+ @chart_type ||= name
1505
+ @chart_types << name
1506
+ @current_chart_type = name
1507
+ @current_chart_series = []
1508
+ end
1445
1509
 
1446
1510
  case name
1447
1511
  when "chart"
1448
1512
  @inside_chart = true
1449
1513
  when "plotArea"
1450
1514
  @inside_plot_area = true
1515
+ when "axId"
1516
+ val = attributes["val"]
1517
+ if @inside_val_ax
1518
+ @is_secondary_val_ax = val == "3" || @val_ax_count > 1
1519
+ elsif @current_chart_series && val == "3"
1520
+ @current_chart_series.each { |s| s[:secondary_axis] = true }
1521
+ end
1451
1522
  when "grouping"
1452
1523
  @grouping = attributes["val"] if attributes["val"]
1453
1524
  when "barDir"
@@ -2191,7 +2262,11 @@ module Xlsxrb
2191
2262
  if @inside_cat_ax
2192
2263
  @cat_axis_delete = attributes["val"] == "1"
2193
2264
  elsif @inside_val_ax
2194
- @val_axis_delete = attributes["val"] == "1"
2265
+ if @is_secondary_val_ax
2266
+ @sec_val_axis_delete = attributes["val"] == "1"
2267
+ else
2268
+ @val_axis_delete = attributes["val"] == "1"
2269
+ end
2195
2270
  end
2196
2271
  end
2197
2272
  when "overlay"
@@ -2285,6 +2360,8 @@ module Xlsxrb
2285
2360
  @cat_axis_type = :date
2286
2361
  when "valAx"
2287
2362
  @inside_val_ax = true
2363
+ @val_ax_count += 1
2364
+ @is_secondary_val_ax = (@val_ax_count > 1)
2288
2365
  when "scaling"
2289
2366
  @inside_scaling = true if @inside_cat_ax || @inside_val_ax
2290
2367
  when "logBase"
@@ -2292,7 +2369,11 @@ module Xlsxrb
2292
2369
  if @inside_cat_ax
2293
2370
  @cat_axis_log_base = attributes["val"].to_f
2294
2371
  elsif @inside_val_ax
2295
- @val_axis_log_base = attributes["val"].to_f
2372
+ if @is_secondary_val_ax
2373
+ @sec_val_axis_log_base = attributes["val"].to_f
2374
+ else
2375
+ @val_axis_log_base = attributes["val"].to_f
2376
+ end
2296
2377
  end
2297
2378
  end
2298
2379
  when "max"
@@ -2300,7 +2381,11 @@ module Xlsxrb
2300
2381
  if @inside_cat_ax
2301
2382
  @cat_axis_scaling_max = attributes["val"].to_f
2302
2383
  elsif @inside_val_ax
2303
- @val_axis_scaling_max = attributes["val"].to_f
2384
+ if @is_secondary_val_ax
2385
+ @sec_val_axis_scaling_max = attributes["val"].to_f
2386
+ else
2387
+ @val_axis_scaling_max = attributes["val"].to_f
2388
+ end
2304
2389
  end
2305
2390
  end
2306
2391
  when "min"
@@ -2308,7 +2393,11 @@ module Xlsxrb
2308
2393
  if @inside_cat_ax
2309
2394
  @cat_axis_scaling_min = attributes["val"].to_f
2310
2395
  elsif @inside_val_ax
2311
- @val_axis_scaling_min = attributes["val"].to_f
2396
+ if @is_secondary_val_ax
2397
+ @sec_val_axis_scaling_min = attributes["val"].to_f
2398
+ else
2399
+ @val_axis_scaling_min = attributes["val"].to_f
2400
+ end
2312
2401
  end
2313
2402
  end
2314
2403
  when "orientation"
@@ -2316,7 +2405,11 @@ module Xlsxrb
2316
2405
  if @inside_cat_ax
2317
2406
  @cat_axis_orientation = attributes["val"]
2318
2407
  elsif @inside_val_ax
2319
- @val_axis_orientation = attributes["val"]
2408
+ if @is_secondary_val_ax
2409
+ @sec_val_axis_orientation = attributes["val"]
2410
+ else
2411
+ @val_axis_orientation = attributes["val"]
2412
+ end
2320
2413
  end
2321
2414
  end
2322
2415
  when "numFmt"
@@ -2339,7 +2432,11 @@ module Xlsxrb
2339
2432
  if @inside_cat_ax
2340
2433
  @cat_axis_num_fmt = nf
2341
2434
  elsif @inside_val_ax
2342
- @val_axis_num_fmt = nf
2435
+ if @is_secondary_val_ax
2436
+ @sec_val_axis_num_fmt = nf
2437
+ else
2438
+ @val_axis_num_fmt = nf
2439
+ end
2343
2440
  end
2344
2441
  elsif @inside_dlbl && @current_dlbl && attributes["formatCode"]
2345
2442
  nf = { format_code: attributes["formatCode"] }
@@ -2356,7 +2453,11 @@ module Xlsxrb
2356
2453
  if @inside_cat_ax
2357
2454
  @cat_axis_major_tick_mark = attributes["val"]
2358
2455
  elsif @inside_val_ax
2359
- @val_axis_major_tick_mark = attributes["val"]
2456
+ if @is_secondary_val_ax
2457
+ @sec_val_axis_major_tick_mark = attributes["val"]
2458
+ else
2459
+ @val_axis_major_tick_mark = attributes["val"]
2460
+ end
2360
2461
  end
2361
2462
  end
2362
2463
  when "minorTickMark"
@@ -2364,7 +2465,11 @@ module Xlsxrb
2364
2465
  if @inside_cat_ax
2365
2466
  @cat_axis_minor_tick_mark = attributes["val"]
2366
2467
  elsif @inside_val_ax
2367
- @val_axis_minor_tick_mark = attributes["val"]
2468
+ if @is_secondary_val_ax
2469
+ @sec_val_axis_minor_tick_mark = attributes["val"]
2470
+ else
2471
+ @val_axis_minor_tick_mark = attributes["val"]
2472
+ end
2368
2473
  end
2369
2474
  end
2370
2475
  when "crosses"
@@ -2372,7 +2477,11 @@ module Xlsxrb
2372
2477
  if @inside_cat_ax
2373
2478
  @cat_axis_crosses = attributes["val"]
2374
2479
  elsif @inside_val_ax
2375
- @val_axis_crosses = attributes["val"]
2480
+ if @is_secondary_val_ax
2481
+ @sec_val_axis_crosses = attributes["val"]
2482
+ else
2483
+ @val_axis_crosses = attributes["val"]
2484
+ end
2376
2485
  end
2377
2486
  end
2378
2487
  when "crossesAt"
@@ -2380,7 +2489,11 @@ module Xlsxrb
2380
2489
  if @inside_cat_ax
2381
2490
  @cat_axis_crosses_at = attributes["val"]&.to_f
2382
2491
  elsif @inside_val_ax
2383
- @val_axis_crosses_at = attributes["val"]&.to_f
2492
+ if @is_secondary_val_ax
2493
+ @sec_val_axis_crosses_at = attributes["val"]&.to_f
2494
+ else
2495
+ @val_axis_crosses_at = attributes["val"]&.to_f
2496
+ end
2384
2497
  end
2385
2498
  end
2386
2499
  when "tickLblSkip"
@@ -2400,22 +2513,40 @@ module Xlsxrb
2400
2513
  if @inside_cat_ax
2401
2514
  @cat_axis_pos = attributes["val"]
2402
2515
  elsif @inside_val_ax
2403
- @val_axis_pos = attributes["val"]
2516
+ if @is_secondary_val_ax
2517
+ @sec_val_axis_pos = attributes["val"]
2518
+ else
2519
+ @val_axis_pos = attributes["val"]
2520
+ end
2404
2521
  end
2405
2522
  end
2406
2523
  when "crossBetween"
2407
- @val_axis_cross_between = attributes["val"] if @inside_val_ax && attributes["val"]
2524
+ if @inside_val_ax && attributes["val"]
2525
+ if @is_secondary_val_ax
2526
+ @sec_val_axis_cross_between = attributes["val"]
2527
+ else
2528
+ @val_axis_cross_between = attributes["val"]
2529
+ end
2530
+ end
2408
2531
  when "majorUnit"
2409
2532
  if @inside_cat_ax && @cat_axis_type == :date && attributes["val"]
2410
2533
  @cat_axis_major_unit = attributes["val"].to_f
2411
2534
  elsif @inside_val_ax && attributes["val"]
2412
- @val_axis_major_unit = attributes["val"].to_f
2535
+ if @is_secondary_val_ax
2536
+ @sec_val_axis_major_unit = attributes["val"].to_f
2537
+ else
2538
+ @val_axis_major_unit = attributes["val"].to_f
2539
+ end
2413
2540
  end
2414
2541
  when "minorUnit"
2415
2542
  if @inside_cat_ax && @cat_axis_type == :date && attributes["val"]
2416
2543
  @cat_axis_minor_unit = attributes["val"].to_f
2417
2544
  elsif @inside_val_ax && attributes["val"]
2418
- @val_axis_minor_unit = attributes["val"].to_f
2545
+ if @is_secondary_val_ax
2546
+ @sec_val_axis_minor_unit = attributes["val"].to_f
2547
+ else
2548
+ @val_axis_minor_unit = attributes["val"].to_f
2549
+ end
2419
2550
  end
2420
2551
  when "baseTimeUnit"
2421
2552
  @cat_axis_base_time_unit = attributes["val"] if @inside_cat_ax && attributes["val"]
@@ -2490,7 +2621,11 @@ module Xlsxrb
2490
2621
  if @inside_cat_ax
2491
2622
  @cat_axis_tick_lbl_pos = attributes["val"]
2492
2623
  elsif @inside_val_ax
2493
- @val_axis_tick_lbl_pos = attributes["val"]
2624
+ if @is_secondary_val_ax
2625
+ @sec_val_axis_tick_lbl_pos = attributes["val"]
2626
+ else
2627
+ @val_axis_tick_lbl_pos = attributes["val"]
2628
+ end
2494
2629
  end
2495
2630
  end
2496
2631
  when "majorGridlines"
@@ -2499,8 +2634,13 @@ module Xlsxrb
2499
2634
  @cat_axis_major_gridlines = true
2500
2635
  @gridlines_target = :cat_axis_major_gridlines
2501
2636
  elsif @inside_val_ax
2502
- @val_axis_major_gridlines = true
2503
- @gridlines_target = :val_axis_major_gridlines
2637
+ if @is_secondary_val_ax
2638
+ @sec_val_axis_major_gridlines = true
2639
+ @gridlines_target = :sec_val_axis_major_gridlines
2640
+ else
2641
+ @val_axis_major_gridlines = true
2642
+ @gridlines_target = :val_axis_major_gridlines
2643
+ end
2504
2644
  end
2505
2645
  when "minorGridlines"
2506
2646
  @inside_gridlines = true
@@ -2508,8 +2648,13 @@ module Xlsxrb
2508
2648
  @cat_axis_minor_gridlines = true
2509
2649
  @gridlines_target = :cat_axis_minor_gridlines
2510
2650
  elsif @inside_val_ax
2511
- @val_axis_minor_gridlines = true
2512
- @gridlines_target = :val_axis_minor_gridlines
2651
+ if @is_secondary_val_ax
2652
+ @sec_val_axis_minor_gridlines = true
2653
+ @gridlines_target = :sec_val_axis_minor_gridlines
2654
+ else
2655
+ @val_axis_minor_gridlines = true
2656
+ @gridlines_target = :val_axis_minor_gridlines
2657
+ end
2513
2658
  end
2514
2659
  when "plotVisOnly"
2515
2660
  @plot_vis_only = attributes["val"] == "1" if attributes["val"]
@@ -2616,7 +2761,11 @@ module Xlsxrb
2616
2761
  if @inside_cat_ax
2617
2762
  @cat_axis_title = @text_buffer.dup
2618
2763
  elsif @inside_val_ax
2619
- @val_axis_title = @text_buffer.dup
2764
+ if @is_secondary_val_ax
2765
+ @sec_val_axis_title = @text_buffer.dup
2766
+ else
2767
+ @val_axis_title = @text_buffer.dup
2768
+ end
2620
2769
  end
2621
2770
  elsif @inside_title && @title_depth == 1
2622
2771
  @title = @text_buffer.dup
@@ -2737,7 +2886,11 @@ module Xlsxrb
2737
2886
  @current_trendline[:name] = @text_buffer.dup if @inside_trendline_name && @current_trendline
2738
2887
  @inside_trendline_name = false
2739
2888
  when "ser"
2740
- @series << @current_ser if @current_ser
2889
+ if @current_ser
2890
+ @current_ser[:chart_type] = @current_chart_type if @current_chart_type
2891
+ @current_chart_series << @current_ser if @current_chart_series
2892
+ @series << @current_ser
2893
+ end
2741
2894
  @current_ser = nil
2742
2895
  @inside_ser = false
2743
2896
  @inside_ser_sp_pr = false
@@ -3007,6 +3160,7 @@ module Xlsxrb
3007
3160
  @inside_axis_def_rpr = false
3008
3161
  when "valAx"
3009
3162
  @inside_val_ax = false
3163
+ @is_secondary_val_ax = false
3010
3164
  @inside_axis_tx_pr = false
3011
3165
  @inside_axis_def_rpr = false
3012
3166
  @inside_disp_units = false
@@ -3145,6 +3299,11 @@ module Xlsxrb
3145
3299
  @inside_leader_lines_ln = false
3146
3300
  @inside_leader_lines_solid_fill = false
3147
3301
  end
3302
+
3303
+ return unless CHART_TYPES.include?(name)
3304
+
3305
+ @current_chart_type = nil
3306
+ @current_chart_series = nil
3148
3307
  end
3149
3308
 
3150
3309
  private
@@ -3154,7 +3313,11 @@ module Xlsxrb
3154
3313
  if @inside_cat_ax
3155
3314
  (@cat_axis_title_layout ||= {})[key] = value
3156
3315
  elsif @inside_val_ax
3157
- (@val_axis_title_layout ||= {})[key] = value
3316
+ if @is_secondary_val_ax
3317
+ (@sec_val_axis_title_layout ||= {})[key] = value
3318
+ else
3319
+ (@val_axis_title_layout ||= {})[key] = value
3320
+ end
3158
3321
  end
3159
3322
  elsif @inside_title && @title_depth == 1
3160
3323
  (@title_layout ||= {})[key] = value
@@ -449,6 +449,12 @@ module Xlsxrb
449
449
  @properties[:summary_right] = %w[1 true].include?(sr) unless sr.nil?
450
450
  sos = attributes["showOutlineSymbols"]
451
451
  @properties[:show_outline_symbols] = %w[1 true].include?(sos) unless sos.nil?
452
+ @properties[:outline_properties] = {
453
+ apply_styles: @properties[:apply_styles],
454
+ summary_below: @properties[:summary_below],
455
+ summary_right: @properties[:summary_right],
456
+ show_outline_symbols: @properties[:show_outline_symbols]
457
+ }.compact
452
458
  end
453
459
  when "pageSetUpPr"
454
460
  if @inside_sheet_pr
@@ -1394,7 +1400,11 @@ module Xlsxrb
1394
1400
  name = element_name(local_name, qname)
1395
1401
  case name
1396
1402
  when "calculatedColumnFormula"
1397
- @current_column[:calculated_column_formula] = @text_buffer.dup if @current_column
1403
+ if @current_column
1404
+ val = @text_buffer.dup
1405
+ @current_column[:calculated_column_formula] = val
1406
+ @current_column[:data_formula] = val
1407
+ end
1398
1408
  @inside_calc_formula = false
1399
1409
  when "totalsRowFormula"
1400
1410
  @current_column[:totals_row_formula] = @text_buffer.dup if @current_column
@@ -1803,6 +1813,103 @@ module Xlsxrb
1803
1813
  end
1804
1814
  end
1805
1815
  end
1816
+
1817
+ # SAX2 listener for parsing <personList> element in xl/persons/person.xml.
1818
+ class PersonListListener
1819
+ include REXML::SAX2Listener
1820
+
1821
+ attr_reader :persons
1822
+
1823
+ def initialize
1824
+ @persons = []
1825
+ end
1826
+
1827
+ def start_element(_uri, local_name, qname, attributes)
1828
+ name = element_name(local_name, qname)
1829
+ return unless name == "person"
1830
+
1831
+ @persons << {
1832
+ id: attributes["id"],
1833
+ display_name: attributes["displayName"],
1834
+ user_id: attributes["userId"],
1835
+ provider_id: attributes["providerId"]
1836
+ }.compact
1837
+ end
1838
+
1839
+ def end_element(_uri, _local_name, _qname); end
1840
+
1841
+ private
1842
+
1843
+ def element_name(local_name, qname)
1844
+ if local_name.nil? || local_name.empty?
1845
+ qname.to_s.split(":").last
1846
+ else
1847
+ local_name
1848
+ end
1849
+ end
1850
+ end
1851
+
1852
+ # SAX2 listener for parsing <ThreadedComments> element in xl/threadedComments/threadedComment{N}.xml.
1853
+ class ThreadedCommentsListener
1854
+ include REXML::SAX2Listener
1855
+
1856
+ attr_reader :threaded_comments
1857
+
1858
+ def initialize
1859
+ @threaded_comments = []
1860
+ @current_comment = nil
1861
+ @inside_text = false
1862
+ @text_buffer = +""
1863
+ end
1864
+
1865
+ def start_element(_uri, local_name, qname, attributes)
1866
+ name = element_name(local_name, qname)
1867
+ case name
1868
+ when "threadedComment"
1869
+ @current_comment = {
1870
+ ref: attributes["ref"],
1871
+ dt: attributes["dT"] || attributes["dt"],
1872
+ person_id: attributes["personId"],
1873
+ id: attributes["id"]
1874
+ }
1875
+ @current_comment[:parent_id] = attributes["parentId"] if attributes["parentId"]
1876
+ @current_comment[:done] = %w[1 true].include?(attributes["done"]) if attributes.key?("done")
1877
+ when "text"
1878
+ if @current_comment
1879
+ @inside_text = true
1880
+ @text_buffer = +""
1881
+ end
1882
+ end
1883
+ end
1884
+
1885
+ def characters(text)
1886
+ @text_buffer << text if @inside_text
1887
+ end
1888
+
1889
+ def end_element(_uri, local_name, qname)
1890
+ name = element_name(local_name, qname)
1891
+ case name
1892
+ when "text"
1893
+ if @current_comment && @inside_text
1894
+ @current_comment[:text] = @text_buffer.dup
1895
+ @inside_text = false
1896
+ end
1897
+ when "threadedComment"
1898
+ @threaded_comments << @current_comment if @current_comment
1899
+ @current_comment = nil
1900
+ end
1901
+ end
1902
+
1903
+ private
1904
+
1905
+ def element_name(local_name, qname)
1906
+ if local_name.nil? || local_name.empty?
1907
+ qname.to_s.split(":").last
1908
+ else
1909
+ local_name
1910
+ end
1911
+ end
1912
+ end
1806
1913
  end
1807
1914
  end
1808
1915
  end
@@ -85,12 +85,12 @@ module Xlsxrb
85
85
  }
86
86
  %w[applyNumberFormat applyFont applyFill applyBorder applyAlignment applyProtection].each do |attr|
87
87
  key = attr.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }.to_sym
88
- xf_entry[key] = attributes[attr] == "1" unless attributes[attr].nil?
88
+ xf_entry[key] = boolean_val(attributes[attr]) unless attributes[attr].nil?
89
89
  end
90
90
  if @inside_cell_xfs
91
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"
92
+ xf_entry[:quote_prefix] = true if boolean_val(attributes["quotePrefix"])
93
+ xf_entry[:pivot_button] = true if boolean_val(attributes["pivotButton"])
94
94
  @cell_xfs << xf_entry
95
95
  @current_xf = xf_entry
96
96
  elsif @inside_cell_style_xfs
@@ -103,15 +103,15 @@ module Xlsxrb
103
103
  alignment = {}
104
104
  alignment[:horizontal] = attributes["horizontal"] if attributes["horizontal"]
105
105
  alignment[:vertical] = attributes["vertical"] if attributes["vertical"]
106
- alignment[:wrap_text] = true if attributes["wrapText"] == "1"
106
+ alignment[:wrap_text] = true if boolean_val(attributes["wrapText"])
107
107
  alignment[:text_rotation] = attributes["textRotation"].to_i if attributes["textRotation"]
108
108
  alignment[:indent] = attributes["indent"].to_i if attributes["indent"]
109
109
  ri = attributes["relativeIndent"]
110
110
  alignment[:relative_indent] = ri.to_i if ri
111
- alignment[:shrink_to_fit] = true if attributes["shrinkToFit"] == "1"
111
+ alignment[:shrink_to_fit] = true if boolean_val(attributes["shrinkToFit"])
112
112
  ro = attributes["readingOrder"]
113
113
  alignment[:reading_order] = ro.to_i if ro
114
- alignment[:justify_last_line] = true if attributes["justifyLastLine"] == "1"
114
+ alignment[:justify_last_line] = true if boolean_val(attributes["justifyLastLine"])
115
115
  unless alignment.empty?
116
116
  if @inside_dxfs && @current_dxf
117
117
  @current_dxf[:alignment] = alignment
@@ -121,8 +121,8 @@ module Xlsxrb
121
121
  end
122
122
  when "protection"
123
123
  protection = {}
124
- protection[:locked] = attributes["locked"] != "0" if attributes.key?("locked")
125
- protection[:hidden] = attributes["hidden"] == "1" if attributes.key?("hidden")
124
+ protection[:locked] = !%w[0 false].include?(attributes["locked"]&.downcase) if attributes.key?("locked")
125
+ protection[:hidden] = boolean_val(attributes["hidden"]) if attributes.key?("hidden")
126
126
  unless protection.empty?
127
127
  if @inside_dxfs && @current_dxf
128
128
  @current_dxf[:protection] = protection
@@ -194,10 +194,10 @@ module Xlsxrb
194
194
  when "border"
195
195
  if @inside_borders || @inside_dxfs
196
196
  @current_border = {}
197
- @current_border[:diagonal_up] = true if attributes["diagonalUp"] == "1"
198
- @current_border[:diagonal_down] = true if attributes["diagonalDown"] == "1"
197
+ @current_border[:diagonal_up] = true if boolean_val(attributes["diagonalUp"])
198
+ @current_border[:diagonal_down] = true if boolean_val(attributes["diagonalDown"])
199
199
  ol = attributes["outline"]
200
- @current_border[:outline] = %w[1 true].include?(ol) unless ol.nil?
200
+ @current_border[:outline] = boolean_val(ol) unless ol.nil?
201
201
  end
202
202
  when "left", "right", "top", "bottom", "diagonal", "vertical", "horizontal", "start", "end"
203
203
  if @current_border
@@ -303,6 +303,11 @@ module Xlsxrb
303
303
 
304
304
  private
305
305
 
306
+ #: (untyped val) -> bool
307
+ def boolean_val(val)
308
+ val == "1" || (val&.casecmp?("true") || false)
309
+ end
310
+
306
311
  def truthy_val?(attributes)
307
312
  val = attributes["val"]
308
313
  val.nil? || (val != "0" && val.downcase != "false")