xlsxrb 0.1.13 → 0.1.15
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 +104 -0
- data/README.md +17 -14
- 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 +121 -4
- data/lib/xlsxrb/elements/worksheet.rb +519 -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/listeners/feature_listeners.rb +7 -1
- data/lib/xlsxrb/ooxml/reader.rb +14 -17
- 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 +74 -16
- data/lib/xlsxrb/ooxml/workbook_writer.rb +82 -23
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +319 -79
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +263 -66
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +5 -3
- data/lib/xlsxrb/ooxml/writer.rb +14 -0
- data/lib/xlsxrb/ooxml/xml_builder.rb +14 -2
- data/lib/xlsxrb/ooxml/zip_reader.rb +144 -28
- data/lib/xlsxrb/stream_row.rb +243 -16
- data/lib/xlsxrb/stream_sheet.rb +647 -16
- data/lib/xlsxrb/stream_writer.rb +375 -34
- data/lib/xlsxrb/style_builder.rb +308 -22
- data/lib/xlsxrb/utils.rb +211 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +72 -11
- data/lib/xlsxrb/worksheet_builder.rb +738 -24
- data/lib/xlsxrb.rb +1073 -83
- 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 +79 -2
- data/sig/generated/xlsxrb/elements/worksheet.rbs +282 -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/workbook_writer.rbs +1 -1
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +28 -6
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +13 -3
- data/sig/generated/xlsxrb/ooxml/writer.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +13 -0
- data/sig/generated/xlsxrb/stream_row.rbs +100 -6
- data/sig/generated/xlsxrb/stream_sheet.rbs +358 -14
- data/sig/generated/xlsxrb/stream_writer.rbs +220 -30
- data/sig/generated/xlsxrb/style_builder.rbs +55 -18
- data/sig/generated/xlsxrb/utils.rbs +151 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +26 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +356 -18
- data/sig/generated/xlsxrb.rbs +223 -27
- metadata +11 -1
data/lib/xlsxrb/style_builder.rb
CHANGED
|
@@ -20,6 +20,281 @@ module Xlsxrb
|
|
|
20
20
|
grey: "FF808080"
|
|
21
21
|
}.freeze
|
|
22
22
|
|
|
23
|
+
CSS_COLORS = {
|
|
24
|
+
aliceblue: "F0F8FF",
|
|
25
|
+
antiquewhite: "FAEBD7",
|
|
26
|
+
aqua: "00FFFF",
|
|
27
|
+
aquamarine: "7FFFD4",
|
|
28
|
+
azure: "F0FFFF",
|
|
29
|
+
beige: "F5F5DC",
|
|
30
|
+
bisque: "FFE4C4",
|
|
31
|
+
black: "000000",
|
|
32
|
+
blanchedalmond: "FFEBCD",
|
|
33
|
+
blue: "0000FF",
|
|
34
|
+
blueviolet: "8A2BE2",
|
|
35
|
+
brown: "A52A2A",
|
|
36
|
+
burlywood: "DEB887",
|
|
37
|
+
cadetblue: "5F9EA0",
|
|
38
|
+
chartreuse: "7FFF00",
|
|
39
|
+
chocolate: "D2691E",
|
|
40
|
+
coral: "FF7F50",
|
|
41
|
+
cornflowerblue: "6495ED",
|
|
42
|
+
cornsilk: "FFF8DC",
|
|
43
|
+
crimson: "DC143C",
|
|
44
|
+
cyan: "00FFFF",
|
|
45
|
+
darkblue: "00008B",
|
|
46
|
+
darkcyan: "008B8B",
|
|
47
|
+
darkgoldenrod: "B8860B",
|
|
48
|
+
darkgray: "A9A9A9",
|
|
49
|
+
darkgrey: "A9A9A9",
|
|
50
|
+
darkgreen: "006400",
|
|
51
|
+
darkkhaki: "BDB76B",
|
|
52
|
+
darkmagenta: "8B008B",
|
|
53
|
+
darkolivegreen: "556B2F",
|
|
54
|
+
darkorange: "FF8C00",
|
|
55
|
+
darkorchid: "9932CC",
|
|
56
|
+
darkred: "8B0000",
|
|
57
|
+
darksalmon: "E9967A",
|
|
58
|
+
darkseagreen: "8FBC8F",
|
|
59
|
+
darkslateblue: "483D8B",
|
|
60
|
+
darkslategray: "2F4F4F",
|
|
61
|
+
darkslategrey: "2F4F4F",
|
|
62
|
+
darkturquoise: "00CED1",
|
|
63
|
+
darkviolet: "9400D3",
|
|
64
|
+
deeppink: "FF1493",
|
|
65
|
+
deepskyblue: "00BFFF",
|
|
66
|
+
dimgray: "696969",
|
|
67
|
+
dimgrey: "696969",
|
|
68
|
+
dodgerblue: "1E90FF",
|
|
69
|
+
firebrick: "B22222",
|
|
70
|
+
floralwhite: "FFFAF0",
|
|
71
|
+
forestgreen: "228B22",
|
|
72
|
+
fuchsia: "FF00FF",
|
|
73
|
+
gainsboro: "DCDCDC",
|
|
74
|
+
ghostwhite: "F8F8FF",
|
|
75
|
+
gold: "FFD700",
|
|
76
|
+
goldenrod: "DAA520",
|
|
77
|
+
gray: "808080",
|
|
78
|
+
grey: "808080",
|
|
79
|
+
green: "00FF00",
|
|
80
|
+
greenyellow: "ADFF2F",
|
|
81
|
+
honeydew: "F0FFF0",
|
|
82
|
+
hotpink: "FF69B4",
|
|
83
|
+
indianred: "CD5C5C",
|
|
84
|
+
indigo: "4B0082",
|
|
85
|
+
ivory: "FFFFF0",
|
|
86
|
+
khaki: "F0E68C",
|
|
87
|
+
lavender: "E6E6FA",
|
|
88
|
+
lavenderblush: "FFF0F5",
|
|
89
|
+
lawngreen: "7CFC00",
|
|
90
|
+
lemonchiffon: "FFFACD",
|
|
91
|
+
lightblue: "ADD8E6",
|
|
92
|
+
lightcoral: "F08080",
|
|
93
|
+
lightcyan: "E0FFFF",
|
|
94
|
+
lightgoldenrodyellow: "FAFAD2",
|
|
95
|
+
lightgray: "D3D3D3",
|
|
96
|
+
lightgrey: "D3D3D3",
|
|
97
|
+
lightgreen: "90EE90",
|
|
98
|
+
lightpink: "FFB6C1",
|
|
99
|
+
lightsalmon: "FFA07A",
|
|
100
|
+
lightseagreen: "20B2AA",
|
|
101
|
+
lightskyblue: "87CEFA",
|
|
102
|
+
lightslategray: "778899",
|
|
103
|
+
lightslategrey: "778899",
|
|
104
|
+
lightsteelblue: "B0C4DE",
|
|
105
|
+
lightyellow: "FFFFE0",
|
|
106
|
+
lime: "00FF00",
|
|
107
|
+
limegreen: "32CD32",
|
|
108
|
+
linen: "FAF0E6",
|
|
109
|
+
magenta: "FF00FF",
|
|
110
|
+
maroon: "800000",
|
|
111
|
+
mediumaquamarine: "66CDAA",
|
|
112
|
+
mediumblue: "0000CD",
|
|
113
|
+
mediumorchid: "BA55D3",
|
|
114
|
+
mediumpurple: "9370DB",
|
|
115
|
+
mediumseagreen: "3CB371",
|
|
116
|
+
mediumslateblue: "7B68EE",
|
|
117
|
+
mediumspringgreen: "00FA9A",
|
|
118
|
+
mediumturquoise: "48D1CC",
|
|
119
|
+
mediumvioletred: "C71585",
|
|
120
|
+
midnightblue: "191970",
|
|
121
|
+
mintcream: "F5FFFA",
|
|
122
|
+
mistyrose: "FFE4E1",
|
|
123
|
+
moccasin: "FFE4B5",
|
|
124
|
+
navajowhite: "FFDEAD",
|
|
125
|
+
navy: "000080",
|
|
126
|
+
oldlace: "FDF5E6",
|
|
127
|
+
olive: "808000",
|
|
128
|
+
olivedrab: "6B8E23",
|
|
129
|
+
orange: "FFA500",
|
|
130
|
+
orangered: "FF4500",
|
|
131
|
+
orchid: "DA70D6",
|
|
132
|
+
palegoldenrod: "EEE8AA",
|
|
133
|
+
palegreen: "98FB98",
|
|
134
|
+
paleturquoise: "AFEEEE",
|
|
135
|
+
palevioletred: "DB7093",
|
|
136
|
+
papayawhip: "FFEFD5",
|
|
137
|
+
peachpuff: "FFDAB9",
|
|
138
|
+
peru: "CD853F",
|
|
139
|
+
pink: "FFC0CB",
|
|
140
|
+
plum: "DDA0DD",
|
|
141
|
+
powderblue: "B0E0E6",
|
|
142
|
+
purple: "800080",
|
|
143
|
+
rebeccapurple: "663399",
|
|
144
|
+
red: "FF0000",
|
|
145
|
+
rosybrown: "BC8F8F",
|
|
146
|
+
royalblue: "4169E1",
|
|
147
|
+
saddlebrown: "8B4513",
|
|
148
|
+
salmon: "FA8072",
|
|
149
|
+
sandybrown: "F4A460",
|
|
150
|
+
seagreen: "2E8B57",
|
|
151
|
+
seashell: "FFF5EE",
|
|
152
|
+
sienna: "A0522D",
|
|
153
|
+
silver: "C0C0C0",
|
|
154
|
+
skyblue: "87CEEB",
|
|
155
|
+
slateblue: "6A5ACD",
|
|
156
|
+
slategray: "708090",
|
|
157
|
+
slategrey: "708090",
|
|
158
|
+
snow: "FFFAFA",
|
|
159
|
+
springgreen: "00FF7F",
|
|
160
|
+
steelblue: "4682B4",
|
|
161
|
+
tan: "D2B48C",
|
|
162
|
+
teal: "008080",
|
|
163
|
+
thistle: "D8BFD8",
|
|
164
|
+
tomato: "FF6347",
|
|
165
|
+
turquoise: "40E0D0",
|
|
166
|
+
violet: "EE82EE",
|
|
167
|
+
wheat: "F5DEB3",
|
|
168
|
+
white: "FFFFFF",
|
|
169
|
+
whitesmoke: "F5F5F5",
|
|
170
|
+
yellow: "FFFF00",
|
|
171
|
+
yellowgreen: "9ACD32"
|
|
172
|
+
}.freeze
|
|
173
|
+
|
|
174
|
+
BORDER_STYLES = {
|
|
175
|
+
none: :none,
|
|
176
|
+
thin: :thin,
|
|
177
|
+
medium: :medium,
|
|
178
|
+
dashed: :dashed,
|
|
179
|
+
dotted: :dotted,
|
|
180
|
+
thick: :thick,
|
|
181
|
+
double: :double,
|
|
182
|
+
hair: :hair,
|
|
183
|
+
medium_dashed: :mediumDashed,
|
|
184
|
+
mediumdashed: :mediumDashed,
|
|
185
|
+
mediumDashed: :mediumDashed,
|
|
186
|
+
dash_dot: :dashDot,
|
|
187
|
+
dashdot: :dashDot,
|
|
188
|
+
dashDot: :dashDot,
|
|
189
|
+
medium_dash_dot: :mediumDashDot,
|
|
190
|
+
mediumdashdot: :mediumDashDot,
|
|
191
|
+
mediumDashDot: :mediumDashDot,
|
|
192
|
+
dash_dot_dot: :dashDotDot,
|
|
193
|
+
dashdotdot: :dashDotDot,
|
|
194
|
+
dashDotDot: :dashDotDot,
|
|
195
|
+
medium_dash_dot_dot: :mediumDashDotDot,
|
|
196
|
+
mediumdashdotdot: :mediumDashDotDot,
|
|
197
|
+
mediumDashDotDot: :mediumDashDotDot,
|
|
198
|
+
slant_dash_dot: :slantDashDot,
|
|
199
|
+
slantdashdot: :slantDashDot,
|
|
200
|
+
slantDashDot: :slantDashDot
|
|
201
|
+
}.freeze
|
|
202
|
+
|
|
203
|
+
BORDER_INDEX_MAP = {
|
|
204
|
+
0 => :none,
|
|
205
|
+
1 => :thin,
|
|
206
|
+
2 => :medium,
|
|
207
|
+
3 => :dashed,
|
|
208
|
+
4 => :dotted,
|
|
209
|
+
5 => :thick,
|
|
210
|
+
6 => :double,
|
|
211
|
+
7 => :hair,
|
|
212
|
+
8 => :mediumDashed,
|
|
213
|
+
9 => :dashDot,
|
|
214
|
+
10 => :mediumDashDot,
|
|
215
|
+
11 => :dashDotDot,
|
|
216
|
+
12 => :mediumDashDotDot,
|
|
217
|
+
13 => :slantDashDot
|
|
218
|
+
}.freeze
|
|
219
|
+
|
|
220
|
+
# Normalizes a color definition (CSS color name, hex code, or RGB integer) to an OpenXML hex color string.
|
|
221
|
+
#
|
|
222
|
+
# @example
|
|
223
|
+
# StyleBuilder.normalize_color(:navy) #=> "FF000080"
|
|
224
|
+
# StyleBuilder.normalize_color("#FF0000") #=> "FFFF0000"
|
|
225
|
+
# StyleBuilder.normalize_color("#F00") #=> "FFFF0000"
|
|
226
|
+
# StyleBuilder.normalize_color("FF0000", alpha: false) #=> "FF0000"
|
|
227
|
+
# StyleBuilder.normalize_color(0xFF0000) #=> "FFFF0000"
|
|
228
|
+
#
|
|
229
|
+
# @param color [String, Symbol, Integer, nil]
|
|
230
|
+
# @param alpha [Boolean] Whether to include the alpha channel ("AARRGGBB" vs "RRGGBB").
|
|
231
|
+
# @return [String, nil]
|
|
232
|
+
# @api public
|
|
233
|
+
#: (String | Symbol | Integer | nil color, ?alpha: bool) -> String?
|
|
234
|
+
def self.normalize_color(color, alpha: true)
|
|
235
|
+
return nil if color.nil?
|
|
236
|
+
|
|
237
|
+
hex = case color
|
|
238
|
+
when Symbol, defined?(String) && String
|
|
239
|
+
str = color.to_s.strip
|
|
240
|
+
return nil if str.empty?
|
|
241
|
+
|
|
242
|
+
# Strip leading colon (e.g. ":red")
|
|
243
|
+
str = str.sub(/^:/, "")
|
|
244
|
+
return nil if str.empty?
|
|
245
|
+
|
|
246
|
+
sym_key = str.downcase.to_sym
|
|
247
|
+
if CSS_COLORS.key?(sym_key)
|
|
248
|
+
CSS_COLORS[sym_key]
|
|
249
|
+
else
|
|
250
|
+
# Hex string parsing
|
|
251
|
+
clean_hex = str.sub(/^#/, "")
|
|
252
|
+
case clean_hex.length
|
|
253
|
+
when 3
|
|
254
|
+
if clean_hex.match?(/\A[0-9a-fA-F]{3}\z/)
|
|
255
|
+
r, g, b = clean_hex.chars
|
|
256
|
+
"#{r}#{r}#{g}#{g}#{b}#{b}".upcase
|
|
257
|
+
end
|
|
258
|
+
when 6
|
|
259
|
+
clean_hex.upcase if clean_hex.match?(/\A[0-9a-fA-F]{6}\z/)
|
|
260
|
+
when 8
|
|
261
|
+
if clean_hex.match?(/\A[0-9a-fA-F]{8}\z/)
|
|
262
|
+
return alpha ? clean_hex.upcase : clean_hex[2..7].upcase
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
when Integer
|
|
267
|
+
format("%06X", color & 0xFFFFFF)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
return nil unless hex
|
|
271
|
+
|
|
272
|
+
alpha ? "FF#{hex}" : hex
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Normalizes a border style symbol, string, or integer index to an OOXML ST_BorderStyle symbol.
|
|
276
|
+
#
|
|
277
|
+
# @example
|
|
278
|
+
# StyleBuilder.normalize_border_style(:border_thin) #=> :thin
|
|
279
|
+
# StyleBuilder.normalize_border_style(1) #=> :thin
|
|
280
|
+
# StyleBuilder.normalize_border_style(:medium_dashed) #=> :mediumDashed
|
|
281
|
+
# StyleBuilder.normalize_border_style("double") #=> :double
|
|
282
|
+
# StyleBuilder.normalize_border_style(0) #=> :none
|
|
283
|
+
#
|
|
284
|
+
# @param style [Symbol, String, Integer, nil]
|
|
285
|
+
# @return [Symbol, nil]
|
|
286
|
+
# @api public
|
|
287
|
+
#: (Symbol | String | Integer | nil style) -> Symbol?
|
|
288
|
+
def self.normalize_border_style(style)
|
|
289
|
+
return nil if style.nil?
|
|
290
|
+
|
|
291
|
+
return BORDER_INDEX_MAP[style] if style.is_a?(Integer)
|
|
292
|
+
|
|
293
|
+
str = style.to_s.sub(/^border_/, "")
|
|
294
|
+
sym = str.to_sym
|
|
295
|
+
BORDER_STYLES[sym] || BORDER_STYLES[str.downcase.to_sym]
|
|
296
|
+
end
|
|
297
|
+
|
|
23
298
|
# Resolves a color symbol, string, or prefix to an aRGB hex string.
|
|
24
299
|
#
|
|
25
300
|
# @param color [Symbol, String, Object, nil]
|
|
@@ -28,10 +303,15 @@ module Xlsxrb
|
|
|
28
303
|
def self.resolve_color(color)
|
|
29
304
|
return nil unless color
|
|
30
305
|
|
|
306
|
+
return Colors.palette_color(color, alpha: true) if color.is_a?(Integer) && defined?(Colors) && color >= 0 && color < Colors::INDEXED_COLORS.size
|
|
307
|
+
|
|
31
308
|
if color.is_a?(Symbol) || (color.is_a?(String) && color.start_with?(":") && color.length > 1)
|
|
32
309
|
key = color.to_s.sub(/^:/, "").to_sym
|
|
33
|
-
return COLORS[key] || color.to_s
|
|
310
|
+
return COLORS[key] || (CSS_COLORS[key] ? "FF#{CSS_COLORS[key]}" : nil) || color.to_s
|
|
34
311
|
end
|
|
312
|
+
|
|
313
|
+
return normalize_color(color, alpha: true) || color.to_s if color.is_a?(String) && color.start_with?("#")
|
|
314
|
+
|
|
35
315
|
color.to_s
|
|
36
316
|
end
|
|
37
317
|
|
|
@@ -314,75 +594,81 @@ module Xlsxrb
|
|
|
314
594
|
end
|
|
315
595
|
|
|
316
596
|
# Sets all borders.
|
|
317
|
-
# @param style [String, Symbol] The border style.
|
|
597
|
+
# @param style [String, Symbol, Integer] The border style.
|
|
318
598
|
# @param color [String, Symbol, nil] The color.
|
|
319
599
|
# @return [self]
|
|
320
600
|
# @api public
|
|
321
|
-
#: (?style: String | Symbol, ?color: String | Symbol | nil) -> self
|
|
601
|
+
#: (?style: String | Symbol | Integer, ?color: String | Symbol | nil) -> self
|
|
322
602
|
def border_all(style: "thin", color: nil)
|
|
603
|
+
norm_style = self.class.normalize_border_style(style)&.to_s || style.to_s
|
|
323
604
|
color_opt = color ? { color: resolve_color(color) } : {}
|
|
324
|
-
@border_props[:left] = { style:
|
|
325
|
-
@border_props[:right] = { style:
|
|
326
|
-
@border_props[:top] = { style:
|
|
327
|
-
@border_props[:bottom] = { style:
|
|
605
|
+
@border_props[:left] = { style: norm_style, **color_opt }
|
|
606
|
+
@border_props[:right] = { style: norm_style, **color_opt }
|
|
607
|
+
@border_props[:top] = { style: norm_style, **color_opt }
|
|
608
|
+
@border_props[:bottom] = { style: norm_style, **color_opt }
|
|
328
609
|
self
|
|
329
610
|
end
|
|
330
611
|
|
|
331
612
|
# Sets the left border.
|
|
332
|
-
# @param style [String, Symbol] The border style.
|
|
613
|
+
# @param style [String, Symbol, Integer] The border style.
|
|
333
614
|
# @param color [String, Symbol, nil] The color.
|
|
334
615
|
# @return [self]
|
|
335
616
|
# @api public
|
|
336
|
-
#: (?style: String | Symbol, ?color: String | Symbol | nil) -> self
|
|
617
|
+
#: (?style: String | Symbol | Integer, ?color: String | Symbol | nil) -> self
|
|
337
618
|
def border_left(style: "thin", color: nil)
|
|
338
|
-
|
|
619
|
+
norm_style = self.class.normalize_border_style(style)&.to_s || style.to_s
|
|
620
|
+
@border_props[:left] = { style: norm_style, color: resolve_color(color) }.compact
|
|
339
621
|
self
|
|
340
622
|
end
|
|
341
623
|
|
|
342
624
|
# Sets the right border.
|
|
343
|
-
# @param style [String, Symbol] The border style.
|
|
625
|
+
# @param style [String, Symbol, Integer] The border style.
|
|
344
626
|
# @param color [String, Symbol, nil] The color.
|
|
345
627
|
# @return [self]
|
|
346
628
|
# @api public
|
|
347
|
-
#: (?style: String | Symbol, ?color: String | Symbol | nil) -> self
|
|
629
|
+
#: (?style: String | Symbol | Integer, ?color: String | Symbol | nil) -> self
|
|
348
630
|
def border_right(style: "thin", color: nil)
|
|
349
|
-
|
|
631
|
+
norm_style = self.class.normalize_border_style(style)&.to_s || style.to_s
|
|
632
|
+
@border_props[:right] = { style: norm_style, color: resolve_color(color) }.compact
|
|
350
633
|
self
|
|
351
634
|
end
|
|
352
635
|
|
|
353
636
|
# Sets the top border.
|
|
354
|
-
# @param style [String, Symbol] The border style.
|
|
637
|
+
# @param style [String, Symbol, Integer] The border style.
|
|
355
638
|
# @param color [String, Symbol, nil] The color.
|
|
356
639
|
# @return [self]
|
|
357
640
|
# @api public
|
|
358
|
-
#: (?style: String | Symbol, ?color: String | Symbol | nil) -> self
|
|
641
|
+
#: (?style: String | Symbol | Integer, ?color: String | Symbol | nil) -> self
|
|
359
642
|
def border_top(style: "thin", color: nil)
|
|
360
|
-
|
|
643
|
+
norm_style = self.class.normalize_border_style(style)&.to_s || style.to_s
|
|
644
|
+
@border_props[:top] = { style: norm_style, color: resolve_color(color) }.compact
|
|
361
645
|
self
|
|
362
646
|
end
|
|
363
647
|
|
|
364
648
|
# Sets the bottom border.
|
|
365
|
-
# @param style [String, Symbol] The border style.
|
|
649
|
+
# @param style [String, Symbol, Integer] The border style.
|
|
366
650
|
# @param color [String, Symbol, nil] The color.
|
|
367
651
|
# @return [self]
|
|
368
652
|
# @api public
|
|
369
|
-
#: (?style: String | Symbol, ?color: String | Symbol | nil) -> self
|
|
653
|
+
#: (?style: String | Symbol | Integer, ?color: String | Symbol | nil) -> self
|
|
370
654
|
def border_bottom(style: "thin", color: nil)
|
|
371
|
-
|
|
655
|
+
norm_style = self.class.normalize_border_style(style)&.to_s || style.to_s
|
|
656
|
+
@border_props[:bottom] = { style: norm_style, color: resolve_color(color) }.compact
|
|
372
657
|
self
|
|
373
658
|
end
|
|
374
659
|
|
|
375
660
|
# rubocop:disable Naming/MethodParameterName
|
|
376
661
|
# Sets diagonal borders.
|
|
377
|
-
# @param style [String, Symbol] The border style.
|
|
662
|
+
# @param style [String, Symbol, Integer] The border style.
|
|
378
663
|
# @param color [String, Symbol, nil] The color.
|
|
379
664
|
# @param up [Boolean] Diagonal up.
|
|
380
665
|
# @param down [Boolean] Diagonal down.
|
|
381
666
|
# @return [self]
|
|
382
667
|
# @api public
|
|
383
|
-
#: (?style: String | Symbol, ?color: String | Symbol | nil, ?up: bool, ?down: bool) -> self
|
|
668
|
+
#: (?style: String | Symbol | Integer, ?color: String | Symbol | nil, ?up: bool, ?down: bool) -> self
|
|
384
669
|
def border_diagonal(style: "thin", color: nil, up: false, down: false)
|
|
385
|
-
|
|
670
|
+
norm_style = self.class.normalize_border_style(style)&.to_s || style.to_s
|
|
671
|
+
@border_props[:diagonal] = { style: norm_style, color: resolve_color(color) }.compact
|
|
386
672
|
@border_props[:diagonal_up] = true if up
|
|
387
673
|
@border_props[:diagonal_down] = true if down
|
|
388
674
|
self
|
data/lib/xlsxrb/utils.rb
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rbs_inline: enabled
|
|
4
|
+
|
|
5
|
+
require_relative "elements/cell"
|
|
6
|
+
require_relative "ooxml/utils"
|
|
7
|
+
|
|
8
|
+
module Xlsxrb
|
|
9
|
+
# Public coordinate conversion and reference parsing utilities.
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
module Utils
|
|
13
|
+
# Converts a cell reference string (e.g. "A1", "BC100") to 0-based [row, col] coordinates.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# Xlsxrb::Utils.ref_to_row_col("A1") #=> [0, 0]
|
|
17
|
+
# Xlsxrb::Utils.ref_to_row_col("BC100") #=> [99, 54]
|
|
18
|
+
#
|
|
19
|
+
# @param cell_ref [String, nil] Excel cell reference string.
|
|
20
|
+
# @return [Array(Integer, Integer), nil] 0-based [row_idx, col_idx] tuple, or nil if invalid.
|
|
21
|
+
# @api public
|
|
22
|
+
#: (String? cell_ref) -> [Integer, Integer]?
|
|
23
|
+
def self.ref_to_row_col(cell_ref)
|
|
24
|
+
Elements::Cell.parse_ref(cell_ref)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Converts 0-based [row_idx, col_idx] coordinates to an A1-style reference string.
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# Xlsxrb::Utils.row_col_to_ref(0, 0) #=> "A1"
|
|
31
|
+
# Xlsxrb::Utils.row_col_to_ref(99, 54) #=> "BC100"
|
|
32
|
+
#
|
|
33
|
+
# @param row_idx [Integer] 0-based row index.
|
|
34
|
+
# @param col_idx [Integer] 0-based column index.
|
|
35
|
+
# @return [String] A1-style reference string.
|
|
36
|
+
# @api public
|
|
37
|
+
#: (Integer row_idx, Integer col_idx) -> String
|
|
38
|
+
def self.row_col_to_ref(row_idx, col_idx)
|
|
39
|
+
"#{Elements::Cell.column_letter(col_idx)}#{row_idx + 1}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Converts a cell range reference string (e.g. "A1:C10", "B2:D5", "A1") to 0-based
|
|
43
|
+
# [[top_row, left_col], [bottom_row, right_col]] coordinate pairs.
|
|
44
|
+
#
|
|
45
|
+
# @example Range reference
|
|
46
|
+
# Xlsxrb::Utils.parse_range("A1:C10") #=> [[0, 0], [9, 2]]
|
|
47
|
+
#
|
|
48
|
+
# @example Single cell reference
|
|
49
|
+
# Xlsxrb::Utils.parse_range("B5") #=> [[4, 1], [4, 1]]
|
|
50
|
+
#
|
|
51
|
+
# @param range_ref [String, nil] Cell range reference string.
|
|
52
|
+
# @return [Array([Integer, Integer], [Integer, Integer]), nil] 0-based coordinate pairs, or nil if invalid.
|
|
53
|
+
# @api public
|
|
54
|
+
#: (String? range_ref) -> [[Integer, Integer], [Integer, Integer]]?
|
|
55
|
+
def self.parse_range(range_ref)
|
|
56
|
+
return nil if range_ref.nil? || range_ref.empty?
|
|
57
|
+
|
|
58
|
+
parts = range_ref.split(":", 2)
|
|
59
|
+
first = Elements::Cell.parse_ref(parts[0])
|
|
60
|
+
return nil unless first
|
|
61
|
+
|
|
62
|
+
second = parts[1] ? Elements::Cell.parse_ref(parts[1]) : first
|
|
63
|
+
return nil unless second
|
|
64
|
+
|
|
65
|
+
top_row = [first[0], second[0]].min
|
|
66
|
+
bottom_row = [first[0], second[0]].max
|
|
67
|
+
left_col = [first[1], second[1]].min
|
|
68
|
+
right_col = [first[1], second[1]].max
|
|
69
|
+
[[top_row, left_col], [bottom_row, right_col]]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Converts 0-based bounding coordinates to an A1-style range reference string.
|
|
73
|
+
#
|
|
74
|
+
# @example Range coordinates
|
|
75
|
+
# Xlsxrb::Utils.range_to_ref(0, 0, 9, 2) #=> "A1:C10"
|
|
76
|
+
#
|
|
77
|
+
# @example Single cell coordinates
|
|
78
|
+
# Xlsxrb::Utils.range_to_ref(4, 1, 4, 1) #=> "B5"
|
|
79
|
+
#
|
|
80
|
+
# @param top_row [Integer] 0-based start row index.
|
|
81
|
+
# @param left_col [Integer] 0-based start column index.
|
|
82
|
+
# @param bottom_row [Integer] 0-based end row index.
|
|
83
|
+
# @param right_col [Integer] 0-based end column index.
|
|
84
|
+
# @return [String] A1-style range reference string.
|
|
85
|
+
# @api public
|
|
86
|
+
#: (Integer top_row, Integer left_col, Integer bottom_row, Integer right_col) -> String
|
|
87
|
+
def self.range_to_ref(top_row, left_col, bottom_row, right_col)
|
|
88
|
+
if top_row == bottom_row && left_col == right_col
|
|
89
|
+
row_col_to_ref(top_row, left_col)
|
|
90
|
+
else
|
|
91
|
+
"#{row_col_to_ref(top_row, left_col)}:#{row_col_to_ref(bottom_row, right_col)}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Converts a column letter name (e.g. "A", "BC") to a 0-based integer index.
|
|
96
|
+
#
|
|
97
|
+
# @example
|
|
98
|
+
# Xlsxrb::Utils.col_name_to_index("A") #=> 0
|
|
99
|
+
# Xlsxrb::Utils.col_name_to_index("BC") #=> 54
|
|
100
|
+
#
|
|
101
|
+
# @param col_name [String, Symbol, Integer] Column letter or index.
|
|
102
|
+
# @return [Integer] 0-based column index.
|
|
103
|
+
# @api public
|
|
104
|
+
#: (String | Symbol | Integer col_name) -> Integer
|
|
105
|
+
def self.col_name_to_index(col_name)
|
|
106
|
+
Elements::Cell.column_index(col_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Converts a 0-based column integer index to a column letter name (e.g. 0 -> "A", 54 -> "BC").
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# Xlsxrb::Utils.col_index_to_name(0) #=> "A"
|
|
113
|
+
# Xlsxrb::Utils.col_index_to_name(54) #=> "BC"
|
|
114
|
+
#
|
|
115
|
+
# @param col_idx [Integer] 0-based column index.
|
|
116
|
+
# @return [String] Column letter name.
|
|
117
|
+
# @api public
|
|
118
|
+
#: (Integer col_idx) -> String
|
|
119
|
+
def self.col_index_to_name(col_idx)
|
|
120
|
+
Elements::Cell.column_letter(col_idx)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Splits an A1-style cell reference into its column name and 1-based row number.
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# Xlsxrb::Utils.split_coordinate("A1") #=> ["A", 1]
|
|
127
|
+
# Xlsxrb::Utils.split_coordinate("BC100") #=> ["BC", 100]
|
|
128
|
+
#
|
|
129
|
+
# @param cell_ref [String, nil] Excel cell reference string.
|
|
130
|
+
# @return [Array(String, Integer), nil] [col_name, row_number] tuple (1-based row), or nil if invalid.
|
|
131
|
+
# @api public
|
|
132
|
+
#: (String? cell_ref) -> [String, Integer]?
|
|
133
|
+
def self.split_coordinate(cell_ref)
|
|
134
|
+
return nil unless cell_ref
|
|
135
|
+
|
|
136
|
+
coords = Elements::Cell.parse_ref(cell_ref)
|
|
137
|
+
return nil unless coords
|
|
138
|
+
|
|
139
|
+
row_idx, col_idx = coords
|
|
140
|
+
[Elements::Cell.column_letter(col_idx), row_idx + 1]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Converts a Date or Time to an Excel numeric serial number (1900 or 1904 system).
|
|
144
|
+
#
|
|
145
|
+
# @example
|
|
146
|
+
# Xlsxrb::Utils.date_to_serial(Date.new(2026, 1, 1)) #=> 46023
|
|
147
|
+
#
|
|
148
|
+
# @param date_or_time [Date, Time] Date or Time instance.
|
|
149
|
+
# @param date1904 [Boolean] Whether to use the 1904 date system.
|
|
150
|
+
# @return [Integer, Float] Serial number.
|
|
151
|
+
# @api public
|
|
152
|
+
#: (Date | Time date_or_time, ?date1904: bool) -> (Integer | Float)
|
|
153
|
+
def self.date_to_serial(date_or_time, date1904: false)
|
|
154
|
+
if date_or_time.is_a?(Time)
|
|
155
|
+
Ooxml::Utils.datetime_to_serial(date_or_time, date1904: date1904)
|
|
156
|
+
else
|
|
157
|
+
Ooxml::Utils.date_to_serial(date_or_time, date1904: date1904)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Converts an Excel serial number to a Date.
|
|
162
|
+
#
|
|
163
|
+
# @example
|
|
164
|
+
# Xlsxrb::Utils.serial_to_date(46023) #=> #<Date: 2026-01-01>
|
|
165
|
+
#
|
|
166
|
+
# @param serial [Numeric] Excel serial number.
|
|
167
|
+
# @param date1904 [Boolean] Whether to use the 1904 date system.
|
|
168
|
+
# @return [Date]
|
|
169
|
+
# @api public
|
|
170
|
+
#: (Numeric serial, ?date1904: bool) -> Date
|
|
171
|
+
def self.serial_to_date(serial, date1904: false)
|
|
172
|
+
Ooxml::Utils.serial_to_date(serial, date1904: date1904)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Converts a Time or Date to a fractional Excel serial number.
|
|
176
|
+
#
|
|
177
|
+
# @example
|
|
178
|
+
# Xlsxrb::Utils.datetime_to_serial(Time.utc(2026, 1, 1, 12, 0, 0)) #=> 46023.5
|
|
179
|
+
#
|
|
180
|
+
# @param time [Time, Date] Time or Date instance.
|
|
181
|
+
# @param date1904 [Boolean] Whether to use the 1904 date system.
|
|
182
|
+
# @return [Float] Fractional serial number.
|
|
183
|
+
# @api public
|
|
184
|
+
#: (Time | Date time, ?date1904: bool) -> Float
|
|
185
|
+
def self.datetime_to_serial(time, date1904: false)
|
|
186
|
+
if time.is_a?(Time)
|
|
187
|
+
Ooxml::Utils.datetime_to_serial(time, date1904: date1904)
|
|
188
|
+
else
|
|
189
|
+
Ooxml::Utils.date_to_serial(time, date1904: date1904).to_f
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Converts an Excel serial number to a Time (UTC).
|
|
194
|
+
#
|
|
195
|
+
# @example
|
|
196
|
+
# Xlsxrb::Utils.serial_to_time(46023.5) #=> 2026-01-01 12:00:00 UTC
|
|
197
|
+
#
|
|
198
|
+
# @param serial [Numeric] Excel serial number.
|
|
199
|
+
# @param date1904 [Boolean] Whether to use the 1904 date system.
|
|
200
|
+
# @return [Time]
|
|
201
|
+
# @api public
|
|
202
|
+
#: (Numeric serial, ?date1904: bool) -> Time
|
|
203
|
+
def self.serial_to_time(serial, date1904: false)
|
|
204
|
+
Ooxml::Utils.serial_to_datetime(serial, date1904: date1904)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class << self
|
|
208
|
+
alias serial_to_datetime serial_to_time
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
data/lib/xlsxrb/version.rb
CHANGED