xlsxrb 0.1.13 → 0.1.14
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 +57 -0
- data/README.md +2 -0
- data/lib/xlsxrb/colors.rb +118 -0
- data/lib/xlsxrb/elements/cell.rb +205 -16
- data/lib/xlsxrb/elements/column.rb +6 -4
- data/lib/xlsxrb/elements/coordinate_access.rb +32 -0
- data/lib/xlsxrb/elements/image.rb +128 -0
- data/lib/xlsxrb/elements/row.rb +206 -9
- data/lib/xlsxrb/elements/styles.rb +157 -0
- data/lib/xlsxrb/elements/types.rb +95 -1
- data/lib/xlsxrb/elements/workbook.rb +85 -4
- data/lib/xlsxrb/elements/worksheet.rb +424 -29
- data/lib/xlsxrb/elements.rb +2 -0
- data/lib/xlsxrb/number_formatter.rb +429 -0
- data/lib/xlsxrb/ooxml/cfb.rb +1 -1
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +7 -5
- data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +11 -4
- data/lib/xlsxrb/ooxml/reader.rb +12 -14
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +7 -1
- data/lib/xlsxrb/ooxml/styles_parser.rb +39 -9
- data/lib/xlsxrb/ooxml/utils.rb +34 -18
- data/lib/xlsxrb/ooxml/workbook_parser.rb +53 -15
- data/lib/xlsxrb/ooxml/workbook_writer.rb +65 -14
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +319 -79
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +206 -61
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +4 -2
- data/lib/xlsxrb/ooxml/writer.rb +14 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +109 -25
- data/lib/xlsxrb/stream_row.rb +243 -16
- data/lib/xlsxrb/stream_sheet.rb +414 -15
- data/lib/xlsxrb/stream_writer.rb +213 -17
- data/lib/xlsxrb/style_builder.rb +308 -22
- data/lib/xlsxrb/utils.rb +158 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +73 -3
- data/lib/xlsxrb/worksheet_builder.rb +227 -16
- data/lib/xlsxrb.rb +578 -79
- data/sig/generated/xlsxrb/colors.rbs +40 -0
- data/sig/generated/xlsxrb/elements/cell.rbs +113 -8
- data/sig/generated/xlsxrb/elements/column.rbs +3 -2
- data/sig/generated/xlsxrb/elements/coordinate_access.rbs +18 -0
- data/sig/generated/xlsxrb/elements/image.rbs +82 -0
- data/sig/generated/xlsxrb/elements/row.rbs +83 -4
- data/sig/generated/xlsxrb/elements/styles.rbs +79 -0
- data/sig/generated/xlsxrb/elements/types.rbs +43 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +51 -2
- data/sig/generated/xlsxrb/elements/worksheet.rbs +231 -18
- data/sig/generated/xlsxrb/number_formatter.rbs +132 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +6 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/utils.rbs +17 -8
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +9 -3
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +28 -6
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/writer.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +10 -0
- data/sig/generated/xlsxrb/stream_row.rbs +100 -6
- data/sig/generated/xlsxrb/stream_sheet.rbs +247 -13
- data/sig/generated/xlsxrb/stream_writer.rbs +120 -14
- data/sig/generated/xlsxrb/style_builder.rbs +55 -18
- data/sig/generated/xlsxrb/utils.rbs +119 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +26 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +107 -11
- data/sig/generated/xlsxrb.rbs +186 -27
- metadata +11 -1
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
# rbs_inline: enabled
|
|
4
4
|
|
|
5
5
|
require_relative "xml_parser"
|
|
6
|
+
require_relative "../elements/styles"
|
|
6
7
|
|
|
7
8
|
module Xlsxrb
|
|
8
9
|
module Ooxml
|
|
9
10
|
# SAX-based parser for xl/styles.xml.
|
|
10
|
-
# Returns
|
|
11
|
+
# Returns an Elements::Styles (Hash) with :num_fmts, :fonts, :fills, :borders, :cell_xfs, :cell_style_xfs.
|
|
11
12
|
class StylesParser
|
|
13
|
+
#: (String? xml_string) -> Elements::Styles
|
|
12
14
|
def self.parse(xml_string)
|
|
13
|
-
return
|
|
15
|
+
return Elements::Styles.new if xml_string.nil? || xml_string.empty?
|
|
14
16
|
|
|
15
17
|
listener = Listener.new
|
|
16
18
|
XmlParser.parse(xml_string, listener)
|
|
17
|
-
listener.result
|
|
19
|
+
styles = Elements::Styles.new(listener.result)
|
|
20
|
+
styles.precompute!
|
|
21
|
+
styles
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
# SAX listener for parsing styles.xml content.
|
|
@@ -37,6 +41,7 @@ module Xlsxrb
|
|
|
37
41
|
@current_fill = nil
|
|
38
42
|
@current_fill_pattern = nil
|
|
39
43
|
@current_border = nil
|
|
44
|
+
@current_border_side = nil
|
|
40
45
|
@current_xf = nil
|
|
41
46
|
@in_cell_xfs = false
|
|
42
47
|
@in_cell_style_xfs = false
|
|
@@ -70,7 +75,11 @@ module Xlsxrb
|
|
|
70
75
|
when "fgColor", "bgColor"
|
|
71
76
|
handle_fill_color(localname, attrs) if @current_fill
|
|
72
77
|
when "border"
|
|
73
|
-
|
|
78
|
+
if parent_context?("borders")
|
|
79
|
+
@current_border = {}
|
|
80
|
+
@current_border[:diagonal_up] = true if attrs["diagonalUp"] == "1"
|
|
81
|
+
@current_border[:diagonal_down] = true if attrs["diagonalDown"] == "1"
|
|
82
|
+
end
|
|
74
83
|
when "left", "right", "top", "bottom", "diagonal"
|
|
75
84
|
handle_border_side(localname, attrs)
|
|
76
85
|
when "cellXfs"
|
|
@@ -81,6 +90,8 @@ module Xlsxrb
|
|
|
81
90
|
handle_xf(attrs)
|
|
82
91
|
when "alignment"
|
|
83
92
|
handle_alignment(attrs)
|
|
93
|
+
when "protection"
|
|
94
|
+
handle_protection(attrs)
|
|
84
95
|
end
|
|
85
96
|
end
|
|
86
97
|
|
|
@@ -103,6 +114,8 @@ module Xlsxrb
|
|
|
103
114
|
@result[:borders] << @current_border
|
|
104
115
|
@current_border = nil
|
|
105
116
|
end
|
|
117
|
+
when "left", "right", "top", "bottom", "diagonal"
|
|
118
|
+
@current_border_side = nil
|
|
106
119
|
when "cellXfs"
|
|
107
120
|
@in_cell_xfs = false
|
|
108
121
|
when "cellStyleXfs"
|
|
@@ -124,10 +137,14 @@ module Xlsxrb
|
|
|
124
137
|
end
|
|
125
138
|
|
|
126
139
|
def handle_color(attrs)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@
|
|
140
|
+
if @current_font
|
|
141
|
+
color = extract_color(attrs)
|
|
142
|
+
@current_font[:color] = color unless color.empty?
|
|
143
|
+
elsif @current_border && @current_border_side
|
|
144
|
+
color = extract_color(attrs)
|
|
145
|
+
@current_border[@current_border_side] ||= {}
|
|
146
|
+
@current_border[@current_border_side][:color] = color unless color.empty?
|
|
147
|
+
end
|
|
131
148
|
end
|
|
132
149
|
|
|
133
150
|
def handle_fill_color(localname, attrs)
|
|
@@ -141,8 +158,10 @@ module Xlsxrb
|
|
|
141
158
|
def handle_border_side(side, attrs)
|
|
142
159
|
return unless @current_border
|
|
143
160
|
|
|
161
|
+
@current_border_side = side.to_sym
|
|
144
162
|
style = attrs["style"]
|
|
145
|
-
@current_border[
|
|
163
|
+
@current_border[@current_border_side] ||= {}
|
|
164
|
+
@current_border[@current_border_side][:style] = style if style
|
|
146
165
|
end
|
|
147
166
|
|
|
148
167
|
def handle_xf(attrs)
|
|
@@ -168,9 +187,20 @@ module Xlsxrb
|
|
|
168
187
|
alignment[:vertical] = attrs["vertical"] if attrs["vertical"]
|
|
169
188
|
alignment[:wrap_text] = true if attrs["wrapText"] == "1"
|
|
170
189
|
alignment[:text_rotation] = attrs["textRotation"]&.to_i if attrs["textRotation"]
|
|
190
|
+
alignment[:indent] = attrs["indent"]&.to_i if attrs["indent"]
|
|
191
|
+
alignment[:shrink_to_fit] = true if attrs["shrinkToFit"] == "1"
|
|
171
192
|
@current_xf[:alignment] = alignment unless alignment.empty?
|
|
172
193
|
end
|
|
173
194
|
|
|
195
|
+
def handle_protection(attrs)
|
|
196
|
+
return unless @current_xf
|
|
197
|
+
|
|
198
|
+
protection = {}
|
|
199
|
+
protection[:locked] = attrs["locked"] == "1" unless attrs["locked"].nil?
|
|
200
|
+
protection[:hidden] = attrs["hidden"] == "1" unless attrs["hidden"].nil?
|
|
201
|
+
@current_xf[:protection] = protection unless protection.empty?
|
|
202
|
+
end
|
|
203
|
+
|
|
174
204
|
def finalize_xf
|
|
175
205
|
return unless @current_xf
|
|
176
206
|
|
data/lib/xlsxrb/ooxml/utils.rb
CHANGED
|
@@ -14,6 +14,10 @@ module Xlsxrb
|
|
|
14
14
|
EPOCH_1900 = Date.new(1899, 12, 31) # serial 1 = Jan 1, 1900
|
|
15
15
|
EPOCH_1900_JD = EPOCH_1900.jd
|
|
16
16
|
|
|
17
|
+
# Excel 1904 date system epoch.
|
|
18
|
+
EPOCH_1904 = Date.new(1904, 1, 1) # serial 0 = Jan 1, 1904
|
|
19
|
+
EPOCH_1904_JD = EPOCH_1904.jd
|
|
20
|
+
|
|
17
21
|
# Built-in number format codes defined by SpreadsheetML.
|
|
18
22
|
BUILTIN_NUM_FMT_CODES = {
|
|
19
23
|
0 => "General",
|
|
@@ -56,35 +60,47 @@ module Xlsxrb
|
|
|
56
60
|
DEFAULT_DATETIME_FORMAT = "yyyy\\-mm\\-dd\\ hh:mm:ss"
|
|
57
61
|
|
|
58
62
|
class << self
|
|
59
|
-
# Converts a Date to an Excel serial number (1900 system).
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
# Converts a Date to an Excel serial number (1900 or 1904 system).
|
|
64
|
+
#: (Date date, ?date1904: bool) -> untyped
|
|
65
|
+
def date_to_serial(date, date1904: false)
|
|
66
|
+
if date1904
|
|
67
|
+
date.jd - EPOCH_1904_JD
|
|
68
|
+
else
|
|
69
|
+
serial = date.jd - EPOCH_1900_JD
|
|
70
|
+
# Lotus 1-2-3 bug: serial 60 = Feb 29, 1900 (doesn't exist).
|
|
71
|
+
# Dates on or after Mar 1, 1900 (raw serial >= 60) need +1.
|
|
72
|
+
serial += 1 if serial >= 60
|
|
73
|
+
serial
|
|
74
|
+
end
|
|
66
75
|
end
|
|
67
76
|
|
|
68
|
-
# Converts an Excel serial number (1900 system) to a Date.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
# Converts an Excel serial number (1900 or 1904 system) to a Date.
|
|
78
|
+
#: (Numeric serial, ?date1904: bool) -> Date
|
|
79
|
+
def serial_to_date(serial, date1904: false)
|
|
80
|
+
if date1904
|
|
81
|
+
EPOCH_1904 + serial
|
|
82
|
+
else
|
|
83
|
+
# Adjust for Lotus 1-2-3 bug.
|
|
84
|
+
serial -= 1 if serial > 60
|
|
85
|
+
EPOCH_1900 + serial
|
|
86
|
+
end
|
|
73
87
|
end
|
|
74
88
|
|
|
75
|
-
# Converts a Time to a fractional Excel serial number (1900 system).
|
|
76
|
-
|
|
89
|
+
# Converts a Time to a fractional Excel serial number (1900 or 1904 system).
|
|
90
|
+
#: (Time time, ?date1904: bool) -> Float
|
|
91
|
+
def datetime_to_serial(time, date1904: false)
|
|
77
92
|
date = time.to_date
|
|
78
|
-
day_serial = date_to_serial(date)
|
|
93
|
+
day_serial = date_to_serial(date, date1904: date1904)
|
|
79
94
|
# Fractional part: seconds since midnight / seconds per day
|
|
80
95
|
seconds_since_midnight = (time.hour * 3600) + (time.min * 60) + time.sec
|
|
81
96
|
day_serial + (seconds_since_midnight.to_f / 86_400)
|
|
82
97
|
end
|
|
83
98
|
|
|
84
|
-
# Converts a fractional Excel serial number to a Time (1900 system, UTC).
|
|
85
|
-
|
|
99
|
+
# Converts a fractional Excel serial number to a Time (1900 or 1904 system, UTC).
|
|
100
|
+
#: (Numeric serial, ?date1904: bool) -> Time
|
|
101
|
+
def serial_to_datetime(serial, date1904: false)
|
|
86
102
|
int_part, frac = serial.divmod(1)
|
|
87
|
-
date = serial_to_date(int_part)
|
|
103
|
+
date = serial_to_date(int_part, date1904: date1904)
|
|
88
104
|
total_seconds = (frac * 86_400).round
|
|
89
105
|
hours, remaining = total_seconds.divmod(3600)
|
|
90
106
|
minutes, seconds = remaining.divmod(60)
|
|
@@ -10,39 +10,77 @@ module Xlsxrb
|
|
|
10
10
|
# Returns sheet list: [{ name:, sheet_id:, r_id: }, ...].
|
|
11
11
|
class WorkbookParser
|
|
12
12
|
def self.parse(xml_string)
|
|
13
|
-
|
|
13
|
+
parse_with_properties(xml_string)[:sheets]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.parse_with_properties(xml_string)
|
|
17
|
+
return { sheets: [], date1904: false, defined_names: [] } if xml_string.nil? || xml_string.empty?
|
|
14
18
|
|
|
15
19
|
listener = Listener.new
|
|
16
20
|
XmlParser.parse(xml_string, listener)
|
|
17
|
-
listener.sheets
|
|
21
|
+
{ sheets: listener.sheets, date1904: listener.date1904, defined_names: listener.defined_names }
|
|
18
22
|
end
|
|
19
23
|
|
|
20
|
-
# SAX listener for workbook.xml sheets.
|
|
24
|
+
# SAX listener for workbook.xml sheets, properties, and defined names.
|
|
21
25
|
class Listener
|
|
22
26
|
include REXML::SAX2Listener
|
|
23
27
|
|
|
24
|
-
attr_reader :sheets
|
|
28
|
+
attr_reader :sheets, :date1904, :defined_names
|
|
25
29
|
|
|
26
30
|
def initialize
|
|
27
31
|
@sheets = []
|
|
32
|
+
@date1904 = false
|
|
33
|
+
@defined_names = []
|
|
34
|
+
@in_defined_name = false
|
|
35
|
+
@current_dn = nil
|
|
36
|
+
@dn_text = +""
|
|
28
37
|
end
|
|
29
38
|
|
|
30
39
|
def start_element(_uri, localname, _qname, attrs)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
case localname
|
|
41
|
+
when "sheet"
|
|
42
|
+
name_attr = attrs["name"]
|
|
43
|
+
name_attr = REXML::Text.unnormalize(name_attr) if name_attr
|
|
44
|
+
raw_state = attrs["state"]
|
|
45
|
+
state_sym = case raw_state
|
|
46
|
+
when "hidden" then :hidden
|
|
47
|
+
when "veryHidden" then :very_hidden
|
|
48
|
+
else :visible
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@sheets << {
|
|
52
|
+
name: name_attr,
|
|
53
|
+
sheet_id: attrs["sheetId"]&.to_i,
|
|
54
|
+
r_id: attrs["r:id"] || attrs["id"] || attrs.find { |k, _| k.end_with?(":id") }&.last,
|
|
55
|
+
state: state_sym
|
|
56
|
+
}
|
|
57
|
+
when "workbookPr"
|
|
58
|
+
d1904 = attrs["date1904"]
|
|
59
|
+
@date1904 = %w[1 true].include?(d1904) unless d1904.nil?
|
|
60
|
+
when "definedName"
|
|
61
|
+
@in_defined_name = true
|
|
62
|
+
@current_dn = {
|
|
63
|
+
name: attrs["name"],
|
|
64
|
+
hidden: %w[1 true].include?(attrs["hidden"])
|
|
65
|
+
}
|
|
66
|
+
@current_dn[:local_sheet_id] = attrs["localSheetId"].to_i if attrs["localSheetId"]
|
|
67
|
+
@current_dn[:comment] = attrs["comment"] if attrs["comment"]
|
|
68
|
+
@dn_text = +""
|
|
69
|
+
end
|
|
70
|
+
end
|
|
35
71
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
sheet_id: attrs["sheetId"]&.to_i,
|
|
39
|
-
r_id: attrs["r:id"] || attrs["id"] || attrs.find { |k, _| k.end_with?(":id") }&.last
|
|
40
|
-
}
|
|
72
|
+
def characters(text)
|
|
73
|
+
@dn_text << text if @in_defined_name
|
|
41
74
|
end
|
|
42
75
|
|
|
43
|
-
def end_element(_uri,
|
|
76
|
+
def end_element(_uri, localname, _qname)
|
|
77
|
+
return unless localname == "definedName" && @in_defined_name
|
|
44
78
|
|
|
45
|
-
|
|
79
|
+
@current_dn[:value] = @dn_text.strip
|
|
80
|
+
@defined_names << @current_dn
|
|
81
|
+
@in_defined_name = false
|
|
82
|
+
@current_dn = nil
|
|
83
|
+
end
|
|
46
84
|
end
|
|
47
85
|
end
|
|
48
86
|
|
|
@@ -259,7 +259,7 @@ module Xlsxrb
|
|
|
259
259
|
comment_idx += 1
|
|
260
260
|
comment_writer = Xlsxrb::Ooxml::Writer.new
|
|
261
261
|
sheet_comments.each do |c|
|
|
262
|
-
comment_writer.add_comment(c[:cell], c[:text], author: c[:author] || "Author")
|
|
262
|
+
comment_writer.add_comment(c[:cell] || c[:ref], c[:text], author: c[:author] || "Author")
|
|
263
263
|
end
|
|
264
264
|
zip.add_entry("xl/comments#{comment_idx}.xml", comment_writer.send(:generate_comments_xml, comment_writer.comments))
|
|
265
265
|
zip.add_entry("xl/drawings/vmlDrawing#{comment_idx}.vml", comment_writer.send(:generate_vml_drawing_xml, comment_writer.comments))
|
|
@@ -495,11 +495,17 @@ module Xlsxrb
|
|
|
495
495
|
|
|
496
496
|
b.open_tag("sheets")
|
|
497
497
|
@sheets.each_with_index do |sheet, idx|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
498
|
+
s_name = sheet.respond_to?(:name) ? sheet.name : sheet[:name]
|
|
499
|
+
sheet_attrs = {
|
|
500
|
+
name: s_name,
|
|
501
|
+
sheetId: (idx + 1).to_s,
|
|
502
|
+
"r:id": "rId#{idx + 1}"
|
|
503
|
+
}
|
|
504
|
+
state = sheet.respond_to?(:state) ? sheet.state : sheet[:state]
|
|
505
|
+
if state && state.to_sym != :visible
|
|
506
|
+
sheet_attrs[:state] = state.to_sym == :very_hidden ? "veryHidden" : "hidden"
|
|
507
|
+
end
|
|
508
|
+
b.empty_tag("sheet", sheet_attrs)
|
|
503
509
|
end
|
|
504
510
|
b.close_tag("sheets")
|
|
505
511
|
|
|
@@ -508,9 +514,16 @@ module Xlsxrb
|
|
|
508
514
|
b.open_tag("definedNames")
|
|
509
515
|
@defined_names.each do |dn|
|
|
510
516
|
dn_attrs = { name: dn[:name] }
|
|
511
|
-
|
|
517
|
+
local_id = dn[:local_sheet_id]
|
|
518
|
+
if !local_id && dn[:sheet]
|
|
519
|
+
target_name = dn[:sheet].to_s
|
|
520
|
+
matched_idx = @sheets.find_index { |s| (s.respond_to?(:name) ? s.name : s[:name]) == target_name }
|
|
521
|
+
local_id = matched_idx if matched_idx
|
|
522
|
+
end
|
|
523
|
+
dn_attrs[:localSheetId] = local_id.to_s if local_id
|
|
512
524
|
dn_attrs[:hidden] = "1" if dn[:hidden]
|
|
513
|
-
|
|
525
|
+
val = dn[:value] || (dn[:sheet] && dn[:range] ? "'#{dn[:sheet]}'!#{dn[:range]}" : dn[:range])
|
|
526
|
+
b.tag("definedName", dn_attrs) { |_| b.text(val) }
|
|
514
527
|
end
|
|
515
528
|
b.close_tag("definedNames")
|
|
516
529
|
end
|
|
@@ -581,11 +594,26 @@ module Xlsxrb
|
|
|
581
594
|
b.open_tag("styleSheet", { xmlns: SSML_NS })
|
|
582
595
|
|
|
583
596
|
# Number formats
|
|
584
|
-
|
|
597
|
+
raw_num_fmts = @styles&.dig(:num_fmts) || []
|
|
598
|
+
num_fmts = if raw_num_fmts.is_a?(Hash)
|
|
599
|
+
raw_num_fmts.map { |k, v| { num_fmt_id: k.to_i, format_code: v.to_s } }
|
|
600
|
+
elsif raw_num_fmts.is_a?(Array)
|
|
601
|
+
raw_num_fmts.map do |nf|
|
|
602
|
+
if nf.is_a?(Hash)
|
|
603
|
+
id = (nf[:num_fmt_id] || nf[:id]).to_i
|
|
604
|
+
code = (nf[:format_code] || nf[:code] || nf[:format]).to_s
|
|
605
|
+
{ num_fmt_id: id, format_code: code }
|
|
606
|
+
elsif nf.is_a?(Array) && nf.size == 2
|
|
607
|
+
{ num_fmt_id: nf[0].to_i, format_code: nf[1].to_s }
|
|
608
|
+
end
|
|
609
|
+
end.compact
|
|
610
|
+
else
|
|
611
|
+
[]
|
|
612
|
+
end
|
|
585
613
|
unless num_fmts.empty?
|
|
586
614
|
b.tag("numFmts", { count: num_fmts.size.to_s }) do |_|
|
|
587
615
|
num_fmts.each do |nf|
|
|
588
|
-
b.empty_tag("numFmt", { numFmtId: nf[:num_fmt_id].to_s, formatCode: nf[:format_code] })
|
|
616
|
+
b.empty_tag("numFmt", { numFmtId: nf[:num_fmt_id].to_s, formatCode: nf[:format_code] })
|
|
589
617
|
end
|
|
590
618
|
end
|
|
591
619
|
end
|
|
@@ -596,13 +624,13 @@ module Xlsxrb
|
|
|
596
624
|
b.tag("fonts", { count: fonts.size.to_s }) do |_|
|
|
597
625
|
fonts.each do |font_props|
|
|
598
626
|
b.tag("font") do |_|
|
|
599
|
-
b.empty_tag("sz", { val: (font_props[:sz] || 11).to_s }) if font_props[:sz] || fonts == [{}]
|
|
600
627
|
b.empty_tag("b") if font_props[:bold]
|
|
601
628
|
b.empty_tag("i") if font_props[:italic]
|
|
602
629
|
b.empty_tag("strike") if font_props[:strike]
|
|
603
630
|
if font_props[:underline]
|
|
604
631
|
b.empty_tag("u", font_props[:underline] == "single" ? {} : { val: font_props[:underline] })
|
|
605
632
|
end
|
|
633
|
+
b.empty_tag("sz", { val: (font_props[:sz] || 11).to_s }) if font_props[:sz] || fonts == [{}]
|
|
606
634
|
b.empty_tag("color", { rgb: font_props[:color] }) if font_props[:color]
|
|
607
635
|
b.empty_tag("name", { val: font_props[:name] || "Calibri" })
|
|
608
636
|
end
|
|
@@ -646,7 +674,16 @@ module Xlsxrb
|
|
|
646
674
|
side_data = border_props[side]
|
|
647
675
|
if side_data
|
|
648
676
|
b.tag(side.to_s, { style: side_data[:style] }) do |_|
|
|
649
|
-
|
|
677
|
+
c = side_data[:color]
|
|
678
|
+
if c.is_a?(Hash)
|
|
679
|
+
c_attrs = {}
|
|
680
|
+
c_attrs[:rgb] = c[:rgb] if c[:rgb]
|
|
681
|
+
c_attrs[:theme] = c[:theme].to_s if c[:theme]
|
|
682
|
+
c_attrs[:tint] = c[:tint].to_s if c[:tint]
|
|
683
|
+
b.empty_tag("color", c_attrs) unless c_attrs.empty?
|
|
684
|
+
elsif c
|
|
685
|
+
b.empty_tag("color", { rgb: c })
|
|
686
|
+
end
|
|
650
687
|
end
|
|
651
688
|
else
|
|
652
689
|
b.empty_tag(side.to_s)
|
|
@@ -662,7 +699,7 @@ module Xlsxrb
|
|
|
662
699
|
end
|
|
663
700
|
|
|
664
701
|
# Cell XFs (formatting)
|
|
665
|
-
xf_entries = @styles&.dig(:xf_entries) || []
|
|
702
|
+
xf_entries = @styles&.dig(:cell_xfs) || @styles&.dig(:xf_entries) || []
|
|
666
703
|
xf_count = [xf_entries.size, 1].max
|
|
667
704
|
b.tag("cellXfs", { count: xf_count.to_s }) do |_|
|
|
668
705
|
xf_entries.each do |xf|
|
|
@@ -889,7 +926,8 @@ module Xlsxrb
|
|
|
889
926
|
sheet_view: nil, sheet_properties: nil,
|
|
890
927
|
tables: nil, table_start_rid: nil,
|
|
891
928
|
legacy_drawing_rid: nil, sparkline_groups: nil)
|
|
892
|
-
|
|
929
|
+
date1904 = @workbook_properties&.[](:date1904) ? true : false
|
|
930
|
+
ws = WorksheetWriter.new(io, date1904: date1904)
|
|
893
931
|
ws.start(
|
|
894
932
|
columns: sheet[:columns] || [],
|
|
895
933
|
sheet_properties: sheet_properties,
|
|
@@ -914,6 +952,8 @@ module Xlsxrb
|
|
|
914
952
|
attrs[:hidden] = true if row.hidden
|
|
915
953
|
attrs[:custom_height] = true if row.custom_height
|
|
916
954
|
attrs[:outline_level] = row.outline_level if row.outline_level
|
|
955
|
+
style_idx = row.unmapped_data&.[](:style_index) || (row.respond_to?(:style_index) && row.style_index)
|
|
956
|
+
attrs[:style_index] = style_idx if style_idx
|
|
917
957
|
ws.write_row(row.index, row.cells, attrs: attrs, unmapped: row.unmapped_data || [], sst_index: @shared_strings_index)
|
|
918
958
|
end
|
|
919
959
|
end
|
|
@@ -1020,7 +1060,18 @@ module Xlsxrb
|
|
|
1020
1060
|
b.tag("cp:keywords") { |_| b.text(@core_properties[:keywords]) } if @core_properties[:keywords]
|
|
1021
1061
|
b.tag("dc:description") { |_| b.text(@core_properties[:description]) } if @core_properties[:description]
|
|
1022
1062
|
b.tag("cp:lastModifiedBy") { |_| b.text(@core_properties[:last_modified_by]) } if @core_properties[:last_modified_by]
|
|
1063
|
+
b.tag("cp:revision") { |_| b.text(@core_properties[:revision]) } if @core_properties[:revision]
|
|
1064
|
+
if @core_properties[:created]
|
|
1065
|
+
created_val = @core_properties[:created].respond_to?(:iso8601) ? @core_properties[:created].iso8601 : @core_properties[:created]
|
|
1066
|
+
b.tag("dcterms:created", { "xsi:type": "dcterms:W3CDTF" }) { |_| b.text(created_val) }
|
|
1067
|
+
end
|
|
1068
|
+
if @core_properties[:modified]
|
|
1069
|
+
modified_val = @core_properties[:modified].respond_to?(:iso8601) ? @core_properties[:modified].iso8601 : @core_properties[:modified]
|
|
1070
|
+
b.tag("dcterms:modified", { "xsi:type": "dcterms:W3CDTF" }) { |_| b.text(modified_val) }
|
|
1071
|
+
end
|
|
1023
1072
|
b.tag("cp:category") { |_| b.text(@core_properties[:category]) } if @core_properties[:category]
|
|
1073
|
+
b.tag("cp:contentStatus") { |_| b.text(@core_properties[:content_status]) } if @core_properties[:content_status]
|
|
1074
|
+
b.tag("dc:language") { |_| b.text(@core_properties[:language]) } if @core_properties[:language]
|
|
1024
1075
|
b.close_tag("cp:coreProperties")
|
|
1025
1076
|
io.string
|
|
1026
1077
|
end
|