xlsxrb 0.1.0
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 +7 -0
- data/.devcontainer/Dockerfile +64 -0
- data/.devcontainer/devcontainer.json +17 -0
- data/CHANGELOG.md +12 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +384 -0
- data/Rakefile +290 -0
- data/benchmark.rb +390 -0
- data/docs/ARCHITECTURE.md +488 -0
- data/docs/SPEC_SOURCES.md +42 -0
- data/docs/visual/VisualGallery.md +4684 -0
- data/docs/wasm/wasm_doc_helper.css +189 -0
- data/docs/wasm/wasm_doc_helper.js +320 -0
- data/lib/xlsxrb/elements/cell.rb +77 -0
- data/lib/xlsxrb/elements/column.rb +28 -0
- data/lib/xlsxrb/elements/row.rb +47 -0
- data/lib/xlsxrb/elements/types.rb +36 -0
- data/lib/xlsxrb/elements/workbook.rb +47 -0
- data/lib/xlsxrb/elements/worksheet.rb +50 -0
- data/lib/xlsxrb/elements.rb +15 -0
- data/lib/xlsxrb/ooxml/reader.rb +8048 -0
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +75 -0
- data/lib/xlsxrb/ooxml/styles_parser.rb +194 -0
- data/lib/xlsxrb/ooxml/utils.rb +122 -0
- data/lib/xlsxrb/ooxml/workbook_parser.rb +78 -0
- data/lib/xlsxrb/ooxml/workbook_writer.rb +1000 -0
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +455 -0
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +1008 -0
- data/lib/xlsxrb/ooxml/writer.rb +5450 -0
- data/lib/xlsxrb/ooxml/xml_builder.rb +113 -0
- data/lib/xlsxrb/ooxml/xml_parser.rb +68 -0
- data/lib/xlsxrb/ooxml/zip_generator.rb +162 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +158 -0
- data/lib/xlsxrb/ooxml/zip_writer.rb +213 -0
- data/lib/xlsxrb/ooxml.rb +22 -0
- data/lib/xlsxrb/style_builder.rb +241 -0
- data/lib/xlsxrb/version.rb +5 -0
- data/lib/xlsxrb.rb +1427 -0
- data/measure_memory.rb +42 -0
- data/sig/xlsxrb.rbs +23 -0
- data/vendor/sdk_runner/Program.cs +91 -0
- data/vendor/sdk_runner/sdk_runner.csproj +13 -0
- metadata +144 -0
data/lib/xlsxrb/ooxml.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ooxml/utils"
|
|
4
|
+
require_relative "ooxml/zip_reader"
|
|
5
|
+
require_relative "ooxml/zip_writer"
|
|
6
|
+
require_relative "ooxml/xml_parser"
|
|
7
|
+
require_relative "ooxml/xml_builder"
|
|
8
|
+
require_relative "ooxml/shared_strings_parser"
|
|
9
|
+
require_relative "ooxml/styles_parser"
|
|
10
|
+
require_relative "ooxml/workbook_parser"
|
|
11
|
+
require_relative "ooxml/worksheet_parser"
|
|
12
|
+
require_relative "ooxml/worksheet_writer"
|
|
13
|
+
require_relative "ooxml/workbook_writer"
|
|
14
|
+
|
|
15
|
+
module Xlsxrb
|
|
16
|
+
# Low-level OOXML infrastructure layer.
|
|
17
|
+
# Handles ZIP extraction, SAX XML parsing, and XML generation
|
|
18
|
+
# in strict accordance with ECMA-376.
|
|
19
|
+
module Ooxml
|
|
20
|
+
Event = Data.define(:type, :args, :source)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Xlsxrb
|
|
4
|
+
# Helper class for building cell styles with a fluent DSL.
|
|
5
|
+
# Encapsulates font, fill, border, alignment, and number format properties.
|
|
6
|
+
class StyleBuilder
|
|
7
|
+
def initialize(name = nil)
|
|
8
|
+
@name = name
|
|
9
|
+
@font_props = {}
|
|
10
|
+
@fill_props = {}
|
|
11
|
+
@border_props = {}
|
|
12
|
+
@num_fmt_id = nil
|
|
13
|
+
@alignment = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :name, :font_props, :fill_props, :border_props, :num_fmt_id, :alignment
|
|
17
|
+
|
|
18
|
+
# Applies option-style definitions so callers can use add_style(name, **opts)
|
|
19
|
+
# as an alternative to block-based fluent chaining.
|
|
20
|
+
def apply_options!(**opts)
|
|
21
|
+
bold(opts[:bold]) if opts.key?(:bold)
|
|
22
|
+
italic(opts[:italic]) if opts.key?(:italic)
|
|
23
|
+
size(opts[:size]) if opts.key?(:size)
|
|
24
|
+
font_name(opts[:font_name]) if opts.key?(:font_name)
|
|
25
|
+
font_color(opts[:font_color]) if opts.key?(:font_color)
|
|
26
|
+
underline(opts[:underline]) if opts.key?(:underline)
|
|
27
|
+
strike(opts[:strike]) if opts.key?(:strike)
|
|
28
|
+
vert_align(opts[:vert_align]) if opts.key?(:vert_align)
|
|
29
|
+
|
|
30
|
+
fill_color(opts[:fill_color]) if opts.key?(:fill_color)
|
|
31
|
+
if opts.key?(:fill_pattern)
|
|
32
|
+
pattern = opts[:fill_pattern] || {}
|
|
33
|
+
fill_pattern(
|
|
34
|
+
pattern[:pattern] || "solid",
|
|
35
|
+
fg_color: pattern[:fg_color],
|
|
36
|
+
bg_color: pattern[:bg_color]
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
fill_gradient(**opts[:fill_gradient]) if opts.key?(:fill_gradient) && opts[:fill_gradient]
|
|
40
|
+
|
|
41
|
+
border_all(**opts[:border_all]) if opts.key?(:border_all) && opts[:border_all]
|
|
42
|
+
border_left(**opts[:border_left]) if opts.key?(:border_left) && opts[:border_left]
|
|
43
|
+
border_right(**opts[:border_right]) if opts.key?(:border_right) && opts[:border_right]
|
|
44
|
+
border_top(**opts[:border_top]) if opts.key?(:border_top) && opts[:border_top]
|
|
45
|
+
border_bottom(**opts[:border_bottom]) if opts.key?(:border_bottom) && opts[:border_bottom]
|
|
46
|
+
|
|
47
|
+
number_format(opts[:number_format]) if opts.key?(:number_format)
|
|
48
|
+
|
|
49
|
+
align_horizontal(opts[:align_horizontal]) if opts.key?(:align_horizontal)
|
|
50
|
+
align_vertical(opts[:align_vertical]) if opts.key?(:align_vertical)
|
|
51
|
+
wrap_text(opts[:wrap_text]) if opts.key?(:wrap_text)
|
|
52
|
+
text_rotation(opts[:text_rotation]) if opts.key?(:text_rotation)
|
|
53
|
+
indent(opts[:indent]) if opts.key?(:indent)
|
|
54
|
+
shrink_to_fit(opts[:shrink_to_fit]) if opts.key?(:shrink_to_fit)
|
|
55
|
+
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# --- Font Properties ---
|
|
60
|
+
|
|
61
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
62
|
+
def bold(value = true)
|
|
63
|
+
@font_props[:bold] = value
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def italic(value = true)
|
|
68
|
+
@font_props[:italic] = value
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def size(size_value)
|
|
73
|
+
@font_props[:sz] = size_value.to_i
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def font_name(name)
|
|
78
|
+
@font_props[:name] = name
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def font_color(color)
|
|
83
|
+
@font_props[:color] = color
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def underline(val = "single")
|
|
88
|
+
@font_props[:underline] = val
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def strike(value = true)
|
|
93
|
+
@font_props[:strike] = value
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def vert_align(value)
|
|
98
|
+
@font_props[:vert_align] = value
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
102
|
+
|
|
103
|
+
# --- Fill Properties ---
|
|
104
|
+
|
|
105
|
+
def fill_pattern(pattern, fg_color: nil, bg_color: nil)
|
|
106
|
+
@fill_props[:pattern] = pattern
|
|
107
|
+
@fill_props[:fg_color] = fg_color if fg_color
|
|
108
|
+
@fill_props[:bg_color] = bg_color if bg_color
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def fill_color(color)
|
|
113
|
+
@fill_props[:pattern] = "solid"
|
|
114
|
+
@fill_props[:fg_color] = color
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def fill(pattern: "solid", fg_color: nil, bg_color: nil)
|
|
119
|
+
fill_pattern(pattern, fg_color: fg_color, bg_color: bg_color)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def fill_gradient(type:, degree: nil, stops: [])
|
|
123
|
+
@fill_props[:gradient] = {
|
|
124
|
+
type: type,
|
|
125
|
+
degree: degree,
|
|
126
|
+
stops: stops
|
|
127
|
+
}.compact
|
|
128
|
+
self
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# --- Border Properties ---
|
|
132
|
+
|
|
133
|
+
def border_all(style: "thin", color: nil)
|
|
134
|
+
color_opt = color ? { color: color } : {}
|
|
135
|
+
@border_props[:left] = { style: style, **color_opt }
|
|
136
|
+
@border_props[:right] = { style: style, **color_opt }
|
|
137
|
+
@border_props[:top] = { style: style, **color_opt }
|
|
138
|
+
@border_props[:bottom] = { style: style, **color_opt }
|
|
139
|
+
self
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def border_left(style: "thin", color: nil)
|
|
143
|
+
@border_props[:left] = { style: style, color: color }.compact
|
|
144
|
+
self
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def border_right(style: "thin", color: nil)
|
|
148
|
+
@border_props[:right] = { style: style, color: color }.compact
|
|
149
|
+
self
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def border_top(style: "thin", color: nil)
|
|
153
|
+
@border_props[:top] = { style: style, color: color }.compact
|
|
154
|
+
self
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def border_bottom(style: "thin", color: nil)
|
|
158
|
+
@border_props[:bottom] = { style: style, color: color }.compact
|
|
159
|
+
self
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# rubocop:disable Naming/MethodParameterName
|
|
163
|
+
def border_diagonal(style: "thin", color: nil, up: false, down: false)
|
|
164
|
+
@border_props[:diagonal] = { style: style, color: color }.compact
|
|
165
|
+
@border_props[:diagonal_up] = true if up
|
|
166
|
+
@border_props[:diagonal_down] = true if down
|
|
167
|
+
self
|
|
168
|
+
end
|
|
169
|
+
# rubocop:enable Naming/MethodParameterName
|
|
170
|
+
|
|
171
|
+
# --- Alignment Properties ---
|
|
172
|
+
|
|
173
|
+
def align_horizontal(value)
|
|
174
|
+
@alignment[:horizontal] = value
|
|
175
|
+
self
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def align_vertical(value)
|
|
179
|
+
@alignment[:vertical] = value
|
|
180
|
+
self
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
184
|
+
def wrap_text(value = true)
|
|
185
|
+
@alignment[:wrap_text] = value
|
|
186
|
+
self
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def shrink_to_fit(value = true)
|
|
190
|
+
@alignment[:shrink_to_fit] = value
|
|
191
|
+
self
|
|
192
|
+
end
|
|
193
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
194
|
+
|
|
195
|
+
def text_rotation(value)
|
|
196
|
+
@alignment[:text_rotation] = value
|
|
197
|
+
self
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def indent(value)
|
|
201
|
+
@alignment[:indent] = value.to_i
|
|
202
|
+
self
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# --- Number Format ---
|
|
206
|
+
|
|
207
|
+
def number_format(num_fmt_id)
|
|
208
|
+
@num_fmt_id = num_fmt_id
|
|
209
|
+
self
|
|
210
|
+
end
|
|
211
|
+
alias num_fmt number_format
|
|
212
|
+
|
|
213
|
+
# Register this style with the given Writer, returning the style_id.
|
|
214
|
+
# writer:: Xlsxrb::Ooxml::Writer instance
|
|
215
|
+
def register_with(writer)
|
|
216
|
+
font_id = 0
|
|
217
|
+
fill_id = 0
|
|
218
|
+
border_id = 0
|
|
219
|
+
|
|
220
|
+
font_id = writer.add_font(**@font_props) if @font_props.any?
|
|
221
|
+
fill_id = writer.add_fill(**@fill_props) if @fill_props.any?
|
|
222
|
+
border_id = writer.add_border(**@border_props) if @border_props.any?
|
|
223
|
+
|
|
224
|
+
resolved_num_fmt_id = if @num_fmt_id.is_a?(String)
|
|
225
|
+
writer.add_number_format(@num_fmt_id)
|
|
226
|
+
else
|
|
227
|
+
@num_fmt_id
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
cell_style_opts = {
|
|
231
|
+
num_fmt_id: resolved_num_fmt_id,
|
|
232
|
+
font_id: font_id,
|
|
233
|
+
fill_id: fill_id,
|
|
234
|
+
border_id: border_id
|
|
235
|
+
}
|
|
236
|
+
cell_style_opts[:alignment] = @alignment if @alignment.any?
|
|
237
|
+
|
|
238
|
+
writer.add_cell_style(**cell_style_opts)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|